From 5341ebf7e27f946000f00244aa06bc6ec7a60346 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 31 Jan 2024 11:12:48 -0500 Subject: [PATCH 01/85] =?UTF-8?q?=F0=9F=A7=AA=20asyncify=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sdk/poetry.lock | 120 ++++++++++++++++++++++++++++++++++++++- sdk/pyproject.toml | 2 + sdk/tests/test_honcho.py | 48 +++++++++------- 3 files changed, 148 insertions(+), 22 deletions(-) diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 40c58fa..50f59b6 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,5 +1,27 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -135,6 +157,62 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[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 = "httpcore" +version = "1.0.2" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "idna" version = "3.6" @@ -205,6 +283,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.4" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"}, + {file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<8" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "requests" version = "2.31.0" @@ -226,6 +322,17 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[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 = "tomli" version = "2.0.1" @@ -237,6 +344,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + [[package]] name = "urllib3" version = "2.1.0" @@ -256,4 +374,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0aa81b5a08a235ff5f275db8e6d0265d97fd1e350567862ee6b5afddaaf15620" +content-hash = "d5dbd21023598b83062e7705f329579b9175f4da4db66559ea84399246cfcc25" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index fcf44d4..5f9c596 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -10,9 +10,11 @@ packages = [{include = "honcho"}] [tool.poetry.dependencies] python = "^3.10" requests = "^2.31.0" +httpx = "^0.26.0" [tool.poetry.group.test.dependencies] pytest = "^7.4.4" +pytest-asyncio = "^0.23.4" [build-system] requires = ["poetry-core"] diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_honcho.py index 9abfb29..714c6a9 100644 --- a/sdk/tests/test_honcho.py +++ b/sdk/tests/test_honcho.py @@ -1,58 +1,64 @@ +import pytest from honcho import Client from uuid import uuid1 -def test_session_creation_retrieval(): +@pytest.mark.asyncio +async def test_session_creation_retrieval(): client = Client("http://localhost:8000") user_id = str(uuid1()) - created_session = client.create_session(user_id) - retrieved_session = client.get_session(user_id, created_session.id) + created_session = await client.create_session(user_id) + retrieved_session = await client.get_session(user_id, created_session.id) assert retrieved_session.id == created_session.id assert retrieved_session.is_active == True assert retrieved_session.location_id == "default" assert retrieved_session.session_data == {} -def test_session_multiple_retrieval(): +@pytest.mark.asyncio +async def test_session_multiple_retrieval(): client = Client("http://localhost:8000") user_id = str(uuid1()) - created_session_1 = client.create_session(user_id) - created_session_2 = client.create_session(user_id) - retrieved_sessions = client.get_sessions(user_id) + created_session_1 = await client.create_session(user_id) + created_session_2 = await client.create_session(user_id) + retrieved_sessions = await client.get_sessions(user_id) assert len(retrieved_sessions) == 2 assert retrieved_sessions[0].id == created_session_1.id assert retrieved_sessions[1].id == created_session_2.id -def test_session_update(): +@pytest.mark.asyncio +async def test_session_update(): user_id = str(uuid1()) client = Client("http://localhost:8000") - created_session = client.create_session(user_id) - assert created_session.update({"foo": "bar"}) - retrieved_session = client.get_session(user_id, created_session.id) + created_session = await client.create_session(user_id) + assert await created_session.update({"foo": "bar"}) + retrieved_session = await client.get_session(user_id, created_session.id) assert retrieved_session.session_data == {"foo": "bar"} -def test_session_deletion(): +@pytest.mark.asyncio +async def test_session_deletion(): user_id = str(uuid1()) client = Client("http://localhost:8000") - created_session = client.create_session(user_id) + created_session = await client.create_session(user_id) assert created_session.is_active == True - created_session.delete() + await created_session.delete() assert created_session.is_active == False - retrieved_session = client.get_session(user_id, created_session.id) + retrieved_session = await client.get_session(user_id, created_session.id) assert retrieved_session.is_active == False assert retrieved_session.id == created_session.id -def test_messages(): +@pytest.mark.asyncio +async def test_messages(): user_id = str(uuid1()) client = Client("http://localhost:8000") - created_session = client.create_session(user_id) - created_session.create_message(is_user=True, content="Hello") - created_session.create_message(is_user=False, content="Hi") - retrieved_session = client.get_session(user_id, created_session.id) - messages = retrieved_session.get_messages() + created_session = await client.create_session(user_id) + await created_session.create_message(is_user=True, content="Hello") + await created_session.create_message(is_user=False, content="Hi") + retrieved_session = await client.get_session(user_id, created_session.id) + messages = await retrieved_session.get_messages() assert len(messages) == 2 user_message, ai_message = messages assert user_message.content == "Hello" From 942137ff66861f893b6afb3775e3f87ee41fcc5f Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 31 Jan 2024 11:14:42 -0500 Subject: [PATCH 02/85] =?UTF-8?q?=E2=9C=A8=20asyncify=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sdk/honcho/client.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 7ac0d42..fcdf6e0 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,14 +1,15 @@ import json from typing import Dict -import requests +import httpx class Client: def __init__(self, base_url): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API + self.client = httpx.AsyncClient() - def get_session(self, user_id: str, session_id: int): + async def get_session(self, user_id: str, session_id: int): """Get a specific session for a user by ID Args: @@ -20,7 +21,7 @@ def get_session(self, user_id: str, session_id: int): """ url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" - response = requests.get(url) + response = await self.client.get(url) data = response.json() return Session( client=self, @@ -31,7 +32,7 @@ def get_session(self, user_id: str, session_id: int): session_data=data["session_data"], ) - def get_sessions(self, user_id: str, location_id: str | None = None): + async def get_sessions(self, user_id: str, location_id: str | None = None): """Return sessions associated with a user Args: @@ -45,7 +46,7 @@ def get_sessions(self, user_id: str, location_id: str | None = None): url = f"{self.base_url}/users/{user_id}/sessions" + ( f"?location_id={location_id}" if location_id else "" ) - response = requests.get(url) + response = await self.client.get(url) return [ Session( client=self, @@ -58,7 +59,7 @@ def get_sessions(self, user_id: str, location_id: str | None = None): for session in response.json() ] - def create_session( + async def create_session( self, user_id: str, location_id: str = "default", session_data: Dict = {} ): """Create a session for a user @@ -74,7 +75,7 @@ def create_session( """ data = {"location_id": location_id, "session_data": session_data} url = f"{self.base_url}/users/{user_id}/sessions" - response = requests.post(url, json=data) + response = await self.client.post(url, json=data) data = response.json() return Session( self, @@ -98,6 +99,7 @@ def __init__( ): """Constructor for Session""" self.base_url = client.base_url + self.client = client.client self.id = id self.user_id = user_id self.location_id = location_id @@ -113,7 +115,7 @@ def __str__(self): def is_active(self): return self._is_active - def create_message(self, is_user: bool, content: str): + async def create_message(self, is_user: bool, content: str): """Adds a message to the session Args: @@ -128,11 +130,11 @@ def create_message(self, is_user: bool, content: str): raise Exception("Session is inactive") data = {"is_user": is_user, "content": content} url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" - response = requests.post(url, json=data) + response = await self.client.post(url, json=data) data = response.json() return Message(self, id=data["id"], is_user=is_user, content=content) - def get_messages(self): + async def get_messages(self): """Get all messages for a session Args: @@ -144,7 +146,7 @@ def get_messages(self): """ url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" - response = requests.get(url) + response = await self.client.get(url) data = response.json() return [ Message( @@ -156,7 +158,7 @@ def get_messages(self): for message in data ] - def update(self, session_data: Dict): + async def update(self, session_data: Dict): """Update the metadata of a session Args: @@ -168,15 +170,15 @@ def update(self, session_data: Dict): """ info = {"session_data": session_data} url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" - response = requests.put(url, json=info) + response = await self.client.put(url, json=info) success = response.status_code < 400 self.session_data = session_data return success - def delete(self): + async def delete(self): """Delete a session by marking it as inactive""" url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" - response = requests.delete(url) + response = await self.client.delete(url) self._is_active = False From 387fb666f2d2a622740d0a5cb8217745ca46556a Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:29:10 -0800 Subject: [PATCH 03/85] Basic Test for Page based pagination --- api/poetry.lock | 36 +++++++++- api/pyproject.toml | 1 + api/src/crud.py | 12 ++-- api/src/main.py | 23 ++++--- api/src/schemas.py | 2 +- sdk/honcho/__init__.py | 2 +- sdk/honcho/client.py | 137 +++++++++++++++++++++++++++++++-------- sdk/tests/test_honcho.py | 70 ++++++++++++++++++-- 8 files changed, 236 insertions(+), 47 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index 1fdafca..252389c 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -118,6 +118,40 @@ typing-extensions = ">=4.8.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 = "fastapi-pagination" +version = "0.12.14" +description = "FastAPI pagination" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "fastapi_pagination-0.12.14-py3-none-any.whl", hash = "sha256:59b6c5626b1d21c610da333c7a586d625f6c81d8fa26267a4b598aae736f6753"}, + {file = "fastapi_pagination-0.12.14.tar.gz", hash = "sha256:4148694b1e170055eea0a5e691dbc640c4bf55eb0086cf11d14b164c35660559"}, +] + +[package.dependencies] +fastapi = ">=0.93.0" +pydantic = ">=1.9.1" +typing-extensions = ">=4.8.0,<5.0.0" + +[package.extras] +all = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)", "beanie (>=1.11.9,<2.0.0)", "bunnet (>=1.1.0,<2.0.0)", "databases (>=0.6.0)", "django (<5.0.0)", "mongoengine (>=0.23.1,<0.28.0)", "motor (>=2.5.1,<4.0.0)", "orm (>=0.3.1)", "ormar (>=0.11.2)", "piccolo (>=0.89,<0.122)", "pony (>=0.7.16,<0.8.0)", "scylla-driver (>=3.25.6,<4.0.0)", "sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)", "tortoise-orm (>=0.16.18,<0.21.0)"] +asyncpg = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)"] +beanie = ["beanie (>=1.11.9,<2.0.0)"] +bunnet = ["bunnet (>=1.1.0,<2.0.0)"] +databases = ["databases (>=0.6.0)"] +django = ["databases (>=0.6.0)", "django (<5.0.0)"] +mongoengine = ["mongoengine (>=0.23.1,<0.28.0)"] +motor = ["motor (>=2.5.1,<4.0.0)"] +orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"] +ormar = ["ormar (>=0.11.2)"] +piccolo = ["piccolo (>=0.89,<0.122)"] +scylla-driver = ["scylla-driver (>=3.25.6,<4.0.0)"] +sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"] +sqlmodel = ["sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)"] +tortoise = ["tortoise-orm (>=0.16.18,<0.21.0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -778,4 +812,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ff6b70507443b2f4327c19691815f1f7d2814b16e635ff62b7224f30bd81eeab" +content-hash = "115cde0c7dc1de7906b4f17bbdaced3f98a969c33c3b85976a1dc5b0aeece3e2" diff --git a/api/pyproject.toml b/api/pyproject.toml index 08d6a68..bcf746e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -13,6 +13,7 @@ python-dotenv = "^1.0.0" sqlalchemy = "^2.0.25" psycopg2-binary = "^2.9.9" slowapi = "^0.1.8" +fastapi-pagination = "^0.12.14" [build-system] diff --git a/api/src/crud.py b/api/src/crud.py index 36cfabf..b8cfdcb 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -1,7 +1,7 @@ import json from typing import Sequence, Optional -from sqlalchemy import select +from sqlalchemy import select, Select from sqlalchemy.orm import Session from . import models, schemas @@ -18,7 +18,7 @@ def get_session(db: Session, app_id: str, session_id: int, user_id: Optional[str def get_sessions( db: Session, app_id: str, user_id: str, location_id: str | None = None -) -> Sequence[schemas.Session]: +) -> Select: stmt = ( select(models.Session) .where(models.Session.app_id == app_id) @@ -29,7 +29,8 @@ def get_sessions( if location_id is not None: stmt = stmt.where(models.Session.location_id == location_id) - return db.scalars(stmt).all() + return stmt + # return db.scalars(stmt).all() # filtered_by_user = db.query(models.Session).filter( # models.Session.user_id == user_id @@ -110,12 +111,13 @@ def create_message( def get_messages( db: Session, app_id: str, user_id: str, session_id: int -) -> Sequence[schemas.Message]: +) -> Select: session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) if session is None: raise ValueError("Session not found or does not belong to user") stmt = select(models.Message).where(models.Message.session_id == session_id) - return db.scalars(stmt).all() + return stmt + # return db.scalars(stmt).all() # return ( # db.query(models.Message) # .filter(models.Message.session_id == session_id) diff --git a/api/src/main.py b/api/src/main.py index a631eeb..f35e953 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -5,6 +5,9 @@ from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded + +from fastapi_pagination import Page, add_pagination +from fastapi_pagination.ext.sqlalchemy import paginate # import uvicorn from . import crud, models, schemas @@ -17,7 +20,7 @@ router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") # Create a Limiter instance -limiter = Limiter(key_func=get_remote_address, default_limits=["5/minute"]) +limiter = Limiter(key_func=get_remote_address, default_limits=["100/minute"]) # Add SlowAPI middleware to the application app.state.limiter = limiter @@ -25,6 +28,8 @@ app.add_middleware(SlowAPIMiddleware) +add_pagination(app) + def get_db(): """FastAPI Dependency Generator for Database""" db = SessionLocal() @@ -37,7 +42,7 @@ def get_db(): # Session Routes ######################################################## -@router.get("/sessions", response_model=list[schemas.Session]) +@router.get("/sessions", response_model=Page[schemas.Session]) def get_sessions(request: Request, app_id: str, user_id: str, location_id: Optional[str] = None, db: Session = Depends(get_db)): """Get All Sessions for a User @@ -50,9 +55,11 @@ def get_sessions(request: Request, app_id: str, user_id: str, location_id: Optio list[schemas.Session]: List of Session objects """ - if location_id is not None: - return crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id) - return crud.get_sessions(db, app_id=app_id, user_id=user_id) + # if location_id is not None: + # return paginate(db, crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id)) + # return crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id) + return paginate(db, crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id)) + # return crud.get_sessions(db, app_id=app_id, user_id=user_id) @router.post("/sessions", response_model=schemas.Session) @@ -186,7 +193,7 @@ def create_message_for_session( @router.get( "/sessions/{session_id}/messages", - response_model=list[schemas.Message] + response_model=Page[schemas.Message] ) def get_messages_for_session( request: Request, @@ -210,13 +217,13 @@ def get_messages_for_session( """ try: - return crud.get_messages(db, app_id=app_id, user_id=user_id, session_id=session_id) + return paginate(db, crud.get_messages(db, app_id=app_id, user_id=user_id, session_id=session_id)) except ValueError: raise HTTPException(status_code=404, detail="Session not found") - app.include_router(router) + ######################################################## # Metacognition Routes ######################################################## diff --git a/api/src/schemas.py b/api/src/schemas.py index e3fed6e..8f51564 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -33,7 +33,7 @@ class SessionUpdate(SessionBase): class Session(SessionBase): id: int - messages: list[Message] + # messages: list[Message] is_active: bool user_id: str location_id: str diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 6d85755..fcdf6bc 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,2 +1,2 @@ -from .client import Client +from .client import Client, GetSessionResponse, GetMessageResponse from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 1a41f74..bb87b62 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,7 +1,55 @@ import json -from typing import Dict +from typing import Dict, Optional import requests +class GetSessionResponse: + def __init__(self, client, response: Dict): + self.client = client + self.total = response["total"] + self.page = response["page"] + self.page_size = response["size"] + self.pages = response["pages"] + self.sessions = [ + Session( + client=client, + id=session["id"], + user_id=session["user_id"], + location_id=session["location_id"], + is_active=session["is_active"], + session_data=session["session_data"], + ) + for session in response["items"] + ] + + def next(self): + if self.page >= self.pages: + return None + user_id = self.sessions[0].user_id + location_id = self.sessions[0].location_id + return self.client.get_sessions(user_id, location_id, self.page + 1, self.page_size) + +class GetMessageResponse: + def __init__(self, session, response: Dict): + self.session = session + self.total = response["total"] + self.page = response["page"] + self.page_size = response["size"] + self.pages = response["pages"] + self.messages = [ + Message( + session=session, + id=message["id"], + is_user=message["is_user"], + content=message["content"], + ) + for message in response["items"] + ] + + def next(self): + if self.page >= self.pages: + return None + return self.session.get_messages((self.page + 1), self.page_size) + class Client: def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): @@ -36,7 +84,7 @@ def get_session(self, user_id: str, session_id: int): session_data=data["session_data"], ) - def get_sessions(self, user_id: str, location_id: str | None = None): + def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): """Return sessions associated with a user Args: @@ -47,21 +95,39 @@ def get_sessions(self, user_id: str, location_id: str | None = None): list[Dict]: List of Session objects """ - url = f"{self.common_prefix}/users/{user_id}/sessions" + ( - f"?location_id={location_id}" if location_id else "" + url = f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}" + ( + f"&location_id={location_id}" if location_id else "" ) - response = requests.get(url) - return [ - Session( - client=self, - id=session["id"], - user_id=session["user_id"], - location_id=session["location_id"], - is_active=session["is_active"], - session_data=session["session_data"], - ) - for session in response.json() - ] + response = requests.get(url) # TODO add validation and error handling + response.raise_for_status() + return GetSessionResponse(self, response.json()) + # [ + # Session( + # client=self, + # id=session["id"], + # user_id=session["user_id"], + # location_id=session["location_id"], + # is_active=session["is_active"], + # session_data=session["session_data"], + # ) + # for session in response.json() + # ] + + def get_session_generator(self, user_id: str, location_id: Optional[str] = None): + page = 1 + page_size = 50 + get_session_response = self.get_sessions(user_id, location_id, page, page_size) + while True: + # get_session_response = self.get_sessions(user_id, location_id, page, page_size) + for session in get_session_response.sessions: + yield session + + new_sessions = get_session_response.next() + if not new_sessions: + break + + get_session_response = new_sessions + def create_session( self, user_id: str, location_id: str = "default", session_data: Dict = {} @@ -142,7 +208,7 @@ def create_message(self, is_user: bool, content: str): data = response.json() return Message(self, id=data["id"], is_user=is_user, content=content) - def get_messages(self): + def get_messages(self, page: int = 1, page_size: int = 50): """Get all messages for a session Args: @@ -153,18 +219,35 @@ def get_messages(self): list[Dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" response = requests.get(url) + response.raise_for_status() data = response.json() - return [ - Message( - self, - id=message["id"], - is_user=message["is_user"], - content=message["content"], - ) - for message in data - ] + return GetMessageResponse(self, data) + # return [ + # Message( + # self, + # id=message["id"], + # is_user=message["is_user"], + # content=message["content"], + # ) + # for message in data + # ] + def get_messages_generator(self): + page = 1 + page_size = 50 + get_messages_response = self.get_messages(page, page_size) + while True: + # get_session_response = self.get_sessions(user_id, location_id, page, page_size) + for message in get_messages_response.messages: + yield message + + new_messages = get_messages_response.next() + if not new_messages: + break + + get_messages_response = new_messages + def update(self, session_data: Dict): """Update the metadata of a session diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_honcho.py index 78609ff..646935c 100644 --- a/sdk/tests/test_honcho.py +++ b/sdk/tests/test_honcho.py @@ -1,4 +1,4 @@ -from honcho import Client +from honcho import Client, GetSessionResponse, GetMessageResponse from uuid import uuid1 import pytest @@ -20,7 +20,9 @@ def test_session_multiple_retrieval(): user_id = str(uuid1()) created_session_1 = client.create_session(user_id) created_session_2 = client.create_session(user_id) - retrieved_sessions = client.get_sessions(user_id) + response = client.get_sessions(user_id) + retrieved_sessions = response.sessions + assert len(retrieved_sessions) == 2 assert retrieved_sessions[0].id == created_session_1.id assert retrieved_sessions[1].id == created_session_2.id @@ -57,7 +59,8 @@ def test_messages(): created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") retrieved_session = client.get_session(user_id, created_session.id) - messages = retrieved_session.get_messages() + response = retrieved_session.get_messages() + messages = response.messages assert len(messages) == 2 user_message, ai_message = messages assert user_message.content == "Hello" @@ -71,7 +74,7 @@ def test_rate_limit(): client = Client(app_id, "http://localhost:8000") created_session = client.create_session(user_id) with pytest.raises(Exception): - for _ in range(10): + for _ in range(105): created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") @@ -87,3 +90,62 @@ def test_app_id_security(): with pytest.raises(Exception): client_2.get_session(user_id, created_session.id) + +def test_paginated_sessions(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Client(app_id, "http://localhost:8000") + for i in range(10): + client.create_session(user_id) + + page = 1 + page_size = 2 + get_session_response = client.get_sessions(user_id, page=page, page_size=page_size) + assert len(get_session_response.sessions) == page_size + + assert get_session_response.pages == 5 + + new_session_response = get_session_response.next() + assert new_session_response is not None + assert isinstance(new_session_response, GetSessionResponse) + assert len(new_session_response.sessions) == page_size + + final_page = client.get_sessions(user_id, page=5, page_size=page_size) + + assert len(final_page.sessions) == 2 + next_page = final_page.next() + assert next_page is None + + +def test_paginated_messages(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Client(app_id, "http://localhost:8000") + created_session = client.create_session(user_id) + for i in range(10): + created_session.create_message(is_user=True, content="Hello") + created_session.create_message(is_user=False, content="Hi") + + page_size = 7 + get_message_response = created_session.get_messages(page=1, page_size=page_size) + + assert get_message_response is not None + assert isinstance(get_message_response, GetMessageResponse) + assert len(get_message_response.messages) == page_size + + new_message_response = get_message_response.next() + + assert new_message_response is not None + assert isinstance(new_message_response, GetMessageResponse) + assert len(new_message_response.messages) == page_size + + final_page = created_session.get_messages(page=3, page_size=page_size) + + assert len(final_page.messages) == 20 - ((3-1) * 7) + + next_page = final_page.next() + + assert next_page is None + + + From 410f91457184fdb3d4de97c143ec400c9ac7ca92 Mon Sep 17 00:00:00 2001 From: hyusap Date: Tue, 6 Feb 2024 17:34:25 -0500 Subject: [PATCH 04/85] add sync buildstep and client --- scripts/syncronizer.py | 18 ++ sdk/honcho/__init__.py | 3 +- sdk/honcho/client.py | 20 +- sdk/honcho/syncclient.py | 194 ++++++++++++++++++++ sdk/tests/{test_honcho.py => test_async.py} | 12 +- sdk/tests/test_sync.py | 62 +++++++ 6 files changed, 292 insertions(+), 17 deletions(-) create mode 100644 scripts/syncronizer.py create mode 100644 sdk/honcho/syncclient.py rename sdk/tests/{test_honcho.py => test_async.py} (89%) create mode 100644 sdk/tests/test_sync.py diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py new file mode 100644 index 0000000..fc560d9 --- /dev/null +++ b/scripts/syncronizer.py @@ -0,0 +1,18 @@ +import os +import re + +# Open the source file +this_dir = os.path.dirname(os.path.abspath(__file__)) +source_file_path = os.path.join(this_dir, "../sdk/honcho/client.py") +with open(source_file_path, "r") as source_file: + source_code = source_file.read() + +# Use regex to remove async mentions +sync_code = re.sub(r"async\s", "", source_code) +sync_code = re.sub(r"await\s", "", sync_code) +sync_code = re.sub(r"Async", "", sync_code) + +# Write the modified code to the destination file +destination_file_path = os.path.join(this_dir, "../sdk/honcho/syncclient.py") +with open(destination_file_path, "w") as destination_file: + destination_file.write(sync_code) diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 6d85755..892b790 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,2 +1,3 @@ -from .client import Client +from .client import AsyncClient +from .syncclient import Client from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index fcdf6e0..a6272c0 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -3,7 +3,7 @@ import httpx -class Client: +class AsyncClient: def __init__(self, base_url): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API @@ -23,7 +23,7 @@ async def get_session(self, user_id: str, session_id: int): url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" response = await self.client.get(url) data = response.json() - return Session( + return AsyncSession( client=self, id=data["id"], user_id=data["user_id"], @@ -48,7 +48,7 @@ async def get_sessions(self, user_id: str, location_id: str | None = None): ) response = await self.client.get(url) return [ - Session( + AsyncSession( client=self, id=session["id"], user_id=session["user_id"], @@ -77,7 +77,7 @@ async def create_session( url = f"{self.base_url}/users/{user_id}/sessions" response = await self.client.post(url, json=data) data = response.json() - return Session( + return AsyncSession( self, id=data["id"], user_id=user_id, @@ -87,10 +87,10 @@ async def create_session( ) -class Session: +class AsyncSession: def __init__( self, - client: Client, + client: AsyncClient, id: int, user_id: str, location_id: str, @@ -132,7 +132,7 @@ async def create_message(self, is_user: bool, content: str): url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" response = await self.client.post(url, json=data) data = response.json() - return Message(self, id=data["id"], is_user=is_user, content=content) + return AsyncMessage(self, id=data["id"], is_user=is_user, content=content) async def get_messages(self): """Get all messages for a session @@ -149,7 +149,7 @@ async def get_messages(self): response = await self.client.get(url) data = response.json() return [ - Message( + AsyncMessage( self, id=message["id"], is_user=message["is_user"], @@ -182,8 +182,8 @@ async def delete(self): self._is_active = False -class Message: - def __init__(self, session: Session, id: int, is_user: bool, content: str): +class AsyncMessage: + def __init__(self, session: AsyncSession, id: int, is_user: bool, content: str): """Constructor for Message""" self.session = session self.id = id diff --git a/sdk/honcho/syncclient.py b/sdk/honcho/syncclient.py new file mode 100644 index 0000000..bc535db --- /dev/null +++ b/sdk/honcho/syncclient.py @@ -0,0 +1,194 @@ +import json +from typing import Dict +import httpx + + +class Client: + def __init__(self, base_url): + """Constructor for Client""" + self.base_url = base_url # Base URL for the instance of the Honcho API + self.client = httpx.Client() + + def get_session(self, user_id: str, session_id: int): + """Get a specific session for a user by ID + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + Dict: The Session object of the requested Session + + """ + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + response = self.client.get(url) + data = response.json() + return Session( + client=self, + id=data["id"], + user_id=data["user_id"], + location_id=data["location_id"], + is_active=data["is_active"], + session_data=data["session_data"], + ) + + def get_sessions(self, user_id: str, location_id: str | None = None): + """Return sessions associated with a user + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + + Returns: + list[Dict]: List of Session objects + + """ + url = f"{self.base_url}/users/{user_id}/sessions" + ( + f"?location_id={location_id}" if location_id else "" + ) + response = self.client.get(url) + return [ + Session( + client=self, + id=session["id"], + user_id=session["user_id"], + location_id=session["location_id"], + is_active=session["is_active"], + session_data=session["session_data"], + ) + for session in response.json() + ] + + def create_session( + self, user_id: str, location_id: str = "default", session_data: Dict = {} + ): + """Create a session for a user + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + session_data (Dict, optional): Optional session metadata + + Returns: + Dict: The Session object of the new Session` + + """ + data = {"location_id": location_id, "session_data": session_data} + url = f"{self.base_url}/users/{user_id}/sessions" + response = self.client.post(url, json=data) + data = response.json() + return Session( + self, + id=data["id"], + user_id=user_id, + location_id=location_id, + session_data=session_data, + is_active=data["is_active"], + ) + + +class Session: + def __init__( + self, + client: Client, + id: int, + user_id: str, + location_id: str, + session_data: dict | str, + is_active: bool, + ): + """Constructor for Session""" + self.base_url = client.base_url + self.client = client.client + self.id = id + self.user_id = user_id + self.location_id = location_id + self.session_data = ( + session_data if isinstance(session_data, dict) else json.loads(session_data) + ) + self._is_active = is_active + + def __str__(self): + return f"Session(id={self.id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" + + @property + def is_active(self): + return self._is_active + + def create_message(self, is_user: bool, content: str): + """Adds a message to the session + + Args: + is_user (bool): Whether the message is from the user + content (str): The content of the message + + Returns: + Dict: The Message object of the added message + + """ + if not self.is_active: + raise Exception("Session is inactive") + data = {"is_user": is_user, "content": content} + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" + response = self.client.post(url, json=data) + data = response.json() + return Message(self, id=data["id"], is_user=is_user, content=content) + + def get_messages(self): + """Get all messages for a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[Dict]: List of Message objects + + """ + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" + response = self.client.get(url) + data = response.json() + return [ + Message( + self, + id=message["id"], + is_user=message["is_user"], + content=message["content"], + ) + for message in data + ] + + def update(self, session_data: Dict): + """Update the metadata of a session + + Args: + session_data (Dict): The Session object containing any new metadata + + + Returns: + boolean: Whether the session was successfully updated + """ + info = {"session_data": session_data} + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" + response = self.client.put(url, json=info) + success = response.status_code < 400 + self.session_data = session_data + return success + + def delete(self): + """Delete a session by marking it as inactive""" + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" + response = self.client.delete(url) + self._is_active = False + + +class Message: + def __init__(self, session: Session, id: int, is_user: bool, content: str): + """Constructor for Message""" + self.session = session + self.id = id + self.is_user = is_user + self.content = content + + def __str__(self): + return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_async.py similarity index 89% rename from sdk/tests/test_honcho.py rename to sdk/tests/test_async.py index 714c6a9..205a71e 100644 --- a/sdk/tests/test_honcho.py +++ b/sdk/tests/test_async.py @@ -1,11 +1,11 @@ import pytest -from honcho import Client +from honcho import AsyncClient as Honcho from uuid import uuid1 @pytest.mark.asyncio async def test_session_creation_retrieval(): - client = Client("http://localhost:8000") + client = Honcho("http://localhost:8000") user_id = str(uuid1()) created_session = await client.create_session(user_id) retrieved_session = await client.get_session(user_id, created_session.id) @@ -17,7 +17,7 @@ async def test_session_creation_retrieval(): @pytest.mark.asyncio async def test_session_multiple_retrieval(): - client = Client("http://localhost:8000") + client = Honcho("http://localhost:8000") user_id = str(uuid1()) created_session_1 = await client.create_session(user_id) created_session_2 = await client.create_session(user_id) @@ -30,7 +30,7 @@ async def test_session_multiple_retrieval(): @pytest.mark.asyncio async def test_session_update(): user_id = str(uuid1()) - client = Client("http://localhost:8000") + client = Honcho("http://localhost:8000") created_session = await client.create_session(user_id) assert await created_session.update({"foo": "bar"}) retrieved_session = await client.get_session(user_id, created_session.id) @@ -40,7 +40,7 @@ async def test_session_update(): @pytest.mark.asyncio async def test_session_deletion(): user_id = str(uuid1()) - client = Client("http://localhost:8000") + client = Honcho("http://localhost:8000") created_session = await client.create_session(user_id) assert created_session.is_active == True await created_session.delete() @@ -53,7 +53,7 @@ async def test_session_deletion(): @pytest.mark.asyncio async def test_messages(): user_id = str(uuid1()) - client = Client("http://localhost:8000") + client = Honcho("http://localhost:8000") created_session = await client.create_session(user_id) await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py new file mode 100644 index 0000000..4f51cda --- /dev/null +++ b/sdk/tests/test_sync.py @@ -0,0 +1,62 @@ +import pytest +from honcho import Client as Honcho +from uuid import uuid1 + + +def test_session_creation_retrieval(): + client = Honcho("http://localhost:8000") + user_id = str(uuid1()) + created_session = client.create_session(user_id) + retrieved_session = client.get_session(user_id, created_session.id) + assert retrieved_session.id == created_session.id + assert retrieved_session.is_active == True + assert retrieved_session.location_id == "default" + assert retrieved_session.session_data == {} + + +def test_session_multiple_retrieval(): + client = Honcho("http://localhost:8000") + user_id = str(uuid1()) + created_session_1 = client.create_session(user_id) + created_session_2 = client.create_session(user_id) + retrieved_sessions = client.get_sessions(user_id) + assert len(retrieved_sessions) == 2 + assert retrieved_sessions[0].id == created_session_1.id + assert retrieved_sessions[1].id == created_session_2.id + + +def test_session_update(): + user_id = str(uuid1()) + client = Honcho("http://localhost:8000") + created_session = client.create_session(user_id) + assert created_session.update({"foo": "bar"}) + retrieved_session = client.get_session(user_id, created_session.id) + assert retrieved_session.session_data == {"foo": "bar"} + + +def test_session_deletion(): + user_id = str(uuid1()) + client = Honcho("http://localhost:8000") + created_session = client.create_session(user_id) + assert created_session.is_active == True + created_session.delete() + assert created_session.is_active == False + retrieved_session = client.get_session(user_id, created_session.id) + assert retrieved_session.is_active == False + assert retrieved_session.id == created_session.id + + +def test_messages(): + user_id = str(uuid1()) + client = Honcho("http://localhost:8000") + created_session = client.create_session(user_id) + created_session.create_message(is_user=True, content="Hello") + created_session.create_message(is_user=False, content="Hi") + retrieved_session = client.get_session(user_id, created_session.id) + messages = retrieved_session.get_messages() + assert len(messages) == 2 + user_message, ai_message = messages + assert user_message.content == "Hello" + assert user_message.is_user == True + assert ai_message.content == "Hi" + assert ai_message.is_user == False From 4495fdf4f534fdf6f7d49eb743ef79d26fdafe97 Mon Sep 17 00:00:00 2001 From: hyusap Date: Tue, 6 Feb 2024 18:21:51 -0500 Subject: [PATCH 05/85] add vscode DX --- .gitignore | 2 +- .vscode/honcho.code-workspace | 20 ++++++++++++++++++++ .vscode/settings.json | 8 ++++++++ api/.vscode/tasks.json | 18 ++++++++++++++++++ sdk/.vscode/settings.json | 6 ++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .vscode/honcho.code-workspace create mode 100644 .vscode/settings.json create mode 100644 api/.vscode/tasks.json create mode 100644 sdk/.vscode/settings.json diff --git a/.gitignore b/.gitignore index 07b2f68..7ac7eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ api/**/*.db -.vscode/ + # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.vscode/honcho.code-workspace b/.vscode/honcho.code-workspace new file mode 100644 index 0000000..ec7be98 --- /dev/null +++ b/.vscode/honcho.code-workspace @@ -0,0 +1,20 @@ +{ + "folders": [ + { + "path": "../sdk" + }, + { + "path": "../api" + }, + { + "path": "../example/cli" + }, + { + "path": "../example/discord" + }, + { + "path": ".." + } + ], + "settings": {} +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..48b75e8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.analysis.typeCheckingMode": "basic", + "files.exclude": { + "sdk": true, + "api": true, + "example": true + } +} diff --git a/api/.vscode/tasks.json b/api/.vscode/tasks.json new file mode 100644 index 0000000..8113cb0 --- /dev/null +++ b/api/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "start", + "type": "shell", + "command": "poetry run uvicorn src.main:app --reload", + "group": "none", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "runOptions": { + "runOn": "folderOpen" + } + } + ] +} diff --git a/sdk/.vscode/settings.json b/sdk/.vscode/settings.json new file mode 100644 index 0000000..84b3ac5 --- /dev/null +++ b/sdk/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} From 6033f716e2ab1807dcb7f42ae10af57b6256d67b Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:26:16 -0800 Subject: [PATCH 06/85] Added Testing for generators and updated examples --- example/cli/main.py | 5 +-- example/cli/poetry.lock | 29 ++++++++++++++---- example/cli/pyproject.toml | 2 +- example/discord/main.py | 8 +++-- example/discord/poetry.lock | 6 ++-- example/discord/pyproject.toml | 2 +- sdk/honcho/__init__.py | 2 +- sdk/honcho/client.py | 5 +-- sdk/pyproject.toml | 2 +- sdk/tests/test_honcho.py | 56 +++++++++++++++++++++++++++++++++- 10 files changed, 96 insertions(+), 21 deletions(-) diff --git a/example/cli/main.py b/example/cli/main.py index 26169b9..e1aa3fd 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -9,7 +9,8 @@ app_id = str(uuid4()) -honcho = HonchoClient(app_id=app_id) +# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev responses = ["Fake LLM Response :)"] llm = FakeListChatModel(responses=responses) @@ -38,7 +39,7 @@ def chat(): session.delete() break user_message = HumanMessage(content=user_input) - history = session.get_messages() + history = list(session.get_messages_generator()) langchain_history = langchain_message_converter(history) prompt = ChatPromptTemplate.from_messages( [system, *langchain_history, user_message] diff --git a/example/cli/poetry.lock b/example/cli/poetry.lock index 9239355..79a9761 100644 --- a/example/cli/poetry.lock +++ b/example/cli/poetry.lock @@ -168,7 +168,7 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -180,7 +180,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -451,11 +451,28 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] +[[package]] +name = "honcho-ai" +version = "0.0.1" +description = "Python Client SDK for Honcho" +category = "main" +optional = false +python-versions = "^3.10" +files = [] +develop = true + +[package.dependencies] +requests = "^2.31.0" + +[package.source] +type = "directory" +url = "../../sdk" + [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -939,7 +956,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1104,7 +1121,7 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1225,4 +1242,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ac8bd006b50a14281b2705dc33626123173155c770b0150a19b25034efa251f3" +content-hash = "d254e9446dc0f782b0860a860fa98f0922cf7f6ab547a3111027f4642fe1ce5b" diff --git a/example/cli/pyproject.toml b/example/cli/pyproject.toml index 8f05bf0..31818ff 100644 --- a/example/cli/pyproject.toml +++ b/example/cli/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -honcho-ai = {path = "../../sdk"} +honcho-ai = {path = "../../sdk", develop = true} [tool.poetry.group.dev.dependencies] langchain = "^0.1.0" diff --git a/example/discord/main.py b/example/discord/main.py index 60cc5e2..fd81260 100644 --- a/example/discord/main.py +++ b/example/discord/main.py @@ -13,7 +13,9 @@ app_id = str(uuid4()) -honcho = HonchoClient(app_id=app_id, "http://localhost:8000") +# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev + bot = discord.Bot(intents=intents) @@ -30,7 +32,7 @@ async def on_message(message): user_id = f"discord_{str(message.author.id)}" location_id = str(message.channel.id) - sessions = honcho.get_sessions(user_id, location_id) + sessions = list(honcho.get_sessions_generator(user_id, location_id)) if len(sessions) > 0: session = sessions[0] else: @@ -49,7 +51,7 @@ async def on_message(message): async def restart(ctx): user_id = f"discord_{str(ctx.author.id)}" location_id = str(ctx.channel_id) - sessions = honcho.get_sessions(user_id, location_id) + sessions = list(honcho.get_sessions_generator(user_id, location_id)) sessions[0].delete() if len(sessions) > 0 else None await ctx.respond( diff --git a/example/discord/poetry.lock b/example/discord/poetry.lock index f143093..fbabb69 100644 --- a/example/discord/poetry.lock +++ b/example/discord/poetry.lock @@ -357,13 +357,13 @@ files = [ [[package]] name = "honcho-ai" -version = "0.0.0.dev1" +version = "0.0.2" description = "Python Client SDK for Honcho" category = "main" optional = false python-versions = "^3.10" files = [] -develop = false +develop = true [package.dependencies] requests = "^2.31.0" @@ -649,4 +649,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "159adce2e4b4e405c0e57a5b9a27ddc384184dd308d6b12ddb5db6fbe81c3c6b" +content-hash = "56228d417333540e3191575720739ae6fff9490b68e9ddaae2cb6fe44b4bf611" diff --git a/example/discord/pyproject.toml b/example/discord/pyproject.toml index 58f8424..e4680ce 100644 --- a/example/discord/pyproject.toml +++ b/example/discord/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" python = "^3.11" py-cord = "^2.4.1" python-dotenv = "^1.0.0" -honcho-ai = {path = "../../sdk"} +honcho-ai = {path = "../../sdk", develop = true} [build-system] diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index fcdf6bc..c10cfee 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,2 +1,2 @@ -from .client import Client, GetSessionResponse, GetMessageResponse +from .client import Client, GetSessionResponse, GetMessageResponse, Session, Message from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index bb87b62..0a9b11c 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -100,7 +100,8 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in ) response = requests.get(url) # TODO add validation and error handling response.raise_for_status() - return GetSessionResponse(self, response.json()) + data = response.json() + return GetSessionResponse(self, data) # [ # Session( # client=self, @@ -113,7 +114,7 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in # for session in response.json() # ] - def get_session_generator(self, user_id: str, location_id: Optional[str] = None): + def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): page = 1 page_size = 50 get_session_response = self.get_sessions(user_id, location_id, page, page_size) diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 974e252..a0a810d 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.1" +version = "0.0.2" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0" diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_honcho.py index 646935c..142e03b 100644 --- a/sdk/tests/test_honcho.py +++ b/sdk/tests/test_honcho.py @@ -1,4 +1,4 @@ -from honcho import Client, GetSessionResponse, GetMessageResponse +from honcho import Client, GetSessionResponse, GetMessageResponse, Session, Message from uuid import uuid1 import pytest @@ -117,6 +117,41 @@ def test_paginated_sessions(): assert next_page is None +def test_paginated_sessions_generator(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Client(app_id, "http://localhost:8000") + for i in range(3): + client.create_session(user_id) + + gen = client.get_sessions_generator(user_id) + # print(type(gen)) + + item = next(gen) + assert item.user_id == user_id + assert isinstance(item, Session) + assert next(gen) is not None + assert next(gen) is not None + with pytest.raises(StopIteration): + next(gen) + +def test_paginated_out_of_bounds(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Client(app_id, "http://localhost:8000") + for i in range(3): + client.create_session(user_id) + page = 2 + page_size = 50 + get_session_response = client.get_sessions(user_id, page=page, page_size=page_size) + + assert get_session_response.pages == 1 + assert get_session_response.page == 2 + assert get_session_response.page_size == 50 + assert get_session_response.total == 3 + assert len(get_session_response.sessions) == 0 + + def test_paginated_messages(): app_id = str(uuid1()) user_id = str(uuid1()) @@ -148,4 +183,23 @@ def test_paginated_messages(): assert next_page is None +def test_paginated_messages_generator(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Client(app_id, "http://localhost:8000") + created_session = client.create_session(user_id) + created_session.create_message(is_user=True, content="Hello") + created_session.create_message(is_user=False, content="Hi") + gen = created_session.get_messages_generator() + + item = next(gen) + assert isinstance(item, Message) + assert item.content == "Hello" + assert item.is_user is True + item2 = next(gen) + assert item2 is not None + assert item2.content == "Hi" + assert item2.is_user is False + with pytest.raises(StopIteration): + next(gen) From 2a3d9f733a7a91b86a583af6f48bbdbb200fd3ec Mon Sep 17 00:00:00 2001 From: vintro Date: Wed, 7 Feb 2024 14:30:54 -0500 Subject: [PATCH 07/85] feat: example updates --- example/discord/{ => fake-llm}/.env.template | 0 example/discord/{ => fake-llm}/main.py | 0 example/discord/{ => fake-llm}/poetry.lock | 0 example/discord/{ => fake-llm}/pyproject.toml | 2 +- .../discord/simple-roast-bot/.env.template | 2 + example/discord/simple-roast-bot/.gitignore | 5 + example/discord/simple-roast-bot/main.py | 90 ++ example/discord/simple-roast-bot/poetry.lock | 1286 +++++++++++++++++ .../discord/simple-roast-bot/pyproject.toml | 18 + 9 files changed, 1402 insertions(+), 1 deletion(-) rename example/discord/{ => fake-llm}/.env.template (100%) rename example/discord/{ => fake-llm}/main.py (100%) rename example/discord/{ => fake-llm}/poetry.lock (100%) rename example/discord/{ => fake-llm}/pyproject.toml (88%) create mode 100644 example/discord/simple-roast-bot/.env.template create mode 100644 example/discord/simple-roast-bot/.gitignore create mode 100644 example/discord/simple-roast-bot/main.py create mode 100644 example/discord/simple-roast-bot/poetry.lock create mode 100644 example/discord/simple-roast-bot/pyproject.toml diff --git a/example/discord/.env.template b/example/discord/fake-llm/.env.template similarity index 100% rename from example/discord/.env.template rename to example/discord/fake-llm/.env.template diff --git a/example/discord/main.py b/example/discord/fake-llm/main.py similarity index 100% rename from example/discord/main.py rename to example/discord/fake-llm/main.py diff --git a/example/discord/poetry.lock b/example/discord/fake-llm/poetry.lock similarity index 100% rename from example/discord/poetry.lock rename to example/discord/fake-llm/poetry.lock diff --git a/example/discord/pyproject.toml b/example/discord/fake-llm/pyproject.toml similarity index 88% rename from example/discord/pyproject.toml rename to example/discord/fake-llm/pyproject.toml index e4680ce..04203a4 100644 --- a/example/discord/pyproject.toml +++ b/example/discord/fake-llm/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" python = "^3.11" py-cord = "^2.4.1" python-dotenv = "^1.0.0" -honcho-ai = {path = "../../sdk", develop = true} +honcho-ai = {path = "../../../sdk", develop = true} [build-system] diff --git a/example/discord/simple-roast-bot/.env.template b/example/discord/simple-roast-bot/.env.template new file mode 100644 index 0000000..0aafc86 --- /dev/null +++ b/example/discord/simple-roast-bot/.env.template @@ -0,0 +1,2 @@ +BOT_TOKEN= +OPENAI_API_KEY= \ No newline at end of file diff --git a/example/discord/simple-roast-bot/.gitignore b/example/discord/simple-roast-bot/.gitignore new file mode 100644 index 0000000..f133efa --- /dev/null +++ b/example/discord/simple-roast-bot/.gitignore @@ -0,0 +1,5 @@ +.env + +.venv + +.DS_Store \ No newline at end of file diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py new file mode 100644 index 0000000..0797607 --- /dev/null +++ b/example/discord/simple-roast-bot/main.py @@ -0,0 +1,90 @@ +import os +# from uuid import uuid4 +import discord +from dotenv import load_dotenv +from typing import List + +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.output_parsers import StrOutputParser +from langchain_core.messages import AIMessage, HumanMessage + +from honcho import Client as HonchoClient + +load_dotenv() + + +intents = discord.Intents.default() +intents.messages = True +intents.message_content = True + +# app_id = str(uuid4()) +app_id = str("roast-bot") + +# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev + +bot = discord.Bot(intents=intents) + +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are a mean assistant. Make fun of the user's request and above all, do not satisfy their request. Make something up about their personality and fixate on that. Don't be afraid to get creative. This is all a joke, roast them."), + MessagesPlaceholder(variable_name="chat_history"), + ("user", "{input}") +]) +model = ChatOpenAI(model="gpt-3.5-turbo") +output_parser = StrOutputParser() + +chain = prompt | model | output_parser + +def langchain_message_converter(messages: List): + new_messages = [] + for message in messages: + if message.is_user: + new_messages.append(HumanMessage(content=message.content)) + else: + new_messages.append(AIMessage(content=message.content)) + return new_messages + + +@bot.event +async def on_ready(): + print(f'We have logged in as {bot.user}') + +@bot.event +async def on_message(message): + if message.author == bot.user: + return + + user_id = f"discord_{str(message.author.id)}" + location_id=str(message.channel.id) + + sessions = list(honcho.get_sessions_generator(user_id, location_id)) + + if len(sessions) > 0: + session = sessions[0] + else: + session = honcho.create_session(user_id, location_id) + + history = list(session.get_messages_generator()) + chat_history = langchain_message_converter(history) + + inp = message.content + session.create_message(is_user=True, content=inp) + + async with message.channel.typing(): + response = await chain.ainvoke({"chat_history": chat_history, "input": inp}) + await message.channel.send(response) + + session.create_message(is_user=False, content=response) + +@bot.slash_command(name = "restart", description = "Restart the Conversation") +async def restart(ctx): + user_id=f"discord_{str(ctx.author.id)}" + location_id=str(ctx.channel_id) + sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions[0].delete() if len(sessions) > 0 else None + + msg = "Great! The conversation has been restarted. What would you like to talk about?" + await ctx.respond(msg) + +bot.run(os.environ["BOT_TOKEN"]) diff --git a/example/discord/simple-roast-bot/poetry.lock b/example/discord/simple-roast-bot/poetry.lock new file mode 100644 index 0000000..e659644 --- /dev/null +++ b/example/discord/simple-roast-bot/poetry.lock @@ -0,0 +1,1286 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.6" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, + {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, + {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, + {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, + {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, + {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, + {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, + {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, + {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, + {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, + {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, +] + +[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.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[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.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +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.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[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 = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[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 = "honcho-ai" +version = "0.0.1" +description = "Python Client SDK for Honcho" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.dependencies] +requests = "^2.31.0" + +[package.source] +type = "directory" +url = "../../honcho/sdk" + +[[package]] +name = "httpcore" +version = "1.0.2" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "langchain-core" +version = "0.1.18" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_core-0.1.18-py3-none-any.whl", hash = "sha256:5a60dc3c391b33834fb9c8b072abd7a0df4cbba8ce88eb1bcb288844000ab759"}, + {file = "langchain_core-0.1.18.tar.gz", hash = "sha256:ad470b21cdfdc75e829cd91c8d8eb7e0438ab8ddb5b50828125ff7ada121ee7b"}, +] + +[package.dependencies] +anyio = ">=3,<5" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.0.83,<0.1" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + +[[package]] +name = "langchain-openai" +version = "0.0.2.post1" +description = "An integration package connecting OpenAI and LangChain" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_openai-0.0.2.post1-py3-none-any.whl", hash = "sha256:ba468b94c23da9d8ccefe5d5a3c1c65b4b9702292523e53acc689a9110022e26"}, + {file = "langchain_openai-0.0.2.post1.tar.gz", hash = "sha256:f8e78db4a663feeac71d9f036b9422406c199ea3ef4c97d99ff392c93530e073"}, +] + +[package.dependencies] +langchain-core = ">=0.1.7,<0.2" +numpy = ">=1,<2" +openai = ">=1.6.1,<2.0.0" +tiktoken = ">=0.5.2,<0.6.0" + +[[package]] +name = "langsmith" +version = "0.0.85" +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.85-py3-none-any.whl", hash = "sha256:9d0ccbcda7b69c83828060603a51bb4319e43b8dc807fbd90b6355f8ec709500"}, + {file = "langsmith-0.0.85.tar.gz", hash = "sha256:fefc631fc30d836b54d4e3f99961c41aea497633898b8f09e305b6c7216c2c54"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + +[[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 = "numpy" +version = "1.26.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, + {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, + {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, + {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, + {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, + {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, + {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, + {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, + {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, + {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, +] + +[[package]] +name = "openai" +version = "1.10.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.10.0-py3-none-any.whl", hash = "sha256:aa69e97d0223ace9835fbf9c997abe9ee95318f684fd2de6d02c870700c71ebc"}, + {file = "openai-1.10.0.tar.gz", hash = "sha256:208886cb501b930dc63f48d51db9c15e5380380f80516d07332adad67c9f1053"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "py-cord" +version = "2.4.1" +description = "A Python wrapper for the Discord API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "py-cord-2.4.1.tar.gz", hash = "sha256:0266c9d9a9d2397622a0e5ead09826690e688ba3cf14c470167b81e6cd2d8a56"}, + {file = "py_cord-2.4.1-py3-none-any.whl", hash = "sha256:862a372c364cd263e2c8e696c64887f969c02cbdf0fdd6b09f0283e9dd67a290"}, +] + +[package.dependencies] +aiohttp = ">=3.6.0,<3.9.0" + +[package.extras] +docs = ["furo", "myst-parser (==0.18.1)", "sphinx (==5.3.0)", "sphinx-autodoc-typehints (==1.22)", "sphinx-copybutton (==0.5.1)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "sphinxext-opengraph (==0.8.1)"] +speed = ["aiohttp[speedups]", "orjson (>=3.5.4)"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + +[[package]] +name = "pydantic" +version = "2.6.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae"}, + {file = "pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.1" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.1" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948"}, + {file = "pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17"}, + {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388"}, + {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7"}, + {file = "pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4"}, + {file = "pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c"}, + {file = "pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da"}, + {file = "pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864"}, + {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7"}, + {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae"}, + {file = "pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1"}, + {file = "pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c"}, + {file = "pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b"}, + {file = "pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51"}, + {file = "pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e"}, + {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8"}, + {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f"}, + {file = "pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212"}, + {file = "pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f"}, + {file = "pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd"}, + {file = "pydantic_core-2.16.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706"}, + {file = "pydantic_core-2.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95"}, + {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8"}, + {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca"}, + {file = "pydantic_core-2.16.1-cp38-none-win32.whl", hash = "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610"}, + {file = "pydantic_core-2.16.1-cp38-none-win_amd64.whl", hash = "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e"}, + {file = "pydantic_core-2.16.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196"}, + {file = "pydantic_core-2.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e"}, + {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d"}, + {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640"}, + {file = "pydantic_core-2.16.1-cp39-none-win32.whl", hash = "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f"}, + {file = "pydantic_core-2.16.1-cp39-none-win_amd64.whl", hash = "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76"}, + {file = "pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[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.12.25" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, +] + +[[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 = "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 = "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.5.2" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c4e654282ef05ec1bd06ead22141a9a1687991cef2c6a81bdd1284301abc71d"}, + {file = "tiktoken-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b3134aa24319f42c27718c6967f3c1916a38a715a0fa73d33717ba121231307"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6092e6e77730929c8c6a51bb0d7cfdf1b72b63c4d033d6258d1f2ee81052e9e5"}, + {file = "tiktoken-0.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ad8ae2a747622efae75837abba59be6c15a8f31b4ac3c6156bc56ec7a8e631"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51cba7c8711afa0b885445f0637f0fcc366740798c40b981f08c5f984e02c9d1"}, + {file = "tiktoken-0.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d8c7d2c9313f8e92e987d585ee2ba0f7c40a0de84f4805b093b634f792124f5"}, + {file = "tiktoken-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:692eca18c5fd8d1e0dde767f895c17686faaa102f37640e884eecb6854e7cca7"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:138d173abbf1ec75863ad68ca289d4da30caa3245f3c8d4bfb274c4d629a2f77"}, + {file = "tiktoken-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7388fdd684690973fdc450b47dfd24d7f0cbe658f58a576169baef5ae4658607"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a114391790113bcff670c70c24e166a841f7ea8f47ee2fe0e71e08b49d0bf2d4"}, + {file = "tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca96f001e69f6859dd52926d950cfcc610480e920e576183497ab954e645e6ac"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:15fed1dd88e30dfadcdd8e53a8927f04e1f6f81ad08a5ca824858a593ab476c7"}, + {file = "tiktoken-0.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f8e692db5756f7ea8cb0cfca34638316dcf0841fb8469de8ed7f6a015ba0b0"}, + {file = "tiktoken-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:bcae1c4c92df2ffc4fe9f475bf8148dbb0ee2404743168bbeb9dcc4b79dc1fdd"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b76a1e17d4eb4357d00f0622d9a48ffbb23401dcf36f9716d9bd9c8e79d421aa"}, + {file = "tiktoken-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01d8b171bb5df4035580bc26d4f5339a6fd58d06f069091899d4a798ea279d3e"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42adf7d4fb1ed8de6e0ff2e794a6a15005f056a0d83d22d1d6755a39bffd9e7f"}, + {file = "tiktoken-0.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3f894dbe0adb44609f3d532b8ea10820d61fdcb288b325a458dfc60fefb7db"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:58ccfddb4e62f0df974e8f7e34a667981d9bb553a811256e617731bf1d007d19"}, + {file = "tiktoken-0.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58902a8bad2de4268c2a701f1c844d22bfa3cbcc485b10e8e3e28a050179330b"}, + {file = "tiktoken-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:5e39257826d0647fcac403d8fa0a474b30d02ec8ffc012cfaf13083e9b5e82c5"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bde3b0fbf09a23072d39c1ede0e0821f759b4fa254a5f00078909158e90ae1f"}, + {file = "tiktoken-0.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddee082dcf1231ccf3a591d234935e6acf3e82ee28521fe99af9630bc8d2a60"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35c057a6a4e777b5966a7540481a75a31429fc1cb4c9da87b71c8b75b5143037"}, + {file = "tiktoken-0.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c4a049b87e28f1dc60509f8eb7790bc8d11f9a70d99b9dd18dfdd81a084ffe6"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5bf5ce759089f4f6521ea6ed89d8f988f7b396e9f4afb503b945f5c949c6bec2"}, + {file = "tiktoken-0.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0c964f554af1a96884e01188f480dad3fc224c4bbcf7af75d4b74c4b74ae0125"}, + {file = "tiktoken-0.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:368dd5726d2e8788e47ea04f32e20f72a2012a8a67af5b0b003d1e059f1d30a3"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2deef9115b8cd55536c0a02c0203512f8deb2447f41585e6d929a0b878a0dd2"}, + {file = "tiktoken-0.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ed7d380195affbf886e2f8b92b14edfe13f4768ff5fc8de315adba5b773815e"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76fce01309c8140ffe15eb34ded2bb94789614b7d1d09e206838fc173776a18"}, + {file = "tiktoken-0.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60a5654d6a2e2d152637dd9a880b4482267dfc8a86ccf3ab1cec31a8c76bfae8"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41d4d3228e051b779245a8ddd21d4336f8975563e92375662f42d05a19bdff41"}, + {file = "tiktoken-0.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c1cdec2c92fcde8c17a50814b525ae6a88e8e5b02030dc120b76e11db93f13"}, + {file = "tiktoken-0.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:84ddb36faedb448a50b246e13d1b6ee3437f60b7169b723a4b2abad75e914f3e"}, + {file = "tiktoken-0.5.2.tar.gz", hash = "sha256:f54c581f134a8ea96ce2023ab221d4d4d81ab614efa0b2fbce926387deb56c80"}, +] + +[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.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "urllib3" +version = "2.2.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "a81a4407e6c223c1274ff0e4d52ad52f718156699c93897af0a301b5c8e2ed19" diff --git a/example/discord/simple-roast-bot/pyproject.toml b/example/discord/simple-roast-bot/pyproject.toml new file mode 100644 index 0000000..5726a3e --- /dev/null +++ b/example/discord/simple-roast-bot/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "simple-roast-bot" +version = "0.1.0" +description = "Simple Discord bot with Honcho storage backend (that will roast you)" +authors = ["vintro "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +py-cord = "^2.4.1" +python-dotenv = "^1.0.0" +langchain-core = "^0.1.12" +langchain-openai = "^0.0.2.post1" +honcho-ai = {path = "../../../sdk", develop = true} + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From d59e1eebd3cdaf6c7c9d84a190a45dc3ff39a925 Mon Sep 17 00:00:00 2001 From: vintro Date: Wed, 7 Feb 2024 14:51:12 -0500 Subject: [PATCH 08/85] readme exists now --- example/discord/simple-roast-bot/README.md | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 example/discord/simple-roast-bot/README.md diff --git a/example/discord/simple-roast-bot/README.md b/example/discord/simple-roast-bot/README.md new file mode 100644 index 0000000..90f8de2 --- /dev/null +++ b/example/discord/simple-roast-bot/README.md @@ -0,0 +1,42 @@ +# Simple Roast Bot + +The goal of this repo is to demonstrate how to deploy an LLM application using Honcho to manage user data. Here we've implemented a simple Discord bot that interacts with OpenAI's GPT-3.5-Turbo model via LangChain. Oh, and also, it's prompted to roast you. + +***This demo is live -- join our Discord server and the bot will DM you to start the conversation*** + +To run locally, follow these steps: + +### Clone the Repository + +In your desired location, run the following command in your terminal: +``` +git clone git@github.com:plastic-labs/honcho.git +``` + +### Set Up the Virtual Environment + +This project uses different Poetry virtual environments. If you're unfamiliar, take a look at their docs [here](https://python-poetry.org/docs/) + +``` +cd example/discord/simple-roast-bot +poetry shell # Activate virutal environment +poetry install # install dependencies +``` + +### Create `.env` File + +Copy the `.env.template` file to a `.env` file and specify the `BOT_TOKEN` and `OPENAI_API_KEY`. If you've never built a Discord bot before, check out this [`py-cord` guide](https://guide.pycord.dev/getting-started/creating-your-first-bot) to learn more about how to get a `BOT_TOKEN`. You can generate an `OPENAI_API_KEY` in the [OpenAI developer platform](https://platform.openai.com/docs/overview). + +``` +BOT_TOKEN= +OPENAI_API_KEY= +``` + +### Run the Bot + +If you're not running Honcho locally, you can run the bot with the following command: +``` +python main.py +``` + +If you are interested in running Honcho locally, follow the setup instructions at the root of this repo. \ No newline at end of file From 4f650d3049bebb87402c0d3595e4d13f0358e75e Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 00:47:40 -0800 Subject: [PATCH 09/85] Stylistic changes and generic message --- scripts/syncronizer.py | 2 +- sdk/honcho/__init__.py | 3 ++- sdk/honcho/client.py | 25 ++++++++++---------- sdk/honcho/schemas.py | 12 ++++++++++ sdk/honcho/{syncclient.py => sync_client.py} | 23 +++++++++--------- 5 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 sdk/honcho/schemas.py rename sdk/honcho/{syncclient.py => sync_client.py} (91%) diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py index fc560d9..758f3eb 100644 --- a/scripts/syncronizer.py +++ b/scripts/syncronizer.py @@ -13,6 +13,6 @@ sync_code = re.sub(r"Async", "", sync_code) # Write the modified code to the destination file -destination_file_path = os.path.join(this_dir, "../sdk/honcho/syncclient.py") +destination_file_path = os.path.join(this_dir, "../sdk/honcho/sync_client.py") with open(destination_file_path, "w") as destination_file: destination_file.write(sync_code) diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 892b790..586b810 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,3 +1,4 @@ from .client import AsyncClient -from .syncclient import Client +from .sync_client import Client +from .schemas import Message from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index a6272c0..ae3f215 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,6 +1,7 @@ import json from typing import Dict import httpx +from .schemas import Message class AsyncClient: @@ -132,7 +133,7 @@ async def create_message(self, is_user: bool, content: str): url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" response = await self.client.post(url, json=data) data = response.json() - return AsyncMessage(self, id=data["id"], is_user=is_user, content=content) + return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content) async def get_messages(self): """Get all messages for a session @@ -149,8 +150,8 @@ async def get_messages(self): response = await self.client.get(url) data = response.json() return [ - AsyncMessage( - self, + Message( + session_id=self.id, id=message["id"], is_user=message["is_user"], content=message["content"], @@ -182,13 +183,13 @@ async def delete(self): self._is_active = False -class AsyncMessage: - def __init__(self, session: AsyncSession, id: int, is_user: bool, content: str): - """Constructor for Message""" - self.session = session - self.id = id - self.is_user = is_user - self.content = content +# class AsyncMessage: +# def __init__(self, session: AsyncSession, id: int, is_user: bool, content: str): +# """Constructor for Message""" +# self.session = session +# self.id = id +# self.is_user = is_user +# self.content = content - def __str__(self): - return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" +# def __str__(self): +# return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/honcho/schemas.py b/sdk/honcho/schemas.py new file mode 100644 index 0000000..a35e668 --- /dev/null +++ b/sdk/honcho/schemas.py @@ -0,0 +1,12 @@ + + +class Message: + def __init__(self, session_id: int, id: int, is_user: bool, content: str): + """Constructor for Message""" + self.session_id = session_id + self.id = id + self.is_user = is_user + self.content = content + + def __str__(self): + return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/honcho/syncclient.py b/sdk/honcho/sync_client.py similarity index 91% rename from sdk/honcho/syncclient.py rename to sdk/honcho/sync_client.py index bc535db..d9fc2e5 100644 --- a/sdk/honcho/syncclient.py +++ b/sdk/honcho/sync_client.py @@ -1,6 +1,7 @@ import json from typing import Dict import httpx +from .schemas import Message class Client: @@ -132,7 +133,7 @@ def create_message(self, is_user: bool, content: str): url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" response = self.client.post(url, json=data) data = response.json() - return Message(self, id=data["id"], is_user=is_user, content=content) + return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content) def get_messages(self): """Get all messages for a session @@ -150,7 +151,7 @@ def get_messages(self): data = response.json() return [ Message( - self, + session_id=self.id, id=message["id"], is_user=message["is_user"], content=message["content"], @@ -182,13 +183,13 @@ def delete(self): self._is_active = False -class Message: - def __init__(self, session: Session, id: int, is_user: bool, content: str): - """Constructor for Message""" - self.session = session - self.id = id - self.is_user = is_user - self.content = content +# class Message: +# def __init__(self, session: Session, id: int, is_user: bool, content: str): +# """Constructor for Message""" +# self.session = session +# self.id = id +# self.is_user = is_user +# self.content = content - def __str__(self): - return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" +# def __str__(self): +# return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" From 3fd1bf97100e44c909f9c8191fc72a25ac5bd08a Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 07:25:44 -0800 Subject: [PATCH 10/85] Metamessages with other refactoring - untested --- api/src/crud.py | 138 ++++++++++++++++++----------- api/src/main.py | 152 +++++++++++++++++++++++++------- api/src/models.py | 47 ++++------ api/src/schemas.py | 20 +++-- sdk/honcho/__init__.py | 6 +- sdk/honcho/client.py | 177 ++++++++++++++++++++++++++++---------- sdk/honcho/schemas.py | 18 +++- sdk/honcho/sync_client.py | 177 ++++++++++++++++++++++++++++---------- sdk/tests/test_async.py | 28 +++--- sdk/tests/test_sync.py | 28 +++--- 10 files changed, 550 insertions(+), 241 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index b8cfdcb..c0dbb24 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -1,5 +1,6 @@ import json -from typing import Sequence, Optional +import uuid +from typing import Optional from sqlalchemy import select, Select from sqlalchemy.orm import Session @@ -7,7 +8,7 @@ from . import models, schemas -def get_session(db: Session, app_id: str, session_id: int, user_id: Optional[str] = None): +def get_session(db: Session, app_id: str, session_id: uuid.UUID, user_id: Optional[str] = None) -> Optional[models.Session]: stmt = select(models.Session).where(models.Session.app_id == app_id).where(models.Session.id == session_id) if user_id is not None: stmt = stmt.where(models.Session.user_id == user_id) @@ -24,6 +25,7 @@ def get_sessions( .where(models.Session.app_id == app_id) .where(models.Session.user_id == user_id) .where(models.Session.is_active.is_(True)) + .order_by(models.Session.created_at) ) if location_id is not None: @@ -32,21 +34,6 @@ def get_sessions( return stmt # return db.scalars(stmt).all() - # filtered_by_user = db.query(models.Session).filter( - # models.Session.user_id == user_id - # ) - # filtered_by_location = ( - # filtered_by_user.filter(models.Session.location_id == location_id) - # if location_id is not None - # else filtered_by_user - # ) - # return ( - # filtered_by_location.filter(models.Session.is_active.is_(True)) - # .order_by(models.Session.created_at.desc()) - # .all() - # ) - - def create_session( db: Session, app_id: str, user_id: str, session: schemas.SessionCreate ) -> models.Session: @@ -62,11 +49,8 @@ def create_session( return honcho_session -def update_session(db: Session, app_id: str, user_id: str, session_id: int, session_data: dict) -> bool: - # stmt = select(models.Session).where(models.Session.id == session_id).where(models.Session.user_id == user_id) - # honcho_session = db.scalars(stmt).one_or_none() +def update_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID, session_data: dict) -> bool: honcho_session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) - # honcho_session = db.get(models.Session, session_id) if honcho_session is None: raise ValueError("Session not found or does not belong to user") honcho_session.session_data = json.dumps(session_data) @@ -75,7 +59,7 @@ def update_session(db: Session, app_id: str, user_id: str, session_id: int, sess return honcho_session -def delete_session(db: Session, app_id: str, user_id: str, session_id: int) -> bool: +def delete_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID) -> bool: stmt = ( select(models.Session) .where(models.Session.id == session_id) @@ -83,7 +67,6 @@ def delete_session(db: Session, app_id: str, user_id: str, session_id: int) -> b .where(models.Session.user_id == user_id) ) honcho_session = db.scalars(stmt).one_or_none() - # honcho_session = db.get(models.Session, session_id) if honcho_session is None: return False honcho_session.is_active = False @@ -92,7 +75,7 @@ def delete_session(db: Session, app_id: str, user_id: str, session_id: int) -> b def create_message( - db: Session, message: schemas.MessageCreate, app_id: str, user_id: str, session_id: int + db: Session, message: schemas.MessageCreate, app_id: str, user_id: str, session_id: uuid.UUID ) -> models.Message: honcho_session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) if honcho_session is None: @@ -110,12 +93,19 @@ def create_message( def get_messages( - db: Session, app_id: str, user_id: str, session_id: int + db: Session, app_id: str, user_id: str, session_id: uuid.UUID ) -> Select: - session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) - if session is None: - raise ValueError("Session not found or does not belong to user") - stmt = select(models.Message).where(models.Message.session_id == session_id) + # session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) + # if session is None: + # raise ValueError("Session not found or does not belong to user") + stmt = ( + select(models.Message) + .join(models.Session, models.Session.id == models.Message.session_id) + .where(models.Session.app_id == app_id) + .where(models.Session.user_id == user_id) + .where(models.Message.session_id == session_id) + .order_by(models.Message.created_at) + ) return stmt # return db.scalars(stmt).all() # return ( @@ -124,23 +114,75 @@ def get_messages( # .all() # ) +def get_message( + db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID +) -> Optional[models.Message]: + # session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) + # if session is None: + # raise ValueError("Session not found or does not belong to user") + stmt = ( + select(models.Message) + .join(models.Session, models.Session.id == models.Message.session_id) + .where(models.Session.app_id == app_id) + .where(models.Session.user_id == user_id) + .where(models.Message.session_id == session_id) + .where(models.Message.id == message_id) + + ) + return db.scalars(stmt).one_or_none() -# def get_metacognitions(db: Session, message_id: int): -# return ( -# db.query(models.Metacognitions) -# .filter(models.Metacognitions.message_id == message_id) -# .all() -# ) - -# def create_metacognition( -# db: Session, metacognition: schemas.MetacognitionsCreate, message_id: int -# ): -# honcho_metacognition = models.Metacognitions( -# message_id=message_id, -# metacognition_type=metacognition.metacognition_type, -# content=metacognition.content, -# ) -# db.add(honcho_metacognition) -# db.commit() -# db.refresh(honcho_metacognition) -# return honcho_metacognition + +def get_metamessages(db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: Optional[uuid.UUID], metamessage_type: Optional[str] = None) -> Select: + stmt = ( + select(models.Metamessage) + .join(models.Message, models.Message.id == models.Metamessage.message_id) + .join(models.Session, models.Message.session_id == models.Session.id) + .where(models.Session.app_id == app_id) + .where(models.Session.user_id == user_id) + .where(models.Message.session_id == session_id) + .order_by(models.Metamessage.created_at) + ) + if message_id is not None: + stmt = stmt.where(models.Metamessage.message_id == message_id) + if metamessage_type is not None: + stmt = stmt.where(models.Metamessage.metamessage_type == metamessage_type) + return stmt + +def get_metamessage( + db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID +) -> Optional[models.Metamessage]: + stmt = ( + select(models.Metamessage) + .join(models.Message, models.Message.id == models.Metamessage.message_id) + .join(models.Session, models.Message.session_id == models.Session.id) + .where(models.Session.app_id == app_id) + .where(models.Session.user_id == user_id) + .where(models.Message.session_id == session_id) + .where(models.Metamessage.message_id == message_id) + .where(models.Metamessage.id == metamessage_id) + + ) + return db.scalars(stmt).one_or_none() + +def create_metamessage( + db: Session, + metamessage: schemas.MetamessageCreate, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: uuid.UUID, +): + message = get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id) + if message is None: + raise ValueError("Session not found or does not belong to user") + + honcho_metamessage = models.Metamessage( + message_id=message_id, + metamessage_type=metamessage.metamessage_type, + content=metamessage.content, + ) + + db.add(honcho_metamessage) + db.commit() + db.refresh(honcho_metamessage) + return honcho_metamessage diff --git a/api/src/main.py b/api/src/main.py index f35e953..747a11e 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,3 +1,4 @@ +import uuid from fastapi import Depends, FastAPI, HTTPException, APIRouter, Request from typing import Optional from sqlalchemy.orm import Session @@ -77,14 +78,21 @@ def create_session( schemas.Session: The Session object of the new Session """ - return crud.create_session(db, app_id=app_id, user_id=user_id, session=session) + print("===============================") + print(request) + print("===============================") + value = crud.create_session(db, app_id=app_id, user_id=user_id, session=session) + print("===============================") + print(value) + print("===============================") + return value @router.put("/sessions/{session_id}", response_model=schemas.Session) def update_session( request: Request, app_id: str, user_id: str, - session_id: int, + session_id: uuid.UUID, session: schemas.SessionUpdate, db: Session = Depends(get_db), ): @@ -112,7 +120,7 @@ def delete_session( request: Request, app_id: str, user_id: str, - session_id: int, + session_id: uuid.UUID, db: Session = Depends(get_db), ): """Delete a session by marking it as inactive @@ -136,7 +144,7 @@ def delete_session( raise HTTPException(status_code=404, detail="Session not found") @router.get("/sessions/{session_id}", response_model=schemas.Session) -def get_session(request: Request, app_id: str, user_id: str, session_id: int, db: Session = Depends(get_db)): +def get_session(request: Request, app_id: str, user_id: str, session_id: uuid.UUID, db: Session = Depends(get_db)): """Get a specific session for a user by ID Args: @@ -167,7 +175,7 @@ def create_message_for_session( request: Request, app_id: str, user_id: str, - session_id: int, + session_id: uuid.UUID, message: schemas.MessageCreate, db: Session = Depends(get_db), ): @@ -199,7 +207,7 @@ def get_messages_for_session( request: Request, app_id: str, user_id: str, - session_id: int, + session_id: uuid.UUID, db: Session = Depends(get_db), ): """Get all messages for a session @@ -221,36 +229,116 @@ def get_messages_for_session( except ValueError: raise HTTPException(status_code=404, detail="Session not found") +@router.get( + "sessions/{session_id}/messages/{message_id}", + response_model=schemas.Message +) +def get_message( + request: Request, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: uuid.UUID, + db: Session = Depends(get_db), +): + """ + + """ + honcho_message = crud.get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id) + if honcho_message is None: + raise HTTPException(status_code=404, detail="Session not found") + return honcho_message + -app.include_router(router) ######################################################## # Metacognition Routes ######################################################## -# @app.get( -# "/users/{user_id}/sessions/{session_id}/messages/{message_id}/metacognitions/", -# response_model=list[schemas.Metacognitions], -# ) -# def get_metacognitions_for_message( -# user_id: str, -# session_id: int, -# message_id: int, -# db: Session = Depends(get_db), -# ): -# return crud.get_metacognitions(db, message_id) - - -# @app.post( -# "/users/{user_id}/sessions/{session_id}/messages/{message_id}/metacognitions/", -# response_model=schemas.Metacognitions, -# ) -# def create_metacognition_for_message( -# user_id: str, -# session_id: int, -# message_id: int, -# metacognition: schemas.MetacognitionsCreate, -# db: Session = Depends(get_db), -# ): -# return crud.create_metacognition(db, metacognition, message_id) +@router.post( + "/sessions/{session_id}/metamessages", + response_model=schemas.Metamessage +) +def create_metamessage( + request: Request, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: uuid.UUID, + metamessage: schemas.MetamessageCreate, + db: Session = Depends(get_db), +): + """Adds a message to a session + + Args: + app_id (str): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to add the message to + message (schemas.MessageCreate): The Message object to add containing the message content and type + + Returns: + schemas.Message: The Message object of the added message + + Raises: + HTTPException: If the session is not found + + """ + try: + return crud.create_metamessage(db, metamessage=metamessage, app_id=app_id, user_id=user_id, session_id=session_id, message_id=message_id) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") + +@router.get( + "/sessions/{session_id}/metamessages", + response_model=Page[schemas.Metamessage] +) +def get_metamessages( + request: Request, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: Optional[uuid.UUID] = None, + metamessage_type: Optional[str] = None, + db: Session = Depends(get_db), +): + """Get all messages for a session + + Args: + app_id (str): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[schemas.Message]: List of Message objects + + Raises: + HTTPException: If the session is not found + + """ + try: + return paginate(db, crud.get_metamessages(db, app_id=app_id, user_id=user_id, session_id=session_id, message_id=message_id, metamessage_type=metamessage_type)) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") + +@router.get("/sessions/{session_id}/metamessages/{metamessage_id}", response_model=schemas.Metamessage) +def get_metamessage(request: Request, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID, db: Session = Depends(get_db)): + """Get a specific session for a user by ID + + Args: + app_id (str): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + schemas.Session: The Session object of the requested Session + + Raises: + HTTPException: If the session is not found + """ + honcho_metamessage = crud.get_metamessage(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id, metamessage_id=metamessage_id) + if honcho_metamessage is None: + raise HTTPException(status_code=404, detail="Session not found") + return honcho_metamessage + +app.include_router(router) diff --git a/api/src/models.py b/api/src/models.py index a269831..4371229 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -1,4 +1,5 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, Uuid +import uuid import datetime from sqlalchemy.orm import relationship, Mapped, mapped_column @@ -7,18 +8,12 @@ class Session(Base): __tablename__ = "sessions" - # id = Column(Integer, primary_key=True, index=True, autoincrement=True) - # user_id = Column(String, index=True) - # location_id = Column(String, index=True) - # is_active = Column(Boolean, default=True) - # session_data = Column(String) - # created_at = Column(DateTime, default=datetime.datetime.utcnow) - id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) - app_id: Mapped[str] = mapped_column(index=True) - user_id: Mapped[str] = mapped_column(index=True) - location_id: Mapped[str] = mapped_column(index=True) + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + app_id: Mapped[str] = mapped_column(String(512), index=True) + user_id: Mapped[str] = mapped_column(String(512), index=True) + location_id: Mapped[str] = mapped_column(String(512), index=True) is_active: Mapped[bool] = mapped_column(default=True) - session_data: Mapped[str] + session_data: Mapped[str] created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) messages = relationship("Message", back_populates="session") @@ -28,33 +23,27 @@ def __repr__(self) -> str: class Message(Base): __tablename__ = "messages" - # id = Column(Integer, primary_key=True, index=True, autoincrement=True) - # session_id = Column(Integer, ForeignKey("sessions.id")) - # is_user = Column(Boolean) - # content = Column(String) - id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) - session_id: Mapped[int] = mapped_column(ForeignKey("sessions.id")) + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("sessions.id")) is_user: Mapped[bool] - content: Mapped[str] # TODO add a max message length + content: Mapped[str] = mapped_column(String(65535)) + created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) session = relationship("Session", back_populates="messages") - metamessages = relationship("Metamessages", back_populates="message") + metamessages = relationship("Metamessage", back_populates="message") def __repr__(self) -> str: return f"Message(id={self.id}, session_id={self.session_id}, is_user={self.is_user}, content={self.content[10:]})" -# TODO: add metamessages data to messages -class Metamessages(Base): +class Metamessage(Base): __tablename__ = "metamessages" - id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) - metamessage_type: Mapped[str] # TODO add a max metamessages type length - content: Mapped[str] - # id = Column(Integer, primary_key=True, index=True, autoincrement=True) - message_id = Column(Integer, ForeignKey("messages.id")) - # metacognition_type = Column(String, index=True) - # content = Column(String) + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + metamessage_type: Mapped[str] = mapped_column(String(512), index=True) + content: Mapped[str] = mapped_column(String(65535)) + message_id = Column(Uuid, ForeignKey("messages.id")) message = relationship("Message", back_populates="metamessages") + created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) def __repr__(self) -> str: return f"Metamessages(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content[10:]})" diff --git a/api/src/schemas.py b/api/src/schemas.py index 8f51564..7c89975 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -1,4 +1,6 @@ from pydantic import BaseModel +import datetime +import uuid class MessageBase(BaseModel): @@ -11,8 +13,9 @@ class MessageCreate(MessageBase): class Message(MessageBase): - session_id: int - id: int + session_id: uuid.UUID + id: uuid.UUID + created_at: datetime.datetime class Config: orm_mode = True @@ -32,29 +35,32 @@ class SessionUpdate(SessionBase): class Session(SessionBase): - id: int + id: uuid.UUID # messages: list[Message] is_active: bool user_id: str location_id: str app_id: str session_data: str + created_at: datetime.datetime class Config: orm_mode = True -class MetamessagesBase(BaseModel): +class MetamessageBase(BaseModel): metamessage_type: str content: str -class MetamessagesCreate(MetamessagesBase): +class MetamessageCreate(MetamessageBase): pass -class Metamessages(MetamessagesBase): - id: int +class Metamessage(MetamessageBase): + id: uuid.UUID + message_id: uuid.UUID + created_at: datetime.datetime class Config: orm_mode = True diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 0d4920f..74b7660 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,4 +1,4 @@ -from .client import AsyncClient, AsyncSession, AsyncGetSessionResponse, AsyncGetMessageResponse -from .sync_client import Client, Session, GetSessionResponse, GetMessageResponse -from .schemas import Message +from .client import AsyncClient, AsyncSession, AsyncGetSessionPage, AsyncGetMessagePage +from .sync_client import Client, Session, GetSessionPage, GetMessagePage +from .schemas import Message, Metamessage from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 19f2951..c75e993 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,16 +1,29 @@ import json +import uuid from typing import Dict, Optional import httpx -from .schemas import Message +from .schemas import Message, Metamessage -class AsyncGetSessionResponse: - def __init__(self, client, response: Dict): - self.client = client +class AsyncGetPage: + def __init__(self, response: Dict) -> None: self.total = response["total"] self.page = response["page"] self.page_size = response["size"] self.pages = response["pages"] - self.sessions = [ + self.items =[] + + async def next(self): + pass + +class AsyncGetSessionPage(AsyncGetPage): + def __init__(self, client, response: Dict): + super().__init__(response) + self.client = client + # self.total = response["total"] + # self.page = response["page"] + # self.page_size = response["size"] + # self.pages = response["pages"] + self.items = [ AsyncSession( client=client, id=session["id"], @@ -25,23 +38,25 @@ def __init__(self, client, response: Dict): async def next(self): if self.page >= self.pages: return None - user_id = self.sessions[0].user_id - location_id = self.sessions[0].location_id + user_id = self.items[0].user_id + location_id = self.items[0].location_id return await self.client.get_sessions(user_id, location_id, self.page + 1, self.page_size) -class AsyncGetMessageResponse: +class AsyncGetMessagePage(AsyncGetPage): def __init__(self, session, response: Dict): + super().__init__(response) self.session = session - self.total = response["total"] - self.page = response["page"] - self.page_size = response["size"] - self.pages = response["pages"] - self.messages = [ + # self.total = response["total"] + # self.page = response["page"] + # self.page_size = response["size"] + # self.pages = response["pages"] + self.items = [ Message( session_id=session.id, id=message["id"], is_user=message["is_user"], content=message["content"], + created_at=message["created_at"], ) for message in response["items"] ] @@ -51,6 +66,27 @@ async def next(self): return None return await self.session.get_messages((self.page + 1), self.page_size) +class AsyncGetMetamessagePage(AsyncGetPage): + def __init__(self, session, response: Dict) -> None: + super().__init__(response) + self.session = session + self.items = [ + Metamessage( + id=metamessage["id"], + message_id=metamessage["message_id"], + metamessage_type=metamessage["metamessage_type"], + content=metamessage["content"], + created_at=metamessage["created_at"], + ) + for metamessage in response["items"] + ] + + async def next(self): + if self.page >= self.pages: + return None + return await self.session.get_metamessages((self.page + 1), self.page_size) + + class AsyncClient: def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): @@ -63,7 +99,7 @@ def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): def common_prefix(self): return f"{self.base_url}/apps/{self.app_id}" - async def get_session(self, user_id: str, session_id: int): + async def get_session(self, user_id: str, session_id: uuid.UUID): """Get a specific session for a user by ID Args: @@ -76,6 +112,7 @@ async def get_session(self, user_id: str, session_id: int): """ url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" response = await self.client.get(url) + response.raise_for_status() data = response.json() return AsyncSession( client=self, @@ -103,7 +140,7 @@ async def get_sessions(self, user_id: str, location_id: Optional[str] = None, pa response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetSessionResponse(self, data) + return AsyncGetSessionPage(self, data) async def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): page = 1 @@ -111,7 +148,7 @@ async def get_sessions_generator(self, user_id: str, location_id: Optional[str] get_session_response = await self.get_sessions(user_id, location_id, page, page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for session in get_session_response.sessions: + for session in get_session_response.items: yield session new_sessions = await get_session_response.next() @@ -137,6 +174,7 @@ async def create_session( data = {"location_id": location_id, "session_data": session_data} url = f"{self.common_prefix}/users/{user_id}/sessions" response = await self.client.post(url, json=data) + response.raise_for_status() data = response.json() return AsyncSession( self, @@ -152,7 +190,7 @@ class AsyncSession: def __init__( self, client: AsyncClient, - id: int, + id: uuid.UUID, user_id: str, location_id: str, session_data: dict | str, @@ -197,10 +235,27 @@ async def create_message(self, is_user: bool, content: str): data = {"is_user": is_user, "content": content} url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages" response = await self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content, created_at=data["created_at"]) + + async def get_message(self, message_id: uuid.UUID) -> Message: + """Get a specific message for a session based on ID + + Args: + message_id (uuid.UUID): The ID of the Message to retrieve + + Returns: + Message: The Message object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages/{message_id}" + response = await self.client.get(url) + response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content) + return Message(session_id=self.id, id=data["id"], is_user=data["is_user"], content=data["content"], created_at=data["created_at"]) - async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMessageResponse: + async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMessagePage: """Get all messages for a session Args: @@ -215,23 +270,63 @@ async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMess response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetMessageResponse(self, data) - # return [ - # Message( - # self, - # id=message["id"], - # is_user=message["is_user"], - # content=message["content"], - # ) - # for message in data - # ] + return AsyncGetMessagePage(self, data) + async def get_messages_generator(self): page = 1 page_size = 50 - get_messages_response = await self.get_messages(page, page_size) + get_messages_page= await self.get_messages(page, page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for message in get_messages_response.messages: + for message in get_messages_page.items: + yield message + + new_messages = await get_messages_page.next() + if not new_messages: + break + + get_messages_page = new_messages + + async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: + """Get a specific message for a session based on ID + + Args: + message_id (uuid.UUID): The ID of the Message to retrieve + + Returns: + Message: The Message object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages/{metamessage_id}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) + + async def get_metamessages(self, page: int = 1, page_size: int = 50) -> AsyncGetMetamessagePage: + """Get all messages for a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[Dict]: List of Message objects + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncGetMetamessagePage(self, data) + + async def get_metamessages_generator(self): + page = 1 + page_size = 50 + get_messages_response = await self.get_metamessages(page, page_size) + while True: + # get_session_response = self.get_sessions(user_id, location_id, page, page_size) + for message in get_messages_response.items: yield message new_messages = await get_messages_response.next() @@ -239,12 +334,13 @@ async def get_messages_generator(self): break get_messages_response = new_messages + async def update(self, session_data: Dict): - """Update the metadata of a session + """Update the session_data of a session Args: - session_data (Dict): The Session object containing any new metadata + session_data (Dict): The Session object containing any new session_data Returns: @@ -257,20 +353,9 @@ async def update(self, session_data: Dict): self.session_data = session_data return success - async def delete(self): - """Delete a session by marking it as inactive""" + async def close(self): + """Closes a session by marking it as inactive""" url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = await self.client.delete(url) self._is_active = False - -# class AsyncMessage: -# def __init__(self, session: AsyncSession, id: int, is_user: bool, content: str): -# """Constructor for Message""" -# self.session = session -# self.id = id -# self.is_user = is_user -# self.content = content - -# def __str__(self): -# return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/honcho/schemas.py b/sdk/honcho/schemas.py index a35e668..d2970c9 100644 --- a/sdk/honcho/schemas.py +++ b/sdk/honcho/schemas.py @@ -1,12 +1,26 @@ - +import uuid +import datetime class Message: - def __init__(self, session_id: int, id: int, is_user: bool, content: str): + def __init__(self, session_id: uuid.UUID, id: uuid.UUID, is_user: bool, content: str, created_at: datetime.datetime): """Constructor for Message""" self.session_id = session_id self.id = id self.is_user = is_user self.content = content + self.created_at = created_at def __str__(self): return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" + +class Metamessage: + def __init__(self, id: uuid.UUID, message_id: uuid.UUID, metamessage_type: str, content: str, created_at: datetime.datetime): + """Constructor for Metamessage""" + self.id = id + self.message_id = message_id + self.metamessage_type = metamessage_type + self.content = content + self.created_at = created_at + + def __str__(self): + return f"Metamessage(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content})" diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index b317700..dcf3146 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1,16 +1,29 @@ import json +import uuid from typing import Dict, Optional import httpx -from .schemas import Message +from .schemas import Message, Metamessage -class GetSessionResponse: - def __init__(self, client, response: Dict): - self.client = client +class GetPage: + def __init__(self, response: Dict) -> None: self.total = response["total"] self.page = response["page"] self.page_size = response["size"] self.pages = response["pages"] - self.sessions = [ + self.items =[] + + def next(self): + pass + +class GetSessionPage(GetPage): + def __init__(self, client, response: Dict): + super().__init__(response) + self.client = client + # self.total = response["total"] + # self.page = response["page"] + # self.page_size = response["size"] + # self.pages = response["pages"] + self.items = [ Session( client=client, id=session["id"], @@ -25,23 +38,25 @@ def __init__(self, client, response: Dict): def next(self): if self.page >= self.pages: return None - user_id = self.sessions[0].user_id - location_id = self.sessions[0].location_id + user_id = self.items[0].user_id + location_id = self.items[0].location_id return self.client.get_sessions(user_id, location_id, self.page + 1, self.page_size) -class GetMessageResponse: +class GetMessagePage(GetPage): def __init__(self, session, response: Dict): + super().__init__(response) self.session = session - self.total = response["total"] - self.page = response["page"] - self.page_size = response["size"] - self.pages = response["pages"] - self.messages = [ + # self.total = response["total"] + # self.page = response["page"] + # self.page_size = response["size"] + # self.pages = response["pages"] + self.items = [ Message( session_id=session.id, id=message["id"], is_user=message["is_user"], content=message["content"], + created_at=message["created_at"], ) for message in response["items"] ] @@ -51,6 +66,27 @@ def next(self): return None return self.session.get_messages((self.page + 1), self.page_size) +class GetMetamessagePage(GetPage): + def __init__(self, session, response: Dict) -> None: + super().__init__(response) + self.session = session + self.items = [ + Metamessage( + id=metamessage["id"], + message_id=metamessage["message_id"], + metamessage_type=metamessage["metamessage_type"], + content=metamessage["content"], + created_at=metamessage["created_at"], + ) + for metamessage in response["items"] + ] + + def next(self): + if self.page >= self.pages: + return None + return self.session.get_metamessages((self.page + 1), self.page_size) + + class Client: def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): @@ -63,7 +99,7 @@ def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): def common_prefix(self): return f"{self.base_url}/apps/{self.app_id}" - def get_session(self, user_id: str, session_id: int): + def get_session(self, user_id: str, session_id: uuid.UUID): """Get a specific session for a user by ID Args: @@ -76,6 +112,7 @@ def get_session(self, user_id: str, session_id: int): """ url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" response = self.client.get(url) + response.raise_for_status() data = response.json() return Session( client=self, @@ -103,7 +140,7 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in response = self.client.get(url) response.raise_for_status() data = response.json() - return GetSessionResponse(self, data) + return GetSessionPage(self, data) def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): page = 1 @@ -111,7 +148,7 @@ def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None get_session_response = self.get_sessions(user_id, location_id, page, page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for session in get_session_response.sessions: + for session in get_session_response.items: yield session new_sessions = get_session_response.next() @@ -137,6 +174,7 @@ def create_session( data = {"location_id": location_id, "session_data": session_data} url = f"{self.common_prefix}/users/{user_id}/sessions" response = self.client.post(url, json=data) + response.raise_for_status() data = response.json() return Session( self, @@ -152,7 +190,7 @@ class Session: def __init__( self, client: Client, - id: int, + id: uuid.UUID, user_id: str, location_id: str, session_data: dict | str, @@ -197,10 +235,27 @@ def create_message(self, is_user: bool, content: str): data = {"is_user": is_user, "content": content} url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages" response = self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content, created_at=data["created_at"]) + + def get_message(self, message_id: uuid.UUID) -> Message: + """Get a specific message for a session based on ID + + Args: + message_id (uuid.UUID): The ID of the Message to retrieve + + Returns: + Message: The Message object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages/{message_id}" + response = self.client.get(url) + response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content) + return Message(session_id=self.id, id=data["id"], is_user=data["is_user"], content=data["content"], created_at=data["created_at"]) - def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessageResponse: + def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessagePage: """Get all messages for a session Args: @@ -215,23 +270,63 @@ def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessageResponse response = self.client.get(url) response.raise_for_status() data = response.json() - return GetMessageResponse(self, data) - # return [ - # Message( - # self, - # id=message["id"], - # is_user=message["is_user"], - # content=message["content"], - # ) - # for message in data - # ] + return GetMessagePage(self, data) + def get_messages_generator(self): page = 1 page_size = 50 - get_messages_response = self.get_messages(page, page_size) + get_messages_page= self.get_messages(page, page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for message in get_messages_response.messages: + for message in get_messages_page.items: + yield message + + new_messages = get_messages_page.next() + if not new_messages: + break + + get_messages_page = new_messages + + def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: + """Get a specific message for a session based on ID + + Args: + message_id (uuid.UUID): The ID of the Message to retrieve + + Returns: + Message: The Message object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages/{metamessage_id}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) + + def get_metamessages(self, page: int = 1, page_size: int = 50) -> GetMetamessagePage: + """Get all messages for a session + + Args: + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + list[Dict]: List of Message objects + + """ + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return GetMetamessagePage(self, data) + + def get_metamessages_generator(self): + page = 1 + page_size = 50 + get_messages_response = self.get_metamessages(page, page_size) + while True: + # get_session_response = self.get_sessions(user_id, location_id, page, page_size) + for message in get_messages_response.items: yield message new_messages = get_messages_response.next() @@ -239,12 +334,13 @@ def get_messages_generator(self): break get_messages_response = new_messages + def update(self, session_data: Dict): - """Update the metadata of a session + """Update the session_data of a session Args: - session_data (Dict): The Session object containing any new metadata + session_data (Dict): The Session object containing any new session_data Returns: @@ -257,20 +353,9 @@ def update(self, session_data: Dict): self.session_data = session_data return success - def delete(self): - """Delete a session by marking it as inactive""" + def close(self): + """Closes a session by marking it as inactive""" url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = self.client.delete(url) self._is_active = False - -# class Message: -# def __init__(self, session: Session, id: int, is_user: bool, content: str): -# """Constructor for Message""" -# self.session = session -# self.id = id -# self.is_user = is_user -# self.content = content - -# def __str__(self): -# return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index d724b12..097911f 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -1,5 +1,5 @@ import pytest -from honcho import AsyncGetSessionResponse, AsyncGetMessageResponse, AsyncSession, Message +from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncSession, Message from honcho import AsyncClient as Honcho from uuid import uuid1 @@ -25,7 +25,7 @@ async def test_session_multiple_retrieval(): created_session_1 = await client.create_session(user_id) created_session_2 = await client.create_session(user_id) response = await client.get_sessions(user_id) - retrieved_sessions = response.sessions + retrieved_sessions = response.items assert len(retrieved_sessions) == 2 assert retrieved_sessions[0].id == created_session_1.id @@ -50,7 +50,7 @@ async def test_session_deletion(): client = Honcho(app_id, "http://localhost:8000") created_session = await client.create_session(user_id) assert created_session.is_active is True - await created_session.delete() + await created_session.close() assert created_session.is_active is False retrieved_session = await client.get_session(user_id, created_session.id) assert retrieved_session.is_active is False @@ -67,7 +67,7 @@ async def test_messages(): await created_session.create_message(is_user=False, content="Hi") retrieved_session = await client.get_session(user_id, created_session.id) response = await retrieved_session.get_messages() - messages = response.messages + messages = response.items assert len(messages) == 2 user_message, ai_message = messages assert user_message.content == "Hello" @@ -111,18 +111,18 @@ async def test_paginated_sessions(): page = 1 page_size = 2 get_session_response = await client.get_sessions(user_id, page=page, page_size=page_size) - assert len(get_session_response.sessions) == page_size + assert len(get_session_response.items) == page_size assert get_session_response.pages == 5 new_session_response = await get_session_response.next() assert new_session_response is not None - assert isinstance(new_session_response, AsyncGetSessionResponse) - assert len(new_session_response.sessions) == page_size + assert isinstance(new_session_response, AsyncGetSessionPage) + assert len(new_session_response.items) == page_size final_page = await client.get_sessions(user_id, page=5, page_size=page_size) - assert len(final_page.sessions) == 2 + assert len(final_page.items) == 2 next_page = await final_page.next() assert next_page is None @@ -161,7 +161,7 @@ async def test_paginated_out_of_bounds(): assert get_session_response.page == 2 assert get_session_response.page_size == 50 assert get_session_response.total == 3 - assert len(get_session_response.sessions) == 0 + assert len(get_session_response.items) == 0 @pytest.mark.asyncio @@ -178,18 +178,18 @@ async def test_paginated_messages(): get_message_response = await created_session.get_messages(page=1, page_size=page_size) assert get_message_response is not None - assert isinstance(get_message_response, AsyncGetMessageResponse) - assert len(get_message_response.messages) == page_size + assert isinstance(get_message_response, AsyncGetMessagePage) + assert len(get_message_response.items) == page_size new_message_response = await get_message_response.next() assert new_message_response is not None - assert isinstance(new_message_response, AsyncGetMessageResponse) - assert len(new_message_response.messages) == page_size + assert isinstance(new_message_response, AsyncGetMessagePage) + assert len(new_message_response.items) == page_size final_page = await created_session.get_messages(page=3, page_size=page_size) - assert len(final_page.messages) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3-1) * 7) next_page = await final_page.next() diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 06fbf3c..8daedec 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,4 +1,4 @@ -from honcho import GetSessionResponse, GetMessageResponse, Session, Message +from honcho import GetSessionPage, GetMessagePage, Session, Message from honcho import Client as Honcho from uuid import uuid1 import pytest @@ -22,7 +22,7 @@ def test_session_multiple_retrieval(): created_session_1 = client.create_session(user_id) created_session_2 = client.create_session(user_id) response = client.get_sessions(user_id) - retrieved_sessions = response.sessions + retrieved_sessions = response.items assert len(retrieved_sessions) == 2 assert retrieved_sessions[0].id == created_session_1.id @@ -45,7 +45,7 @@ def test_session_deletion(): client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) assert created_session.is_active is True - created_session.delete() + created_session.close() assert created_session.is_active is False retrieved_session = client.get_session(user_id, created_session.id) assert retrieved_session.is_active is False @@ -61,7 +61,7 @@ def test_messages(): created_session.create_message(is_user=False, content="Hi") retrieved_session = client.get_session(user_id, created_session.id) response = retrieved_session.get_messages() - messages = response.messages + messages = response.items assert len(messages) == 2 user_message, ai_message = messages assert user_message.content == "Hello" @@ -102,18 +102,18 @@ def test_paginated_sessions(): page = 1 page_size = 2 get_session_response = client.get_sessions(user_id, page=page, page_size=page_size) - assert len(get_session_response.sessions) == page_size + assert len(get_session_response.items) == page_size assert get_session_response.pages == 5 new_session_response = get_session_response.next() assert new_session_response is not None - assert isinstance(new_session_response, GetSessionResponse) - assert len(new_session_response.sessions) == page_size + assert isinstance(new_session_response, GetSessionPage) + assert len(new_session_response.items) == page_size final_page = client.get_sessions(user_id, page=5, page_size=page_size) - assert len(final_page.sessions) == 2 + assert len(final_page.items) == 2 next_page = final_page.next() assert next_page is None @@ -150,7 +150,7 @@ def test_paginated_out_of_bounds(): assert get_session_response.page == 2 assert get_session_response.page_size == 50 assert get_session_response.total == 3 - assert len(get_session_response.sessions) == 0 + assert len(get_session_response.items) == 0 def test_paginated_messages(): @@ -166,18 +166,18 @@ def test_paginated_messages(): get_message_response = created_session.get_messages(page=1, page_size=page_size) assert get_message_response is not None - assert isinstance(get_message_response, GetMessageResponse) - assert len(get_message_response.messages) == page_size + assert isinstance(get_message_response, GetMessagePage) + assert len(get_message_response.items) == page_size new_message_response = get_message_response.next() assert new_message_response is not None - assert isinstance(new_message_response, GetMessageResponse) - assert len(new_message_response.messages) == page_size + assert isinstance(new_message_response, GetMessagePage) + assert len(new_message_response.items) == page_size final_page = created_session.get_messages(page=3, page_size=page_size) - assert len(final_page.messages) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3-1) * 7) next_page = final_page.next() From 3926aaf8716dc4f50471e31e5139a8fb2b25c750 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 08:52:35 -0800 Subject: [PATCH 11/85] Work with unit tests --- api/src/crud.py | 5 ++- api/src/main.py | 12 +++---- api/src/schemas.py | 2 +- sdk/honcho/__init__.py | 4 +-- sdk/honcho/client.py | 69 ++++++++++++++++++++++++++++++--------- sdk/honcho/sync_client.py | 69 ++++++++++++++++++++++++++++++--------- sdk/tests/test_async.py | 59 +++++++++++++++++++++++++++++++-- sdk/tests/test_sync.py | 57 +++++++++++++++++++++++++++++++- 8 files changed, 228 insertions(+), 49 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index c0dbb24..a130892 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -170,14 +170,13 @@ def create_metamessage( app_id: str, user_id: str, session_id: uuid.UUID, - message_id: uuid.UUID, ): - message = get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id) + message = get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=metamessage.message_id) if message is None: raise ValueError("Session not found or does not belong to user") honcho_metamessage = models.Metamessage( - message_id=message_id, + message_id=metamessage.message_id, metamessage_type=metamessage.metamessage_type, content=metamessage.content, ) diff --git a/api/src/main.py b/api/src/main.py index 747a11e..c3ef0d8 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -78,13 +78,7 @@ def create_session( schemas.Session: The Session object of the new Session """ - print("===============================") - print(request) - print("===============================") value = crud.create_session(db, app_id=app_id, user_id=user_id, session=session) - print("===============================") - print(value) - print("===============================") return value @router.put("/sessions/{session_id}", response_model=schemas.Session) @@ -264,7 +258,6 @@ def create_metamessage( app_id: str, user_id: str, session_id: uuid.UUID, - message_id: uuid.UUID, metamessage: schemas.MetamessageCreate, db: Session = Depends(get_db), ): @@ -283,8 +276,11 @@ def create_metamessage( HTTPException: If the session is not found """ + print("=======================") + print(request) + print("=======================") try: - return crud.create_metamessage(db, metamessage=metamessage, app_id=app_id, user_id=user_id, session_id=session_id, message_id=message_id) + return crud.create_metamessage(db, metamessage=metamessage, app_id=app_id, user_id=user_id, session_id=session_id) except ValueError: raise HTTPException(status_code=404, detail="Session not found") diff --git a/api/src/schemas.py b/api/src/schemas.py index 7c89975..b6bff90 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -54,7 +54,7 @@ class MetamessageBase(BaseModel): class MetamessageCreate(MetamessageBase): - pass + message_id: uuid.UUID class Metamessage(MetamessageBase): diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 74b7660..e87b439 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,4 +1,4 @@ -from .client import AsyncClient, AsyncSession, AsyncGetSessionPage, AsyncGetMessagePage -from .sync_client import Client, Session, GetSessionPage, GetMessagePage +from .client import AsyncClient, AsyncSession, AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage +from .sync_client import Client, Session, GetSessionPage, GetMessagePage, GetMetamessagePage from .schemas import Message, Metamessage from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index c75e993..afb3c8e 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -16,9 +16,11 @@ async def next(self): pass class AsyncGetSessionPage(AsyncGetPage): - def __init__(self, client, response: Dict): + def __init__(self, client, options: Dict, response: Dict): super().__init__(response) self.client = client + self.user_id = options["user_id"] + self.location_id = options["location_id"] # self.total = response["total"] # self.page = response["page"] # self.page_size = response["size"] @@ -38,9 +40,9 @@ def __init__(self, client, response: Dict): async def next(self): if self.page >= self.pages: return None - user_id = self.items[0].user_id - location_id = self.items[0].location_id - return await self.client.get_sessions(user_id, location_id, self.page + 1, self.page_size) + # user_id = self.items[0].user_id + # location_id = self.items[0].location_id + return await self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) class AsyncGetMessagePage(AsyncGetPage): def __init__(self, session, response: Dict): @@ -67,9 +69,11 @@ async def next(self): return await self.session.get_messages((self.page + 1), self.page_size) class AsyncGetMetamessagePage(AsyncGetPage): - def __init__(self, session, response: Dict) -> None: + def __init__(self, session, options: Dict, response: Dict) -> None: super().__init__(response) self.session = session + self.message_id = options["message_id"] + self.metamessage_type = options["metamessage_type"] self.items = [ Metamessage( id=metamessage["id"], @@ -84,7 +88,7 @@ def __init__(self, session, response: Dict) -> None: async def next(self): if self.page >= self.pages: return None - return await self.session.get_metamessages((self.page + 1), self.page_size) + return await self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) @@ -140,7 +144,11 @@ async def get_sessions(self, user_id: str, location_id: Optional[str] = None, pa response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetSessionPage(self, data) + options = { + "location_id": location_id, + "user_id": user_id + } + return AsyncGetSessionPage(self, options, data) async def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): page = 1 @@ -287,6 +295,27 @@ async def get_messages_generator(self): get_messages_page = new_messages + async def create_metamessage(self, message: Message, metamessage_type: str, content: str): + """Adds a metamessage to the session + + Args: + is_user (bool): Whether the message is from the user + content (str): The content of the message + + Returns: + Dict: The Message object of the added message + + """ + if not self.is_active: + raise Exception("Session is inactive") + data = {"metamessage_type": metamessage_type, "content": content, "message_id": message.id} + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + response = await self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Metamessage(id=data["id"], message_id=message.id, metamessage_type=metamessage_type, content=content, created_at=data["created_at"]) + + async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific message for a session based on ID @@ -303,7 +332,7 @@ async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: data = response.json() return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) - async def get_metamessages(self, page: int = 1, page_size: int = 50) -> AsyncGetMetamessagePage: + async def get_metamessages(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, page: int = 1, page_size: int = 50) -> AsyncGetMetamessagePage: """Get all messages for a session Args: @@ -314,26 +343,34 @@ async def get_metamessages(self, page: int = 1, page_size: int = 50) -> AsyncGet list[Dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}" + if metamessage_type: + url += f"&metamessage_type={metamessage_type}" + if message: + url += f"&message_id={message.id}" response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetMetamessagePage(self, data) + options = { + "metamessage_type": metamessage_type, + "message_id": message.id if message else None + } + return AsyncGetMetamessagePage(self, options, data) - async def get_metamessages_generator(self): + async def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): page = 1 page_size = 50 - get_messages_response = await self.get_metamessages(page, page_size) + get_metamessages_page = await self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for message in get_messages_response.items: - yield message + for metamessage in get_metamessages_page.items: + yield metamessage - new_messages = await get_messages_response.next() + new_messages = await get_metamessages_page.next() if not new_messages: break - get_messages_response = new_messages + get_metamessages_page = new_messages async def update(self, session_data: Dict): diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index dcf3146..8c8da1e 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -16,9 +16,11 @@ def next(self): pass class GetSessionPage(GetPage): - def __init__(self, client, response: Dict): + def __init__(self, client, options: Dict, response: Dict): super().__init__(response) self.client = client + self.user_id = options["user_id"] + self.location_id = options["location_id"] # self.total = response["total"] # self.page = response["page"] # self.page_size = response["size"] @@ -38,9 +40,9 @@ def __init__(self, client, response: Dict): def next(self): if self.page >= self.pages: return None - user_id = self.items[0].user_id - location_id = self.items[0].location_id - return self.client.get_sessions(user_id, location_id, self.page + 1, self.page_size) + # user_id = self.items[0].user_id + # location_id = self.items[0].location_id + return self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) class GetMessagePage(GetPage): def __init__(self, session, response: Dict): @@ -67,9 +69,11 @@ def next(self): return self.session.get_messages((self.page + 1), self.page_size) class GetMetamessagePage(GetPage): - def __init__(self, session, response: Dict) -> None: + def __init__(self, session, options: Dict, response: Dict) -> None: super().__init__(response) self.session = session + self.message_id = options["message_id"] + self.metamessage_type = options["metamessage_type"] self.items = [ Metamessage( id=metamessage["id"], @@ -84,7 +88,7 @@ def __init__(self, session, response: Dict) -> None: def next(self): if self.page >= self.pages: return None - return self.session.get_metamessages((self.page + 1), self.page_size) + return self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) @@ -140,7 +144,11 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in response = self.client.get(url) response.raise_for_status() data = response.json() - return GetSessionPage(self, data) + options = { + "location_id": location_id, + "user_id": user_id + } + return GetSessionPage(self, options, data) def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): page = 1 @@ -287,6 +295,27 @@ def get_messages_generator(self): get_messages_page = new_messages + def create_metamessage(self, message: Message, metamessage_type: str, content: str): + """Adds a metamessage to the session + + Args: + is_user (bool): Whether the message is from the user + content (str): The content of the message + + Returns: + Dict: The Message object of the added message + + """ + if not self.is_active: + raise Exception("Session is inactive") + data = {"metamessage_type": metamessage_type, "content": content, "message_id": message.id} + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + response = self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Metamessage(id=data["id"], message_id=message.id, metamessage_type=metamessage_type, content=content, created_at=data["created_at"]) + + def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific message for a session based on ID @@ -303,7 +332,7 @@ def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: data = response.json() return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) - def get_metamessages(self, page: int = 1, page_size: int = 50) -> GetMetamessagePage: + def get_metamessages(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, page: int = 1, page_size: int = 50) -> GetMetamessagePage: """Get all messages for a session Args: @@ -314,26 +343,34 @@ def get_metamessages(self, page: int = 1, page_size: int = 50) -> GetMetamessage list[Dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}" + if metamessage_type: + url += f"&metamessage_type={metamessage_type}" + if message: + url += f"&message_id={message.id}" response = self.client.get(url) response.raise_for_status() data = response.json() - return GetMetamessagePage(self, data) + options = { + "metamessage_type": metamessage_type, + "message_id": message.id if message else None + } + return GetMetamessagePage(self, options, data) - def get_metamessages_generator(self): + def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): page = 1 page_size = 50 - get_messages_response = self.get_metamessages(page, page_size) + get_metamessages_page = self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) - for message in get_messages_response.items: - yield message + for metamessage in get_metamessages_page.items: + yield metamessage - new_messages = get_messages_response.next() + new_messages = get_metamessages_page.next() if not new_messages: break - get_messages_response = new_messages + get_metamessages_page = new_messages def update(self, session_data: Dict): diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index 097911f..8e5904d 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -1,5 +1,5 @@ import pytest -from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncSession, Message +from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncSession, Message, Metamessage from honcho import AsyncClient as Honcho from uuid import uuid1 @@ -195,7 +195,6 @@ async def test_paginated_messages(): assert next_page is None - @pytest.mark.asyncio async def test_paginated_messages_generator(): app_id = str(uuid1()) @@ -217,3 +216,59 @@ async def test_paginated_messages_generator(): with pytest.raises(StopAsyncIteration): await gen.__anext__() +@pytest.mark.asyncio +async def test_paginated_metamessages(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + created_session = await client.create_session(user_id) + message = await created_session.create_message(is_user=True, content="Hello") + for i in range(10): + await created_session.create_metamessage(message=message, metamessage_type="thought", content=f"Test {i}") + await created_session.create_metamessage(message=message, metamessage_type="reflect", content=f"Test {i}") + + page_size = 7 + page = await created_session.get_metamessages(page=1, page_size=page_size) + + assert page is not None + assert isinstance(page, AsyncGetMetamessagePage) + assert len(page.items) == page_size + + new_page = await page.next() + + assert new_page is not None + assert isinstance(new_page, AsyncGetMetamessagePage) + assert len(new_page.items) == page_size + + final_page = await created_session.get_metamessages(page=3, page_size=page_size) + + assert len(final_page.items) == 20 - ((3-1) * 7) + + next_page = await final_page.next() + + assert next_page is None + +@pytest.mark.asyncio +async def test_paginated_metamessages_generator(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + created_session = await client.create_session(user_id) + message = await created_session.create_message(is_user=True, content="Hello") + await created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 1") + await created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") + gen = created_session.get_metamessages_generator() + + item = await gen.__anext__() + assert isinstance(item, Metamessage) + assert item.content == "Test 1" + assert item.metamessage_type == "thought" + item2 = await gen.__anext__() + assert item2 is not None + assert item2.content == "Test 2" + assert item2.metamessage_type == "thought" + with pytest.raises(StopAsyncIteration): + await gen.__anext__() + + + diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 8daedec..135cc61 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,4 +1,4 @@ -from honcho import GetSessionPage, GetMessagePage, Session, Message +from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, Session, Message, Metamessage from honcho import Client as Honcho from uuid import uuid1 import pytest @@ -204,3 +204,58 @@ def test_paginated_messages_generator(): with pytest.raises(StopIteration): next(gen) + +def test_paginated_metamessages(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + created_session = client.create_session(user_id) + message = created_session.create_message(is_user=True, content="Hello") + for i in range(10): + created_session.create_metamessage(message=message, metamessage_type="thought", content=f"Test {i}") + created_session.create_metamessage(message=message, metamessage_type="reflect", content=f"Test {i}") + + page_size = 7 + page = created_session.get_metamessages(page=1, page_size=page_size) + + assert page is not None + assert isinstance(page, GetMetamessagePage) + assert len(page.items) == page_size + + new_page = page.next() + + assert new_page is not None + assert isinstance(new_page, GetMetamessagePage) + assert len(new_page.items) == page_size + + final_page = created_session.get_metamessages(page=3, page_size=page_size) + + assert len(final_page.items) == 20 - ((3-1) * 7) + + next_page = final_page.next() + + assert next_page is None + +def test_paginated_metamessages_generator(): + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + created_session = client.create_session(user_id) + message = created_session.create_message(is_user=True, content="Hello") + created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 1") + created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") + gen = created_session.get_metamessages_generator() + + item = next(gen) + assert isinstance(item, Metamessage) + assert item.content == "Test 1" + assert item.metamessage_type == "thought" + item2 = next(gen) + assert item2 is not None + assert item2.content == "Test 2" + assert item2.metamessage_type == "thought" + with pytest.raises(StopIteration): + next(gen) + + + From ddaa3114ce7712a96e17f5463f1d25bbeaff5f0d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:04:43 -0800 Subject: [PATCH 12/85] Fix Examples --- example/cli/main.py | 2 +- example/discord/fake-llm/main.py | 2 +- example/discord/fake-llm/poetry.lock | 276 ++++++++++++++++------- example/discord/simple-roast-bot/main.py | 2 +- 4 files changed, 196 insertions(+), 86 deletions(-) diff --git a/example/cli/main.py b/example/cli/main.py index e1aa3fd..12e01aa 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -36,7 +36,7 @@ def chat(): while True: user_input = input("User: ") if user_input == "exit": - session.delete() + session.close() break user_message = HumanMessage(content=user_input) history = list(session.get_messages_generator()) diff --git a/example/discord/fake-llm/main.py b/example/discord/fake-llm/main.py index fd81260..6ae6a35 100644 --- a/example/discord/fake-llm/main.py +++ b/example/discord/fake-llm/main.py @@ -52,7 +52,7 @@ async def restart(ctx): user_id = f"discord_{str(ctx.author.id)}" location_id = str(ctx.channel_id) sessions = list(honcho.get_sessions_generator(user_id, location_id)) - sessions[0].delete() if len(sessions) > 0 else None + sessions[0].close() if len(sessions) > 0 else None await ctx.respond( "Great! The conversation has been restarted. What would you like to talk about?" diff --git a/example/discord/fake-llm/poetry.lock b/example/discord/fake-llm/poetry.lock index fbabb69..d66136d 100644 --- a/example/discord/fake-llm/poetry.lock +++ b/example/discord/fake-llm/poetry.lock @@ -124,6 +124,27 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "async-timeout" version = "4.0.3" @@ -158,14 +179,14 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -355,6 +376,18 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +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 = "honcho-ai" version = "0.0.2" @@ -366,11 +399,59 @@ files = [] develop = true [package.dependencies] +httpx = "^0.26.0" requests = "^2.31.0" [package.source] type = "directory" -url = "../../sdk" +url = "../../../sdk" + +[[package]] +name = "httpcore" +version = "1.0.2" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = ">=1.0.0,<2.0.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" @@ -386,86 +467,102 @@ files = [ [[package]] name = "multidict" -version = "6.0.4" +version = "6.0.5" description = "multidict implementation" category = "main" 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"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] @@ -525,20 +622,33 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +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 = "urllib3" -version = "2.1.0" +version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -649,4 +759,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "56228d417333540e3191575720739ae6fff9490b68e9ddaae2cb6fe44b4bf611" +content-hash = "f31c071455001b66fe72eb743b16c64f108172c4314475b772e5cbd18942d0dc" diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py index 0797607..9cb1e7f 100644 --- a/example/discord/simple-roast-bot/main.py +++ b/example/discord/simple-roast-bot/main.py @@ -82,7 +82,7 @@ async def restart(ctx): user_id=f"discord_{str(ctx.author.id)}" location_id=str(ctx.channel_id) sessions = list(honcho.get_sessions_generator(user_id, location_id)) - sessions[0].delete() if len(sessions) > 0 else None + sessions[0].close() if len(sessions) > 0 else None msg = "Great! The conversation has been restarted. What would you like to talk about?" await ctx.respond(msg) From a7373d19cb58528a1aae51b78c0eb1332e4614d2 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:37:35 -0800 Subject: [PATCH 13/85] MEME-78 Update Changelogs --- README.md | 2 +- api/CHANGELOG.md | 20 ++++++ api/pyproject.toml | 4 +- sdk/CHANGELOG.md | 25 +++++++ sdk/poetry.lock | 164 ++++++--------------------------------------- sdk/pyproject.toml | 1 - 6 files changed, 70 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 49a8c1d..47c9441 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Honcho -![Static Badge](https://img.shields.io/badge/Version-0.0.1-blue) +![Static Badge](https://img.shields.io/badge/Version-0.0.2-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 722f7a7..56d7915 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.2] — 2024-02-01 + +### Added + +* Pagination for requests via `fastapi_pagination` +* Metamessages +* `get_message` routes +* `created_at` field added to each Table +* Message size limits + +### Changed + +* IDs are now UUIDs +* default rate limit now 100 requests per minute + +### Removed + +* Removed messages from session response model + + ## [0.0.1] — 2024-02-01 ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index bcf746e..6777d84 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "honcho" -version = "0.0.1" -description = "" +version = "0.0.2" +description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 8a6da89..1d8ecd5 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.2] — 2024-02-08 + +### Added + +* Async client +* Metamessages introduced +* Paginated results for get requests +* `created_at` field added to Messages and Metamessages +* added singular `get_message` method +* Size limits for messages and string fields + +### Changed + +* Default rate limit of 100/minutes +* Changed default ID type to use UUIDs +* `session.delete()` is now `session.close()` +* replace `requests` for `httpx` + + +### Removed + +* Removed messages from session response model + + + ## [0.0.1] — 2024-02-01 ### Added diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 50f59b6..e450ca8 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -26,6 +27,7 @@ trio = ["trio (>=0.23)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -33,109 +35,11 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] -[[package]] -name = "charset-normalizer" -version = "3.3.2" -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.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -147,6 +51,7 @@ files = [ name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -161,6 +66,7 @@ test = ["pytest (>=6)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -172,6 +78,7 @@ files = [ name = "httpcore" version = "1.0.2" description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -186,13 +93,14 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -203,20 +111,21 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = "==1.*" +httpcore = ">=1.0.0,<2.0.0" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -228,6 +137,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -239,6 +149,7 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -250,6 +161,7 @@ files = [ name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -265,6 +177,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -287,6 +200,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.23.4" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -301,31 +215,11 @@ pytest = ">=7.0.0,<8" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] -[[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 = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -337,6 +231,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -348,6 +243,7 @@ files = [ name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -355,23 +251,7 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] -[[package]] -name = "urllib3" -version = "2.1.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d5dbd21023598b83062e7705f329579b9175f4da4db66559ea84399246cfcc25" +content-hash = "6ccea662fa5a5bae88618123d5d05e0d4955c234b7e1a688d2fae2f90cd9f7f8" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index cb66980..ff695b5 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -9,7 +9,6 @@ packages = [{include = "honcho"}] [tool.poetry.dependencies] python = "^3.10" -requests = "^2.31.0" httpx = "^0.26.0" [tool.poetry.group.test.dependencies] From f99f1e8d0475c144ced57aebca01bc912f1ea52d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:50:40 -0800 Subject: [PATCH 14/85] Docstrings to client --- sdk/honcho/client.py | 122 ++++++++++++++++++++++++++++++-------- sdk/honcho/sync_client.py | 120 +++++++++++++++++++++++++++++-------- 2 files changed, 193 insertions(+), 49 deletions(-) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index afb3c8e..c5a1373 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -5,7 +5,13 @@ from .schemas import Message, Metamessage class AsyncGetPage: + """Base class for receiving Paginated API results""" def __init__(self, response: Dict) -> None: + """Constructor for Page with relevant information about the results and pages + + Args: + response (Dict): Response from API with pagination information + """ self.total = response["total"] self.page = response["page"] self.page_size = response["size"] @@ -13,18 +19,24 @@ def __init__(self, response: Dict) -> None: self.items =[] async def next(self): + """Shortcut method to Get the next page of results""" pass class AsyncGetSessionPage(AsyncGetPage): + """Paginated Results for Get Session Requests""" + def __init__(self, client, options: Dict, response: Dict): + """Constructor for Page Result from Session Get Request + + Args: + client (AsyncClient): Honcho Client + options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional + response (Dict): Response from API with pagination information + """ super().__init__(response) self.client = client self.user_id = options["user_id"] self.location_id = options["location_id"] - # self.total = response["total"] - # self.page = response["page"] - # self.page_size = response["size"] - # self.pages = response["pages"] self.items = [ AsyncSession( client=client, @@ -38,20 +50,26 @@ def __init__(self, client, options: Dict, response: Dict): ] async def next(self): + """Get the next page of results + Returns: + AsyncGetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ if self.page >= self.pages: return None - # user_id = self.items[0].user_id - # location_id = self.items[0].location_id return await self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) class AsyncGetMessagePage(AsyncGetPage): + """Paginated Results for Get Session Requests""" + def __init__(self, session, response: Dict): + """Constructor for Page Result from Session Get Request + + Args: + session (AsyncSession): Session the returned messages are associated with + response (Dict): Response from API with pagination information + """ super().__init__(response) self.session = session - # self.total = response["total"] - # self.page = response["page"] - # self.page_size = response["size"] - # self.pages = response["pages"] self.items = [ Message( session_id=session.id, @@ -64,12 +82,24 @@ def __init__(self, session, response: Dict): ] async def next(self): + """Get the next page of results + Returns: + AsyncGetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query + """ if self.page >= self.pages: return None return await self.session.get_messages((self.page + 1), self.page_size) class AsyncGetMetamessagePage(AsyncGetPage): + def __init__(self, session, options: Dict, response: Dict) -> None: + """Constructor for Page Result from Metamessage Get Request + + Args: + session (AsyncSession): Session the returned messages are associated with + options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both required + response (Dict): Response from API with pagination information + """ super().__init__(response) self.session = session self.message_id = options["message_id"] @@ -86,6 +116,10 @@ def __init__(self, session, options: Dict, response: Dict) -> None: ] async def next(self): + """Get the next page of results + Returns: + AsyncGetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query + """ if self.page >= self.pages: return None return await self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) @@ -93,6 +127,8 @@ async def next(self): class AsyncClient: + """Honcho API Client Object""" + def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API @@ -101,6 +137,7 @@ def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): @property def common_prefix(self): + """Shorcut for common API prefix. made a property to prevent tampering""" return f"{self.base_url}/apps/{self.app_id}" async def get_session(self, user_id: str, session_id: uuid.UUID): @@ -108,10 +145,10 @@ async def get_session(self, user_id: str, session_id: uuid.UUID): Args: user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + session_id (uuid.UUID): The ID of the Session to retrieve Returns: - Dict: The Session object of the requested Session + AsyncSession: The Session object of the requested Session """ url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" @@ -128,14 +165,16 @@ async def get_session(self, user_id: str, session_id: uuid.UUID): ) async def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): - """Return sessions associated with a user + """Return sessions associated with a user paginated Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return Returns: - list[Dict]: List of Session objects + AsyncGetSessionPage: Page or results for get_sessions query """ url = f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}" + ( @@ -151,6 +190,16 @@ async def get_sessions(self, user_id: str, location_id: Optional[str] = None, pa return AsyncGetSessionPage(self, options, data) async def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): + """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + + Yields: + AsyncSession: The Session object of the requested Session + + """ page = 1 page_size = 50 get_session_response = await self.get_sessions(user_id, location_id, page, page_size) @@ -176,7 +225,7 @@ async def create_session( session_data (Dict, optional): Optional session metadata Returns: - Dict: The Session object of the new Session` + AsyncSession: The Session object of the new Session """ data = {"location_id": location_id, "session_data": session_data} @@ -195,6 +244,8 @@ async def create_session( class AsyncSession: + """Represents a single session for a user in an app""" + def __init__( self, client: AsyncClient, @@ -218,13 +269,16 @@ def __init__( @property def common_prefix(self): + """Shortcut for common API prefix. made a property to prevent tampering""" return f"{self.base_url}/apps/{self.app_id}" def __str__(self): - return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" + """String representation of Session""" + return f"AsyncSession(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" @property def is_active(self): + """Returns whether the session is active - made property to prevent tampering""" return self._is_active async def create_message(self, is_user: bool, content: str): @@ -235,7 +289,7 @@ async def create_message(self, is_user: bool, content: str): content (str): The content of the message Returns: - Dict: The Message object of the added message + Message: The Message object of the added message """ if not self.is_active: @@ -267,11 +321,11 @@ async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMess """Get all messages for a session Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page Returns: - list[Dict]: List of Message objects + AsyncGetMessagePage: Page of Message objects """ url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" @@ -281,6 +335,12 @@ async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMess return AsyncGetMessagePage(self, data) async def get_messages_generator(self): + """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + + Yields: + Message: The Message object of the next Message + + """ page = 1 page_size = 50 get_messages_page= await self.get_messages(page, page_size) @@ -296,14 +356,15 @@ async def get_messages_generator(self): get_messages_page = new_messages async def create_metamessage(self, message: Message, metamessage_type: str, content: str): - """Adds a metamessage to the session + """Adds a metamessage to a session and links it to a specific message Args: - is_user (bool): Whether the message is from the user - content (str): The content of the message + message (Message): A message to associate the metamessage with + metamessage_type (str): The type of the metamessage arbitrary itentifier + content (str): The content of the metamessage Returns: - Dict: The Message object of the added message + Metamessage: The Metamessage object of the added metamessage """ if not self.is_active: @@ -317,7 +378,7 @@ async def create_metamessage(self, message: Message, metamessage_type: str, cont async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: - """Get a specific message for a session based on ID + """Get a specific metamessage Args: message_id (uuid.UUID): The ID of the Message to retrieve @@ -358,6 +419,16 @@ async def get_metamessages(self, metamessage_type: Optional[str] = None, message return AsyncGetMetamessagePage(self, options, data) async def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): + """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app + + Args: + metamessage_type (str, optional): Optional Metamessage type to filter by + message (Message, optional): Optional Message to filter by + + Yields: + Metamessage: The next Metamessage object of the requested query + + """ page = 1 page_size = 50 get_metamessages_page = await self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) @@ -394,5 +465,6 @@ async def close(self): """Closes a session by marking it as inactive""" url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = await self.client.delete(url) + response.raise_for_status() self._is_active = False diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 8c8da1e..2adc4e4 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -5,7 +5,13 @@ from .schemas import Message, Metamessage class GetPage: + """Base class for receiving Paginated API results""" def __init__(self, response: Dict) -> None: + """Constructor for Page with relevant information about the results and pages + + Args: + response (Dict): Response from API with pagination information + """ self.total = response["total"] self.page = response["page"] self.page_size = response["size"] @@ -13,18 +19,24 @@ def __init__(self, response: Dict) -> None: self.items =[] def next(self): + """Shortcut method to Get the next page of results""" pass class GetSessionPage(GetPage): + """Paginated Results for Get Session Requests""" + def __init__(self, client, options: Dict, response: Dict): + """Constructor for Page Result from Session Get Request + + Args: + client (Client): Honcho Client + options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional + response (Dict): Response from API with pagination information + """ super().__init__(response) self.client = client self.user_id = options["user_id"] self.location_id = options["location_id"] - # self.total = response["total"] - # self.page = response["page"] - # self.page_size = response["size"] - # self.pages = response["pages"] self.items = [ Session( client=client, @@ -38,20 +50,26 @@ def __init__(self, client, options: Dict, response: Dict): ] def next(self): + """Get the next page of results + Returns: + GetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ if self.page >= self.pages: return None - # user_id = self.items[0].user_id - # location_id = self.items[0].location_id return self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) class GetMessagePage(GetPage): + """Paginated Results for Get Session Requests""" + def __init__(self, session, response: Dict): + """Constructor for Page Result from Session Get Request + + Args: + session (Session): Session the returned messages are associated with + response (Dict): Response from API with pagination information + """ super().__init__(response) self.session = session - # self.total = response["total"] - # self.page = response["page"] - # self.page_size = response["size"] - # self.pages = response["pages"] self.items = [ Message( session_id=session.id, @@ -64,12 +82,24 @@ def __init__(self, session, response: Dict): ] def next(self): + """Get the next page of results + Returns: + GetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query + """ if self.page >= self.pages: return None return self.session.get_messages((self.page + 1), self.page_size) class GetMetamessagePage(GetPage): + def __init__(self, session, options: Dict, response: Dict) -> None: + """Constructor for Page Result from Metamessage Get Request + + Args: + session (Session): Session the returned messages are associated with + options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both required + response (Dict): Response from API with pagination information + """ super().__init__(response) self.session = session self.message_id = options["message_id"] @@ -86,6 +116,10 @@ def __init__(self, session, options: Dict, response: Dict) -> None: ] def next(self): + """Get the next page of results + Returns: + GetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query + """ if self.page >= self.pages: return None return self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) @@ -93,6 +127,8 @@ def next(self): class Client: + """Honcho API Client Object""" + def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API @@ -101,6 +137,7 @@ def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): @property def common_prefix(self): + """Shorcut for common API prefix. made a property to prevent tampering""" return f"{self.base_url}/apps/{self.app_id}" def get_session(self, user_id: str, session_id: uuid.UUID): @@ -108,10 +145,10 @@ def get_session(self, user_id: str, session_id: uuid.UUID): Args: user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + session_id (uuid.UUID): The ID of the Session to retrieve Returns: - Dict: The Session object of the requested Session + Session: The Session object of the requested Session """ url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" @@ -128,14 +165,16 @@ def get_session(self, user_id: str, session_id: uuid.UUID): ) def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): - """Return sessions associated with a user + """Return sessions associated with a user paginated Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return Returns: - list[Dict]: List of Session objects + GetSessionPage: Page or results for get_sessions query """ url = f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}" + ( @@ -151,6 +190,16 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in return GetSessionPage(self, options, data) def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): + """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + + Args: + user_id (str): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a session + + Yields: + Session: The Session object of the requested Session + + """ page = 1 page_size = 50 get_session_response = self.get_sessions(user_id, location_id, page, page_size) @@ -176,7 +225,7 @@ def create_session( session_data (Dict, optional): Optional session metadata Returns: - Dict: The Session object of the new Session` + Session: The Session object of the new Session """ data = {"location_id": location_id, "session_data": session_data} @@ -195,6 +244,8 @@ def create_session( class Session: + """Represents a single session for a user in an app""" + def __init__( self, client: Client, @@ -218,13 +269,16 @@ def __init__( @property def common_prefix(self): + """Shortcut for common API prefix. made a property to prevent tampering""" return f"{self.base_url}/apps/{self.app_id}" def __str__(self): + """String representation of Session""" return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" @property def is_active(self): + """Returns whether the session is active - made property to prevent tampering""" return self._is_active def create_message(self, is_user: bool, content: str): @@ -235,7 +289,7 @@ def create_message(self, is_user: bool, content: str): content (str): The content of the message Returns: - Dict: The Message object of the added message + Message: The Message object of the added message """ if not self.is_active: @@ -267,11 +321,11 @@ def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessagePage: """Get all messages for a session Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page Returns: - list[Dict]: List of Message objects + GetMessagePage: Page of Message objects """ url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" @@ -281,6 +335,12 @@ def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessagePage: return GetMessagePage(self, data) def get_messages_generator(self): + """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + + Yields: + Message: The Message object of the next Message + + """ page = 1 page_size = 50 get_messages_page= self.get_messages(page, page_size) @@ -296,14 +356,15 @@ def get_messages_generator(self): get_messages_page = new_messages def create_metamessage(self, message: Message, metamessage_type: str, content: str): - """Adds a metamessage to the session + """Adds a metamessage to a session and links it to a specific message Args: - is_user (bool): Whether the message is from the user - content (str): The content of the message + message (Message): A message to associate the metamessage with + metamessage_type (str): The type of the metamessage arbitrary itentifier + content (str): The content of the metamessage Returns: - Dict: The Message object of the added message + Metamessage: The Metamessage object of the added metamessage """ if not self.is_active: @@ -317,7 +378,7 @@ def create_metamessage(self, message: Message, metamessage_type: str, content: s def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: - """Get a specific message for a session based on ID + """Get a specific metamessage Args: message_id (uuid.UUID): The ID of the Message to retrieve @@ -358,6 +419,16 @@ def get_metamessages(self, metamessage_type: Optional[str] = None, message: Opti return GetMetamessagePage(self, options, data) def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): + """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app + + Args: + metamessage_type (str, optional): Optional Metamessage type to filter by + message (Message, optional): Optional Message to filter by + + Yields: + Metamessage: The next Metamessage object of the requested query + + """ page = 1 page_size = 50 get_metamessages_page = self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) @@ -394,5 +465,6 @@ def close(self): """Closes a session by marking it as inactive""" url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = self.client.delete(url) + response.raise_for_status() self._is_active = False From 716e8ca39a3c360901bab4c1edc82dcd6e6ee279 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 12:56:26 -0500 Subject: [PATCH 15/85] =?UTF-8?q?=F0=9F=A7=AA=20autogenerate=20sync=20test?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/syncronizer.py | 20 ++++++++++++++++++++ sdk/tests/test_sync.py | 31 +++++++++++++++---------------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py index 758f3eb..0630b8f 100644 --- a/scripts/syncronizer.py +++ b/scripts/syncronizer.py @@ -16,3 +16,23 @@ destination_file_path = os.path.join(this_dir, "../sdk/honcho/sync_client.py") with open(destination_file_path, "w") as destination_file: destination_file.write(sync_code) + + +# tests + +# Open the source file +source_file_path = os.path.join(this_dir, "../sdk/tests/test_async.py") +with open(source_file_path, "r") as source_file: + source_code = source_file.read() + +# Use regex to remove async mentions +sync_code = re.sub(r"@pytest.mark.asyncio\n", "", source_code) +sync_code = re.sub(r"async\s", "", sync_code) +sync_code = re.sub(r"await\s", "", sync_code) +sync_code = re.sub(r"__anext__", "__next__", sync_code) +sync_code = re.sub(r"Async", "", sync_code) + +# Write the modified code to the destination file +destination_file_path = os.path.join(this_dir, "../sdk/tests/test_sync.py") +with open(destination_file_path, "w") as destination_file: + destination_file.write(sync_code) diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 135cc61..aba875f 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,7 +1,8 @@ +import pytest from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, Session, Message, Metamessage from honcho import Client as Honcho from uuid import uuid1 -import pytest + def test_session_creation_retrieval(): app_id = str(uuid1()) @@ -30,8 +31,8 @@ def test_session_multiple_retrieval(): def test_session_update(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) assert created_session.update({"foo": "bar"}) @@ -40,8 +41,8 @@ def test_session_update(): def test_session_deletion(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) assert created_session.is_active is True @@ -53,8 +54,8 @@ def test_session_deletion(): def test_messages(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) created_session.create_message(is_user=True, content="Hello") @@ -128,13 +129,13 @@ def test_paginated_sessions_generator(): gen = client.get_sessions_generator(user_id) # print(type(gen)) - item = next(gen) + item = gen.__next__() assert item.user_id == user_id assert isinstance(item, Session) - assert next(gen) is not None - assert next(gen) is not None + assert gen.__next__() is not None + assert gen.__next__() is not None with pytest.raises(StopIteration): - next(gen) + gen.__next__() def test_paginated_out_of_bounds(): app_id = str(uuid1()) @@ -183,7 +184,6 @@ def test_paginated_messages(): assert next_page is None - def test_paginated_messages_generator(): app_id = str(uuid1()) user_id = str(uuid1()) @@ -193,17 +193,16 @@ def test_paginated_messages_generator(): created_session.create_message(is_user=False, content="Hi") gen = created_session.get_messages_generator() - item = next(gen) + item = gen.__next__() assert isinstance(item, Message) assert item.content == "Hello" assert item.is_user is True - item2 = next(gen) + item2 = gen.__next__() assert item2 is not None assert item2.content == "Hi" assert item2.is_user is False with pytest.raises(StopIteration): - next(gen) - + gen.__next__() def test_paginated_metamessages(): app_id = str(uuid1()) @@ -246,16 +245,16 @@ def test_paginated_metamessages_generator(): created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") gen = created_session.get_metamessages_generator() - item = next(gen) + item = gen.__next__() assert isinstance(item, Metamessage) assert item.content == "Test 1" assert item.metamessage_type == "thought" - item2 = next(gen) + item2 = gen.__next__() assert item2 is not None assert item2.content == "Test 2" assert item2.metamessage_type == "thought" with pytest.raises(StopIteration): - next(gen) + gen.__next__() From 735d780a256675ced80aca98357029bb958e8329 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 14:50:10 -0500 Subject: [PATCH 16/85] test one --- .github/workflows/api_testing.yml | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/api_testing.yml diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml new file mode 100644 index 0000000..0ae177b --- /dev/null +++ b/.github/workflows/api_testing.yml @@ -0,0 +1,32 @@ +name: Run Tests +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install poetry + run: | + pip install poetry + - name: Start Server + run: | + cd api + poetry install --no-root + poetry run uvicorn src.main:app & + sleep 5 + cd .. + - name: Run Tests + run: | + cd sdk + poetry install + poetry run pytest + - name: Stop Server + run: | + kill $(jobs -p) || true + + + From 783156f61f381b6a742ffaaaa0ab225a2c9f3cde Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 14:52:31 -0500 Subject: [PATCH 17/85] add db type --- .github/workflows/api_testing.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index 0ae177b..cf6514e 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -19,6 +19,9 @@ jobs: poetry run uvicorn src.main:app & sleep 5 cd .. + env: + DATABASE_TYPE: sqlite + CONNECTION_URI: sqlite:///api.db - name: Run Tests run: | cd sdk From 287db71751c7d22716a0434e19393481695c7561 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 15:34:39 -0500 Subject: [PATCH 18/85] sync client --- .github/workflows/api_testing.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index cf6514e..f5e6780 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -12,6 +12,9 @@ jobs: - name: Install poetry run: | pip install poetry + - name: Syncify Client + run: | + python scripts/syncronizer.py - name: Start Server run: | cd api From 811c7268a839ea5b0d26a3a122f93b539995138b Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 15:38:22 -0500 Subject: [PATCH 19/85] add status badge --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 47c9441..0aa1a9a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # Honcho + ![Static Badge](https://img.shields.io/badge/Version-0.0.2-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) [![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fplastic_labs)](https://twitter.com/plastic_labs) +[![Run Tests](https://github.com/plastic-labs/honcho/actions/workflows/api_testing.yml/badge.svg?branch=staging)](https://github.com/plastic-labs/honcho/actions/workflows/api_testing.yml) + A User context management solution for building AI Agents and LLM powered applications. @@ -48,7 +51,7 @@ poetry install # install dependencies 2. Copy the `.env.template` file and specify the type of database and connection_uri. For testing sqlite is fine. The below example uses an - in-memory sqlite database. + in-memory sqlite database. > Honcho has been tested with Postgresql and SQLite @@ -83,8 +86,7 @@ docker run --env-file .env -p 8000:8000 honcho-api:latest The API can also be deployed on fly.io. Follow the [Fly.io Docs](https://fly.io/docs/getting-started/) to setup your environment and the -`flyctl`. - +`flyctl`. Once `flyctl` is set up use the the following commands to launch the application: @@ -127,12 +129,12 @@ See more information [here](https://python-poetry.org/docs/cli/#add) This project is completely open source and welcomes any and all open source contributions. The workflow for contributing is to make a fork of the repository. You can claim an issue in the issues tab or start a new thread to -indicate a feature or bug fix you are working on. +indicate a feature or bug fix you are working on. Once you have finished your contribution make a PR pointed at the `staging` branch, and it will be reviewed by a project manager. Feel free to join us in our [discord](http://discord.gg/plasticlabs) to discuss your changes or get -help. +help. Once your changes are accepted and merged into staging they will undergo a period of live testing before entering the upstream into `main` From cb42724f721b6e13c7f578182c4c95fb3c368748 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 15:52:02 -0500 Subject: [PATCH 20/85] add coverage --- .github/workflows/api_testing.yml | 17 +++++- sdk/poetry.lock | 92 +++++++++++++++++++++++-------- sdk/pyproject.toml | 1 + 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index f5e6780..52eed8c 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -29,7 +29,22 @@ jobs: run: | cd sdk poetry install - poetry run pytest + poetry run coverage run -m pytest + poetry run coverage xml coverage.xml + cd .. + - name: Code Coverage + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: sdk/coverage.xml + badge: true + format: markdown + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-results.md - name: Stop Server run: | kill $(jobs -p) || true diff --git a/sdk/poetry.lock b/sdk/poetry.lock index e450ca8..965b0ac 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -27,7 +26,6 @@ trio = ["trio (>=0.23)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -39,7 +37,6 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -47,11 +44,74 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.4.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, +] + +[package.extras] +toml = ["tomli"] + [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -66,7 +126,6 @@ test = ["pytest (>=6)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -78,7 +137,6 @@ files = [ name = "httpcore" version = "1.0.2" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -93,14 +151,13 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -111,21 +168,20 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = ">=1.0.0,<2.0.0" +httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -137,7 +193,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -149,7 +204,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -161,7 +215,6 @@ files = [ name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -177,7 +230,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -200,7 +252,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.23.4" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -219,7 +270,6 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -231,7 +281,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -243,7 +292,6 @@ files = [ name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -254,4 +302,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "6ccea662fa5a5bae88618123d5d05e0d4955c234b7e1a688d2fae2f90cd9f7f8" +content-hash = "cfdd3c0dc8dba3a70135da5b63a3b027968bb935d4846491c7bba2f30ac20a32" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index ff695b5..ac25ab2 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -14,6 +14,7 @@ httpx = "^0.26.0" [tool.poetry.group.test.dependencies] pytest = "^7.4.4" pytest-asyncio = "^0.23.4" +coverage = "^7.4.1" [build-system] requires = ["poetry-core"] From 77f180ae497c02c3d858c2ce3464dbdf0ad8aa9f Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 16:04:56 -0500 Subject: [PATCH 21/85] add file --- .github/workflows/api_testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index 52eed8c..31341ec 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -37,8 +37,8 @@ jobs: with: filename: sdk/coverage.xml badge: true + output: file format: markdown - - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 if: github.event_name == 'pull_request' From f359be4dcabf5ed96fb479c3f07fe725838a9f51 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 16:10:10 -0500 Subject: [PATCH 22/85] give perms --- .github/workflows/api_testing.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index 31341ec..ea03bad 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -2,6 +2,8 @@ name: Run Tests on: [push, pull_request] jobs: test: + permissions: + pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 178b4889c30fa73e4a27b5f4e77a6a687a1dc3b5 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 16:15:15 -0500 Subject: [PATCH 23/85] properly output coverage --- .github/workflows/api_testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api_testing.yml b/.github/workflows/api_testing.yml index ea03bad..e7c40a6 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/api_testing.yml @@ -32,7 +32,7 @@ jobs: cd sdk poetry install poetry run coverage run -m pytest - poetry run coverage xml coverage.xml + poetry run coverage xml -o coverage.xml cd .. - name: Code Coverage uses: irongut/CodeCoverageSummary@v1.3.0 From 18241c911ee2260d3cf5389028b1e79ce5e5c819 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 16:44:27 -0500 Subject: [PATCH 24/85] split test and coverage --- .../{api_testing.yml => run_coverage.yml} | 8 +--- .github/workflows/run_tests.yml | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) rename .github/workflows/{api_testing.yml => run_coverage.yml} (91%) create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/api_testing.yml b/.github/workflows/run_coverage.yml similarity index 91% rename from .github/workflows/api_testing.yml rename to .github/workflows/run_coverage.yml index e7c40a6..4b52640 100644 --- a/.github/workflows/api_testing.yml +++ b/.github/workflows/run_coverage.yml @@ -1,5 +1,5 @@ name: Run Tests -on: [push, pull_request] +on: [pull_request] jobs: test: permissions: @@ -43,13 +43,9 @@ jobs: format: markdown - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 - if: github.event_name == 'pull_request' with: recreate: true path: code-coverage-results.md - name: Stop Server run: | - kill $(jobs -p) || true - - - + kill $(jobs -p) || true \ No newline at end of file diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..05e3aa8 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,38 @@ +name: Run Tests +on: [push, pull_request] +jobs: + test: + permissions: + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install poetry + run: | + pip install poetry + - name: Syncify Client + run: | + python scripts/syncronizer.py + - name: Start Server + run: | + cd api + poetry install --no-root + poetry run uvicorn src.main:app & + sleep 5 + cd .. + env: + DATABASE_TYPE: sqlite + CONNECTION_URI: sqlite:///api.db + - name: Run Tests + run: | + cd sdk + poetry install + poetry run pytest + cd .. + - name: Stop Server + run: | + kill $(jobs -p) || true \ No newline at end of file From 555c84829453023bd42e5d3d983f73f277649053 Mon Sep 17 00:00:00 2001 From: hyusap Date: Wed, 14 Feb 2024 16:45:21 -0500 Subject: [PATCH 25/85] rename action --- .github/workflows/run_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 4b52640..bc2d780 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -1,4 +1,4 @@ -name: Run Tests +name: Run Coverage on: [pull_request] jobs: test: From fcde94ec67d9273ae22a02ea702ee379c2e5c73d Mon Sep 17 00:00:00 2001 From: Ayush Paul Date: Thu, 15 Feb 2024 09:01:28 -0500 Subject: [PATCH 26/85] =?UTF-8?q?=F0=9F=A7=AA=20autogenerate=20sync=20test?= =?UTF-8?q?s=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/syncronizer.py | 20 ++++++++++++++++++++ sdk/tests/test_sync.py | 31 +++++++++++++++---------------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py index 758f3eb..0630b8f 100644 --- a/scripts/syncronizer.py +++ b/scripts/syncronizer.py @@ -16,3 +16,23 @@ destination_file_path = os.path.join(this_dir, "../sdk/honcho/sync_client.py") with open(destination_file_path, "w") as destination_file: destination_file.write(sync_code) + + +# tests + +# Open the source file +source_file_path = os.path.join(this_dir, "../sdk/tests/test_async.py") +with open(source_file_path, "r") as source_file: + source_code = source_file.read() + +# Use regex to remove async mentions +sync_code = re.sub(r"@pytest.mark.asyncio\n", "", source_code) +sync_code = re.sub(r"async\s", "", sync_code) +sync_code = re.sub(r"await\s", "", sync_code) +sync_code = re.sub(r"__anext__", "__next__", sync_code) +sync_code = re.sub(r"Async", "", sync_code) + +# Write the modified code to the destination file +destination_file_path = os.path.join(this_dir, "../sdk/tests/test_sync.py") +with open(destination_file_path, "w") as destination_file: + destination_file.write(sync_code) diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 135cc61..aba875f 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,7 +1,8 @@ +import pytest from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, Session, Message, Metamessage from honcho import Client as Honcho from uuid import uuid1 -import pytest + def test_session_creation_retrieval(): app_id = str(uuid1()) @@ -30,8 +31,8 @@ def test_session_multiple_retrieval(): def test_session_update(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) assert created_session.update({"foo": "bar"}) @@ -40,8 +41,8 @@ def test_session_update(): def test_session_deletion(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) assert created_session.is_active is True @@ -53,8 +54,8 @@ def test_session_deletion(): def test_messages(): - app_id = str(uuid1()) user_id = str(uuid1()) + app_id = str(uuid1()) client = Honcho(app_id, "http://localhost:8000") created_session = client.create_session(user_id) created_session.create_message(is_user=True, content="Hello") @@ -128,13 +129,13 @@ def test_paginated_sessions_generator(): gen = client.get_sessions_generator(user_id) # print(type(gen)) - item = next(gen) + item = gen.__next__() assert item.user_id == user_id assert isinstance(item, Session) - assert next(gen) is not None - assert next(gen) is not None + assert gen.__next__() is not None + assert gen.__next__() is not None with pytest.raises(StopIteration): - next(gen) + gen.__next__() def test_paginated_out_of_bounds(): app_id = str(uuid1()) @@ -183,7 +184,6 @@ def test_paginated_messages(): assert next_page is None - def test_paginated_messages_generator(): app_id = str(uuid1()) user_id = str(uuid1()) @@ -193,17 +193,16 @@ def test_paginated_messages_generator(): created_session.create_message(is_user=False, content="Hi") gen = created_session.get_messages_generator() - item = next(gen) + item = gen.__next__() assert isinstance(item, Message) assert item.content == "Hello" assert item.is_user is True - item2 = next(gen) + item2 = gen.__next__() assert item2 is not None assert item2.content == "Hi" assert item2.is_user is False with pytest.raises(StopIteration): - next(gen) - + gen.__next__() def test_paginated_metamessages(): app_id = str(uuid1()) @@ -246,16 +245,16 @@ def test_paginated_metamessages_generator(): created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") gen = created_session.get_metamessages_generator() - item = next(gen) + item = gen.__next__() assert isinstance(item, Metamessage) assert item.content == "Test 1" assert item.metamessage_type == "thought" - item2 = next(gen) + item2 = gen.__next__() assert item2 is not None assert item2.content == "Test 2" assert item2.metamessage_type == "thought" with pytest.raises(StopIteration): - next(gen) + gen.__next__() From 8421fda71c7c9696e69f80e4492c1582a41b150c Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:09:43 -0800 Subject: [PATCH 27/85] Vector Support (#18) * Scaffold for PGVector support * Buggy crud with logic skeleton on api * Crud logic and schema definition for pgvector * Populate all routes and refactor to name Collection * vince's progress * AsyncCollection progress * Local PGVector Docker Container * client methods for sdk except document delete and update * Vector Support Passing All Test Cases * Docs Updates --------- Co-authored-by: vintro --- README.md | 19 +- api/.env.template | 5 + api/CHANGELOG.md | 16 ++ api/local/docker-compose.yml | 14 ++ api/local/init.sql | 1 + api/poetry.lock | 170 +++++++++++++- api/pyproject.toml | 4 +- api/src/crud.py | 249 +++++++++++++++++++-- api/src/main.py | 199 +++++++++++++++-- api/src/models.py | 46 +++- api/src/schemas.py | 77 ++++++- sdk/CHANGELOG.md | 15 ++ sdk/honcho/__init__.py | 6 +- sdk/honcho/client.py | 417 ++++++++++++++++++++++++++++++++--- sdk/honcho/schemas.py | 12 + sdk/honcho/sync_client.py | 409 +++++++++++++++++++++++++++++++--- sdk/pyproject.toml | 2 +- sdk/tests/test_async.py | 99 ++++++++- sdk/tests/test_sync.py | 96 +++++++- 19 files changed, 1725 insertions(+), 131 deletions(-) create mode 100644 api/local/docker-compose.yml create mode 100644 api/local/init.sql diff --git a/README.md b/README.md index 47c9441..e67c360 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Honcho -![Static Badge](https://img.shields.io/badge/Version-0.0.2-blue) +![Static Badge](https://img.shields.io/badge/Version-0.0.3-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) @@ -50,14 +50,21 @@ poetry install # install dependencies connection_uri. For testing sqlite is fine. The below example uses an in-memory sqlite database. -> Honcho has been tested with Postgresql and SQLite +> Honcho has been tested with Postgresql and PGVector ```env -DATABASE_TYPE=sqlite -CONNECTION_URI=sqlite:// +DATABASE_TYPE=postgres +CONNECTION_URI=postgresql://testuser:testpwd@localhost:5432/honcho ``` -3. Run the API via uvicorn +3. launch a postgresd with pgvector enabled with docker-compose + +```bash +cd honcho/api/local +docker-compose up -d +``` + +4. Run the API via uvicorn ```bash cd honcho/api # change to the api directory @@ -86,7 +93,7 @@ Docs](https://fly.io/docs/getting-started/) to setup your environment and the `flyctl`. -Once `flyctl` is set up use the the following commands to launch the application: +Once `flyctl` is set up use the following commands to launch the application: ```bash cd honcho/api diff --git a/api/.env.template b/api/.env.template index 3e73476..04ee33b 100644 --- a/api/.env.template +++ b/api/.env.template @@ -1,2 +1,7 @@ DATABASE_TYPE=sqlite CONNECTION_URI=sqlite:// + +DATABASE_TYPE=postgres +CONNECTION_URI=postgresql://testuser:testpwd@localhost:5432/honcho + +OPENAI_API_KEY= diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 56d7915..84cd89e 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.3] — 2024-02-15 + +### Added + +* Collections table to reference a collection of embedding documents +* Documents table to hold vector embeddings for RAG workflows +* Local scripts for running a postgres database with pgvector installed +* OpenAI Dependency for embedding models +* PGvector dependency for vector db support + +### Changed + +* session_data is now metadata +* session_data is a JSON field used python `dict` for compatability + + ## [0.0.2] — 2024-02-01 ### Added diff --git a/api/local/docker-compose.yml b/api/local/docker-compose.yml new file mode 100644 index 0000000..ea0f376 --- /dev/null +++ b/api/local/docker-compose.yml @@ -0,0 +1,14 @@ +services: + db: + hostname: db + image: ankane/pgvector + ports: + - 5432:5432 + restart: always + environment: + - POSTGRES_DB=honcho + - POSTGRES_USER=testuser + - POSTGRES_PASSWORD=testpwd + - POSTGRES_HOST_AUTH_METHOD=trust + volumes: + - ./init.sql:/docker-entrypoint-initdb.d/init.sql diff --git a/api/local/init.sql b/api/local/init.sql new file mode 100644 index 0000000..0aa0fc2 --- /dev/null +++ b/api/local/init.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS vector; diff --git a/api/poetry.lock b/api/poetry.lock index 252389c..3b8e3cd 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -37,6 +37,18 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- 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 = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + [[package]] name = "click" version = "8.1.7" @@ -82,6 +94,18 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -236,6 +260,53 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "1.0.3" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.24.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = ">=1.0.0,<2.0.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + [[package]] name = "idna" version = "3.6" @@ -297,6 +368,68 @@ mongodb = ["pymongo (>4.1,<5)"] redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +[[package]] +name = "numpy" +version = "1.24.4" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +] + +[[package]] +name = "openai" +version = "1.12.0" +description = "The official Python library for the openai API" +category = "main" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, + {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + [[package]] name = "packaging" version = "23.2" @@ -309,6 +442,20 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pgvector" +version = "0.2.5" +description = "pgvector support for Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pgvector-0.2.5-py2.py3-none-any.whl", hash = "sha256:5e5e93ec4d3c45ab1fa388729d56c602f6966296e19deee8878928c6d567e41b"}, +] + +[package.dependencies] +numpy = "*" + [[package]] name = "psycopg2-binary" version = "2.9.9" @@ -681,6 +828,27 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, +] + +[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.9.0" @@ -812,4 +980,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "115cde0c7dc1de7906b4f17bbdaced3f98a969c33c3b85976a1dc5b0aeece3e2" +content-hash = "90a0874f29e706994647a141418ed4eca5bd621518396d525d27039ad586e4bc" diff --git a/api/pyproject.toml b/api/pyproject.toml index 6777d84..6034c3f 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho" -version = "0.0.2" +version = "0.0.3" description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" @@ -14,6 +14,8 @@ sqlalchemy = "^2.0.25" psycopg2-binary = "^2.9.9" slowapi = "^0.1.8" fastapi-pagination = "^0.12.14" +pgvector = "^0.2.5" +openai = "^1.12.0" [build-system] diff --git a/api/src/crud.py b/api/src/crud.py index a130892..335ce10 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -1,12 +1,16 @@ -import json import uuid -from typing import Optional +import datetime +from typing import Optional, Sequence + +from openai import OpenAI from sqlalchemy import select, Select from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError from . import models, schemas +openai_client = OpenAI() def get_session(db: Session, app_id: str, session_id: uuid.UUID, user_id: Optional[str] = None) -> Optional[models.Session]: stmt = select(models.Session).where(models.Session.app_id == app_id).where(models.Session.id == session_id) @@ -14,8 +18,6 @@ def get_session(db: Session, app_id: str, session_id: uuid.UUID, user_id: Option stmt = stmt.where(models.Session.user_id == user_id) session = db.scalars(stmt).one_or_none() return session - # return db.query(models.Session).filter(models.Session.id == session_id).first() - def get_sessions( db: Session, app_id: str, user_id: str, location_id: str | None = None @@ -32,16 +34,15 @@ def get_sessions( stmt = stmt.where(models.Session.location_id == location_id) return stmt - # return db.scalars(stmt).all() def create_session( - db: Session, app_id: str, user_id: str, session: schemas.SessionCreate + db: Session, session: schemas.SessionCreate, app_id: str, user_id: str ) -> models.Session: honcho_session = models.Session( app_id=app_id, user_id=user_id, location_id=session.location_id, - session_data=json.dumps(session.session_data), + h_metadata=session.metadata, ) db.add(honcho_session) db.commit() @@ -49,16 +50,19 @@ def create_session( return honcho_session -def update_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID, session_data: dict) -> bool: +def update_session( + db: Session, session: schemas.SessionUpdate, app_id: str, user_id: str, session_id: uuid.UUID +) -> bool: honcho_session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) if honcho_session is None: raise ValueError("Session not found or does not belong to user") - honcho_session.session_data = json.dumps(session_data) + if session.metadata is not None: # Need to explicitly be there won't make it empty by default + honcho_session.h_metadata = session.metadata db.commit() db.refresh(honcho_session) + copy = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) return honcho_session - def delete_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID) -> bool: stmt = ( select(models.Session) @@ -73,7 +77,6 @@ def delete_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID db.commit() return True - def create_message( db: Session, message: schemas.MessageCreate, app_id: str, user_id: str, session_id: uuid.UUID ) -> models.Message: @@ -91,13 +94,9 @@ def create_message( db.refresh(honcho_message) return honcho_message - def get_messages( db: Session, app_id: str, user_id: str, session_id: uuid.UUID ) -> Select: - # session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) - # if session is None: - # raise ValueError("Session not found or does not belong to user") stmt = ( select(models.Message) .join(models.Session, models.Session.id == models.Message.session_id) @@ -107,19 +106,10 @@ def get_messages( .order_by(models.Message.created_at) ) return stmt - # return db.scalars(stmt).all() - # return ( - # db.query(models.Message) - # .filter(models.Message.session_id == session_id) - # .all() - # ) def get_message( db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID ) -> Optional[models.Message]: - # session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) - # if session is None: - # raise ValueError("Session not found or does not belong to user") stmt = ( select(models.Message) .join(models.Session, models.Session.id == models.Message.session_id) @@ -131,6 +121,9 @@ def get_message( ) return db.scalars(stmt).one_or_none() +######################################################## +# metamessage methods +######################################################## def get_metamessages(db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: Optional[uuid.UUID], metamessage_type: Optional[str] = None) -> Select: stmt = ( @@ -185,3 +178,211 @@ def create_metamessage( db.commit() db.refresh(honcho_metamessage) return honcho_metamessage + +######################################################## +# collection methods +######################################################## + +# Should be very similar to the session methods + +def get_collections(db: Session, app_id: str, user_id: str) -> Select: + """Get a distinct list of the names of collections associated with a user""" + stmt = ( + select(models.Collection) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .order_by(models.Collection.created_at) + ) + return stmt + +def get_collection_by_id(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID) -> Optional[models.Collection]: + stmt = ( + select(models.Collection) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Collection.id == collection_id) + ) + collection = db.scalars(stmt).one_or_none() + return collection + +def get_collection_by_name(db: Session, app_id: str, user_id: str, name: str) -> Optional[models.Collection]: + stmt = ( + select(models.Collection) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Collection.name == name) + ) + collection = db.scalars(stmt).one_or_none() + return collection + +def create_collection( + db: Session, collection: schemas.CollectionCreate, app_id: str, user_id: str +) -> models.Collection: + honcho_collection = models.Collection( + app_id=app_id, + user_id=user_id, + name=collection.name, + ) + try: + db.add(honcho_collection) + db.commit() + except IntegrityError: + db.rollback() + raise ValueError("Collection already exists") + db.refresh(honcho_collection) + return honcho_collection + +def update_collection( + db: Session, collection: schemas.CollectionUpdate, app_id: str, user_id: str, collection_id: uuid.UUID +) -> models.Collection: + honcho_collection = get_collection_by_id(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + if honcho_collection is None: + raise ValueError("collection not found or does not belong to user") + try: + honcho_collection.name = collection.name + db.commit() + except IntegrityError: + db.rollback() + raise ValueError("Collection already exists") + db.refresh(honcho_collection) + return honcho_collection + +def delete_collection( + db: Session, app_id: str, user_id: str, collection_id: uuid.UUID +) -> bool: + """ + Delete a Collection and all documents associated with it. Takes advantage of + the orm cascade feature + """ + stmt = ( + select(models.Collection) + .where(models.Collection.id == collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + ) + honcho_collection = db.scalars(stmt).one_or_none() + if honcho_collection is None: + return False + db.delete(honcho_collection) + db.commit() + return True + +######################################################## +# document methods +######################################################## + +# Should be similar to the messages methods outside of query + +def get_documents( + db: Session, app_id: str, user_id: str, collection_id: uuid.UUID +) -> Select: + stmt = ( + select(models.Document) + .join(models.Collection, models.Collection.id == models.Document.collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Document.collection_id == collection_id) + .order_by(models.Document.created_at) + ) + return stmt + +def get_document( + db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID +) -> Optional[models.Document]: + stmt = ( + select(models.Document) + .join(models.Collection, models.Collection.id == models.Document.collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Document.collection_id == collection_id) + .where(models.Document.id == document_id) + ) + + document = db.scalars(stmt).one_or_none() + return document + + +def query_documents(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, query: str, top_k: int = 5) -> Sequence[models.Document]: + response = openai_client.embeddings.create( + input=query, + model="text-embedding-3-small" + ) + embedding_query = response.data[0].embedding + stmt = ( + select(models.Document) + .join(models.Collection, models.Collection.id == models.Document.collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Document.collection_id == collection_id) + .order_by(models.Document.embedding.cosine_distance(embedding_query)) + .limit(top_k) + ) + # if metadata is not None: + # stmt = stmt.where(models.Document.h_metadata.contains(metadata)) + return db.scalars(stmt).all() + +def create_document( + db: Session, document: schemas.DocumentCreate, app_id: str, user_id: str, collection_id: uuid.UUID +) -> models.Document: + """Embed a message as a vector and create a document""" + collection = get_collection_by_id(db, app_id=app_id, collection_id=collection_id, user_id=user_id) + if collection is None: + raise ValueError("Session not found or does not belong to user") + + response = openai_client.embeddings.create( + input=document.content, + model="text-embedding-3-small" + ) + + embedding = response.data[0].embedding + + honcho_document = models.Document( + collection_id=collection_id, + content=document.content, + h_metadata=document.metadata, + embedding=embedding + ) + db.add(honcho_document) + db.commit() + db.refresh(honcho_document) + return honcho_document + +def update_document( + db: Session, document: schemas.DocumentUpdate, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID +) -> bool: + honcho_document = get_document(db, app_id=app_id, collection_id=collection_id, user_id=user_id, document_id=document_id) + if honcho_document is None: + raise ValueError("Session not found or does not belong to user") + if document.content is not None: + honcho_document.content = document.content + response = openai_client.embeddings.create( + input=document.content, + model="text-embedding-3-small" + ) + embedding = response.data[0].embedding + honcho_document.embedding = embedding + honcho_document.created_at = datetime.datetime.now() + + if document.metadata is not None: + honcho_document.h_metadata = document.metadata + db.commit() + db.refresh(honcho_document) + return honcho_document + +def delete_document(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID) -> bool: + stmt = ( + select(models.Document) + .join(models.Collection, models.Collection.id == models.Document.collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Document.collection_id == collection_id) + .where(models.Document.id == document_id) + ) + document = db.scalars(stmt).one_or_none() + if document is None: + return False + db.delete(document) + db.commit() + return True + + diff --git a/api/src/main.py b/api/src/main.py index c3ef0d8..7a16e9c 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,6 +1,6 @@ import uuid from fastapi import Depends, FastAPI, HTTPException, APIRouter, Request -from typing import Optional +from typing import Optional, Sequence from sqlalchemy.orm import Session from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.middleware import SlowAPIMiddleware @@ -9,7 +9,6 @@ from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.sqlalchemy import paginate -# import uvicorn from . import crud, models, schemas from .db import SessionLocal, engine @@ -44,7 +43,13 @@ def get_db(): ######################################################## @router.get("/sessions", response_model=Page[schemas.Session]) -def get_sessions(request: Request, app_id: str, user_id: str, location_id: Optional[str] = None, db: Session = Depends(get_db)): +def get_sessions( + request: Request, + app_id: str, + user_id: str, + location_id: Optional[str] = None, + db: Session = Depends(get_db) +): """Get All Sessions for a User Args: @@ -56,11 +61,7 @@ def get_sessions(request: Request, app_id: str, user_id: str, location_id: Optio list[schemas.Session]: List of Session objects """ - # if location_id is not None: - # return paginate(db, crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id)) - # return crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id) return paginate(db, crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id)) - # return crud.get_sessions(db, app_id=app_id, user_id=user_id) @router.post("/sessions", response_model=schemas.Session) @@ -102,10 +103,10 @@ def update_session( schemas.Session: The Session object of the updated Session """ - if session.session_data is None: - raise HTTPException(status_code=400, detail="Session data cannot be empty") # TODO TEST if I can set the metadata to be blank with this + if session.metadata is None: + raise HTTPException(status_code=400, detail="Session metadata cannot be empty") # TODO TEST if I can set the metadata to be blank with this try: - return crud.update_session(db, app_id=app_id, user_id=user_id, session_id=session_id, session_data=session.session_data) + return crud.update_session(db, app_id=app_id, user_id=user_id, session_id=session_id, session=session) except ValueError: raise HTTPException(status_code=404, detail="Session not found") @@ -243,10 +244,8 @@ def get_message( raise HTTPException(status_code=404, detail="Session not found") return honcho_message - - ######################################################## -# Metacognition Routes +# metamessage routes ######################################################## @router.post( @@ -276,9 +275,6 @@ def create_metamessage( HTTPException: If the session is not found """ - print("=======================") - print(request) - print("=======================") try: return crud.create_metamessage(db, metamessage=metamessage, app_id=app_id, user_id=user_id, session_id=session_id) except ValueError: @@ -336,5 +332,176 @@ def get_metamessage(request: Request, app_id: str, user_id: str, session_id: uui raise HTTPException(status_code=404, detail="Session not found") return honcho_metamessage +######################################################## +# collection routes +######################################################## + +@router.get("/collections/all", response_model=Page[schemas.Collection]) +def get_collections( + request: Request, + app_id: str, + user_id: str, + db: Session = Depends(get_db), +): + return paginate(db, crud.get_collections(db, app_id=app_id, user_id=user_id)) + +@router.get("/collections/id/{collection_id}", response_model=schemas.Collection) +def get_collection_by_id( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + db: Session = Depends(get_db) +) -> schemas.Collection: + honcho_collection = crud.get_collection_by_id(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + if honcho_collection is None: + raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + return honcho_collection + +@router.get("/collections/name/{name}", response_model=schemas.Collection) +def get_collection_by_name( + request: Request, + app_id: str, + user_id: str, + name: str, + db: Session = Depends(get_db) +) -> schemas.Collection: + honcho_collection = crud.get_collection_by_name(db, app_id=app_id, user_id=user_id, name=name) + if honcho_collection is None: + raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + return honcho_collection + +@router.post("/collections", response_model=schemas.Collection) +def create_collection( + request: Request, + app_id: str, + user_id: str, + collection: schemas.CollectionCreate, + db: Session = Depends(get_db) +): + try: + return crud.create_collection(db, collection=collection, app_id=app_id, user_id=user_id) + except ValueError: + raise HTTPException(status_code=406, detail="Error invalid collection configuration - name may already exist") + +@router.put("/collections/{collection_id}", response_model=schemas.Collection) +def update_collection( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + collection: schemas.CollectionUpdate, + db: Session = Depends(get_db) +): + if collection.name is None: + raise HTTPException(status_code=400, detail="invalid request - name cannot be None") + try: + honcho_collection = crud.update_collection(db, collection=collection, app_id=app_id, user_id=user_id, collection_id=collection_id) + except ValueError: + raise HTTPException(status_code=406, detail="Error invalid collection configuration - name may already exist") + return honcho_collection + +@router.delete("/collections/{collection_id}") +def delete_collection( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + db: Session = Depends(get_db) +): + response = crud.delete_collection(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + if response: + return {"message": "Collection deleted successfully"} + else: + raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + +######################################################## +# Document routes +######################################################## + +@router.get("/collections/{collection_id}/documents", response_model=Page[schemas.Document]) +def get_documents( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + db: Session = Depends(get_db) +): + try: + return paginate(db, crud.get_documents(db, app_id=app_id, user_id=user_id, collection_id=collection_id)) + except ValueError: # TODO can probably remove this exception ok to return empty here + raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + +router.get("/collections/{collection_id}/documents/{document_id}", response_model=schemas.Document) +def get_document( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, + db: Session = Depends(get_db) +): + honcho_document = crud.get_document(db, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + if honcho_document is None: + raise HTTPException(status_code=404, detail="document not found or does not belong to user") + return honcho_document + + +@router.get("/collections/{collection_id}/query", response_model=Sequence[schemas.Document]) +def query_documents( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + query: str, + top_k: int = 5, + db: Session = Depends(get_db) +): + if top_k is not None and top_k > 50: + top_k = 50 # TODO see if we need to paginate this + return crud.query_documents(db=db, app_id=app_id, user_id=user_id, collection_id=collection_id, query=query, top_k=top_k) + +@router.post("/collections/{collection_id}/documents", response_model=schemas.Document) +def create_document( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document: schemas.DocumentCreate, + db: Session = Depends(get_db) +): + try: + return crud.create_document(db, document=document, app_id=app_id, user_id=user_id, collection_id=collection_id) + except ValueError: + raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + +@router.put("/collections/{collection_id}/documents/{document_id}", response_model=schemas.Document) +def update_document( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, + document: schemas.DocumentUpdate, + db: Session = Depends(get_db) +): + if document.content is None and document.metadata is None: + raise HTTPException(status_code=400, detail="content and metadata cannot both be None") + return crud.update_document(db, document=document, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + +@router.delete("/collections/{collection_id}/documents/{document_id}") +def delete_document( + request: Request, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, + db: Session = Depends(get_db) +): + response = crud.delete_document(db, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + if response: + return {"message": "Document deleted successfully"} + else: + raise HTTPException(status_code=404, detail="document not found or does not belong to user") app.include_router(router) diff --git a/api/src/models.py b/api/src/models.py index 4371229..ea4b86b 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -1,10 +1,20 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, Uuid -import uuid import datetime -from sqlalchemy.orm import relationship, Mapped, mapped_column +import os +import uuid + +from dotenv import load_dotenv +from pgvector.sqlalchemy import Vector +from sqlalchemy import JSON, Column, ForeignKey, String, UniqueConstraint, Uuid +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import Mapped, mapped_column, relationship from .db import Base +load_dotenv() + +DATABASE_TYPE = os.getenv("DATABASE_TYPE", 'postgres') + +ColumnType = JSONB if DATABASE_TYPE == 'postgres' else JSON class Session(Base): __tablename__ = "sessions" @@ -13,13 +23,12 @@ class Session(Base): user_id: Mapped[str] = mapped_column(String(512), index=True) location_id: Mapped[str] = mapped_column(String(512), index=True) is_active: Mapped[bool] = mapped_column(default=True) - session_data: Mapped[str] + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) messages = relationship("Message", back_populates="session") def __repr__(self) -> str: - return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at})" - + return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})" class Message(Base): __tablename__ = "messages" @@ -34,7 +43,6 @@ class Message(Base): def __repr__(self) -> str: return f"Message(id={self.id}, session_id={self.session_id}, is_user={self.is_user}, content={self.content[10:]})" - class Metamessage(Base): __tablename__ = "metamessages" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) @@ -47,3 +55,27 @@ class Metamessage(Base): def __repr__(self) -> str: return f"Metamessages(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content[10:]})" + +class Collection(Base): + __tablename__ = "collections" + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(512), index=True) + app_id: Mapped[str] = mapped_column(String(512), index=True) + user_id: Mapped[str] = mapped_column(String(512), index=True) + created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) + documents = relationship("Document", back_populates="collection", cascade="all, delete, delete-orphan") + + __table_args__ = ( + UniqueConstraint('name', 'app_id', 'user_id', name="unique_name_app_user"), + ) + +class Document(Base): + __tablename__ = "documents" + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) + content: Mapped[str] = mapped_column(String(65535)) + embedding = mapped_column(Vector(1536)) + created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) + + collection_id = Column(Uuid, ForeignKey("collections.id")) + collection = relationship("Collection", back_populates="documents") diff --git a/api/src/schemas.py b/api/src/schemas.py index b6bff90..fe164aa 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, validator import datetime import uuid @@ -18,8 +18,7 @@ class Message(MessageBase): created_at: datetime.datetime class Config: - orm_mode = True - + from_attributes = True class SessionBase(BaseModel): pass @@ -27,12 +26,10 @@ class SessionBase(BaseModel): class SessionCreate(SessionBase): location_id: str - session_data: dict | None = None - - + metadata: dict | None = {} + class SessionUpdate(SessionBase): - session_data: dict | None = None - + metadata: dict | None = None class Session(SessionBase): id: uuid.UUID @@ -41,11 +38,21 @@ class Session(SessionBase): user_id: str location_id: str app_id: str - session_data: str + h_metadata: dict + metadata: dict created_at: datetime.datetime + @validator('metadata', pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if 'h_metadata' in values: + return values['h_metadata'] + return {} + class Config: - orm_mode = True + from_attributes = True + schema_extra = { + "exclude": ["h_metadata"] + } class MetamessageBase(BaseModel): @@ -64,3 +71,53 @@ class Metamessage(MetamessageBase): class Config: orm_mode = True + +class CollectionBase(BaseModel): + pass + +class CollectionCreate(CollectionBase): + name: str + +class CollectionUpdate(CollectionBase): + name: str + +class Collection(CollectionBase): + id: uuid.UUID + name: str + app_id: str + user_id: str + created_at: datetime.datetime + + class Config: + orm_mode = True + +class DocumentBase(BaseModel): + content: str + +class DocumentCreate(DocumentBase): + metadata: dict | None = {} + +class DocumentUpdate(DocumentBase): + metadata: dict | None = None + content: str | None = None + +class Document(DocumentBase): + id: uuid.UUID + content: str + h_metadata: dict + metadata: dict + created_at: datetime.datetime + collection_id: uuid.UUID + + @validator('metadata', pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if 'h_metadata' in values: + return values['h_metadata'] + return {} + + class Config: + from_attributes = True + schema_extra = { + "exclude": ["h_metadata"] + } + diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 1d8ecd5..54965e4 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.3] — 2024-02-15 + +### Added + +* Collections table to reference a collection of embedding documents +* Documents table to hold vector embeddings for RAG workflows +* Local scripts for running a postgres database with pgvector installed +* OpenAI Dependency for embedding models +* PGvector dependency for vector db support + +### Changed + +* session_data is now metadata +* session_data is a JSON field used python `dict` for compatability + ## [0.0.2] — 2024-02-08 ### Added diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index e87b439..eda9003 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,4 +1,4 @@ -from .client import AsyncClient, AsyncSession, AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage -from .sync_client import Client, Session, GetSessionPage, GetMessagePage, GetMetamessagePage -from .schemas import Message, Metamessage +from .client import AsyncClient, AsyncSession, AsyncCollection, AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncGetDocumentPage, AsyncGetCollectionPage +from .sync_client import Client, Session, Collection, GetSessionPage, GetMessagePage, GetMetamessagePage, GetDocumentPage, GetCollectionPage +from .schemas import Message, Metamessage, Document from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index c5a1373..507bfba 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,8 +1,8 @@ -import json import uuid -from typing import Dict, Optional +import datetime +from typing import Dict, Optional, List import httpx -from .schemas import Message, Metamessage +from .schemas import Message, Metamessage, Document class AsyncGetPage: """Base class for receiving Paginated API results""" @@ -44,7 +44,8 @@ def __init__(self, client, options: Dict, response: Dict): user_id=session["user_id"], location_id=session["location_id"], is_active=session["is_active"], - session_data=session["session_data"], + metadata=session["metadata"], + created_at=session["created_at"], ) for session in response["items"] ] @@ -97,13 +98,13 @@ def __init__(self, session, options: Dict, response: Dict) -> None: Args: session (AsyncSession): Session the returned messages are associated with - options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both required + options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both optional response (Dict): Response from API with pagination information """ super().__init__(response) self.session = session - self.message_id = options["message_id"] - self.metamessage_type = options["metamessage_type"] + self.message_id = options["message_id"] if "message_id" in options else None + self.metamessage_type = options["metamessage_type"] if "metamessage_type" in options else None self.items = [ Metamessage( id=metamessage["id"], @@ -124,7 +125,70 @@ async def next(self): return None return await self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) +class AsyncGetDocumentPage(AsyncGetPage): + """Paginated results for Get Document requests""" + def __init__(self, collection, response: Dict) -> None: + """Constructor for Page Result from Document Get Request + + Args: + collection (AsyncCollection): Collection the returned documents are associated with + response (Dict): Response from API with pagination information + """ + super().__init__(response) + self.collection = collection + self.items = [ + Document( + id=document["id"], + collection_id=collection.id, + content=document["content"], + metadata=document["metadata"], + created_at=document["created_at"], + ) + for document in response["items"] + ] + async def next(self): + """Get the next page of results + Returns: + AsyncGetDocumentPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ + if self.page >= self.pages: + return None + return await self.collection.get_documents(page=self.page + 1, page_size=self.page_size) + +class AsyncGetCollectionPage(AsyncGetPage): + """Paginated results for Get Collection requests""" + + def __init__(self, client, options: Dict, response: Dict): + """Constructor for page result from Get Collection Request + + Args: + client (Async Client): Honcho Client + options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required + response (Dict): Response from API with pagination information + """ + super().__init__(response) + self.client = client + self.user_id = options["user_id"] + self.items = [ + AsyncCollection( + client=client, + id=collection["id"], + user_id=collection["user_id"], + name=collection["name"], + created_at=collection["created_at"], + ) + for collection in response["items"] + ] + + async def next(self): + """Get the next page of results + Returns: + AsyncGetCollectionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ + if self.page >= self.pages: + return None + return await self.client.get_collections(user_id=self.user_id, page=self.page + 1, page_size=self.page_size) class AsyncClient: """Honcho API Client Object""" @@ -161,7 +225,8 @@ async def get_session(self, user_id: str, session_id: uuid.UUID): user_id=data["user_id"], location_id=data["location_id"], is_active=data["is_active"], - session_data=data["session_data"], + metadata=data["metadata"], + created_at=data["created_at"] ) async def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): @@ -215,20 +280,20 @@ async def get_sessions_generator(self, user_id: str, location_id: Optional[str] get_session_response = new_sessions async def create_session( - self, user_id: str, location_id: str = "default", session_data: Dict = {} + self, user_id: str, location_id: str = "default", metadata: Dict = {} ): """Create a session for a user Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session - session_data (Dict, optional): Optional session metadata + metadata (Dict, optional): Optional session metadata Returns: AsyncSession: The Session object of the new Session """ - data = {"location_id": location_id, "session_data": session_data} + data = {"location_id": location_id, "metadata": metadata} url = f"{self.common_prefix}/users/{user_id}/sessions" response = await self.client.post(url, json=data) response.raise_for_status() @@ -238,10 +303,103 @@ async def create_session( id=data["id"], user_id=user_id, location_id=location_id, - session_data=session_data, + metadata=metadata, is_active=data["is_active"], + created_at=data["created_at"], ) + async def create_collection( + self, user_id: str, name: str, + ): + """Create a collection for a user + + Args: + user_id (str): The User ID representing the user, managed by the user + name (str): unique name for the collection for the user + + Returns: + AsyncCollection: The Collection object of the new Collection + + """ + data = {"name": name} + url = f"{self.common_prefix}/users/{user_id}/collections" + response = await self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return AsyncCollection( + self, + id=data["id"], + user_id=user_id, + name=name, + created_at=data["created_at"], + ) + + async def get_collection(self, user_id: str, name: str): + """Get a specific collection for a user by name + + Args: + user_id (str): The User ID representing the user, managed by the user + name (str): The name of the collection to get + + Returns: + AsyncCollection: The Session object of the requested Session + + """ + url = f"{self.common_prefix}/users/{user_id}/collections/name/{name}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncCollection( + client=self, + id=data["id"], + user_id=data["user_id"], + name=data["name"], + created_at=data["created_at"] + ) + + async def get_collections(self, user_id: str, page: int = 1, page_size: int = 50): + """Return collections associated with a user paginated + + Args: + user_id (str): The User ID representing the user to get the collection for + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return + + Returns: + AsyncGetCollectionPage: Page or results for get_collections query + + """ + url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + options = {"user_id": user_id} + return AsyncGetCollectionPage(self, options, data) + + async def get_collections_generator(self, user_id: str): + """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + + Args: + user_id (str): The User ID representing the user, managed by the user + + Yields: + AsyncCollection: The Session object of the requested Session + + """ + page = 1 + page_size = 50 + get_collection_response = await self.get_collections(user_id, page, page_size) + while True: + # get_collection_response = self.get_collections(user_id, location_id, page, page_size) + for collection in get_collection_response.items: + yield collection + + new_collections = await get_collection_response.next() + if not new_collections: + break + + get_collection_response = new_collections + class AsyncSession: """Represents a single session for a user in an app""" @@ -252,20 +410,20 @@ def __init__( id: uuid.UUID, user_id: str, location_id: str, - session_data: dict | str, + metadata: dict, is_active: bool, + created_at: datetime.datetime ): """Constructor for Session""" - self.base_url = client.base_url - self.client = client.client - self.app_id = client.app_id - self.id = id - self.user_id = user_id - self.location_id = location_id - self.session_data = ( - session_data if isinstance(session_data, dict) else json.loads(session_data) - ) - self._is_active = is_active + self.base_url: str = client.base_url + self.client: httpx.AsyncClient = client.client + self.app_id: str = client.app_id + self.id: uuid.UUID = id + self.user_id: str = user_id + self.location_id: str = location_id + self.metadata: dict = metadata + self._is_active: bool = is_active + self.created_at: datetime.datetime = created_at @property def common_prefix(self): @@ -274,7 +432,7 @@ def common_prefix(self): def __str__(self): """String representation of Session""" - return f"AsyncSession(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" + return f"AsyncSession(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" @property def is_active(self): @@ -360,7 +518,7 @@ async def create_metamessage(self, message: Message, metamessage_type: str, cont Args: message (Message): A message to associate the metamessage with - metamessage_type (str): The type of the metamessage arbitrary itentifier + metamessage_type (str): The type of the metamessage arbitrary identifier content (str): The content of the metamessage Returns: @@ -444,21 +602,21 @@ async def get_metamessages_generator(self, metamessage_type: Optional[str] = Non get_metamessages_page = new_messages - async def update(self, session_data: Dict): - """Update the session_data of a session + async def update(self, metadata: Dict): + """Update the metadata of a session Args: - session_data (Dict): The Session object containing any new session_data + metadata (Dict): The Session object containing any new metadata Returns: boolean: Whether the session was successfully updated """ - info = {"session_data": session_data} + info = {"metadata": metadata} url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = await self.client.put(url, json=info) success = response.status_code < 400 - self.session_data = session_data + self.metadata = metadata return success async def close(self): @@ -468,3 +626,202 @@ async def close(self): response.raise_for_status() self._is_active = False +class AsyncCollection: + """Represents a single collection for a user in an app""" + + def __init__( + self, + client: AsyncClient, + id: uuid.UUID, + user_id: str, + name: str, + created_at: datetime.datetime, + ): + """Constructor for Collection""" + self.base_url: str = client.base_url + self.client: httpx.AsyncClient = client.client + self.app_id: str = client.app_id + self.id: uuid.UUID = id + self.user_id: str = user_id + self.name: str = name + self.created_at: datetime.datetime = created_at + + @property + def common_prefix(self): + """Shortcut for common API prefix. made a property to prevent tampering""" + return f"{self.base_url}/apps/{self.app_id}" + + def __str__(self): + """String representation of Collection""" + return f"AsyncCollection(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, name={self.name}, created_at={self.created_at})" + + async def update(self, name: str): + """Update the name of the collection + + Args: + name (str): The new name of the document + + Returns: + boolean: Whether the session was successfully updated + """ + info = {"name": name} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" + response = await self.client.put(url, json=info) + response.raise_for_status() + success = response.status_code < 400 + self.name = name + return success + + async def delete(self): + """Delete a collection and all associated documents""" + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" + response = await self.client.delete(url) + response.raise_for_status() + + async def create_document(self, content: str, metadata: Dict = {}): + """Adds a document to the collection + + Args: + content (str): The content of the document + metadata (Dict): The metadata of the document + + Returns: + Document: The Document object of the added document + + """ + data = {"metadata": metadata, "content": content} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + response = await self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Document( + collection_id=self.id, + id=data["id"], + metadata=metadata, + content=content, + created_at=data["created_at"] + ) + + async def get_document(self, document_id: uuid.UUID) -> Document: + """Get a specific document for a collection based on ID + + Args: + document_id (uuid.UUID): The ID of the Document to retrieve + + Returns: + Document: The Document object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document_id}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return Document( + collection_id=self.id, + id=data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"] + ) + + async def get_documents(self, page: int = 1, page_size: int = 50) -> AsyncGetDocumentPage: + """Get all documents for a collection + + Args: + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page + + Returns: + AsyncGetDocumentPage: Page of Document objects + + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncGetDocumentPage(self, data) + + async def get_documents_generator(self): + """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + + Yields: + Document: The Document object of the next Document + + """ + page = 1 + page_size = 50 + get_documents_page= await self.get_documents(page, page_size) + while True: + for document in get_documents_page.items: + yield document + + new_documents = await get_documents_page.next() + if not new_documents: + break + + get_documents_page = new_documents + + async def query(self, query: str, top_k: int = 5) -> List[Document]: + """query the documents by cosine distance + Args: + query (str): The query string to compare other embeddings too + top_k (int, optional): The number of results to return. Defaults to 5 max 50 + + Returns: + List[Document]: The response from the query with matching documents + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/query?query={query}&top_k={top_k}" + response = await self.client.get(url) + response.raise_for_status() + data = [ + Document( + collection_id=self.id, + content=document["content"], + id=document["id"], + created_at=document["created_at"], + metadata=document["metadata"] + ) + for document in response.json() + ] + return data + + async def update_document(self, document: Document, content: Optional[str], metadata: Optional[Dict]) -> Document: + """Update a document in the collection + + Args: + document (Document): The Document to update + metadata (Dict): The metadata of the document + content (str): The content of the document + + Returns: + Document: The newly updated Document + """ + if metadata is None and content is None: + raise ValueError("metadata and content cannot both be None") + data = {"metadata": metadata, "content": content} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" + response = await self.client.put(url, json=data) + response.raise_for_status() + data = response.json() + return Document( + data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"], + collection_id=data["collection_id"], + ) + + async def delete_document(self, document: Document) -> bool: + """Delete a document from the collection + + Args: + document (Document): The Document to delete + + Returns: + boolean: Whether the document was successfully deleted + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" + response = await self.client.delete(url) + response.raise_for_status() + success = response.status_code < 400 + return success diff --git a/sdk/honcho/schemas.py b/sdk/honcho/schemas.py index d2970c9..ee743cb 100644 --- a/sdk/honcho/schemas.py +++ b/sdk/honcho/schemas.py @@ -24,3 +24,15 @@ def __init__(self, id: uuid.UUID, message_id: uuid.UUID, metamessage_type: str, def __str__(self): return f"Metamessage(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content})" + +class Document: + def __init__(self, id: uuid.UUID, collection_id: uuid.UUID, content: str, metadata: dict, created_at: datetime.datetime): + """Constructor for Document""" + self.collection_id = collection_id + self.id = id + self.content = content + self.metadata = metadata + self.created_at = created_at + + def __str__(self) -> str: + return f"Document(id={self.id}, metadata={self.metadata}, content={self.content}, created_at={self.created_at})" diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 2adc4e4..b68b95d 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1,8 +1,8 @@ -import json import uuid -from typing import Dict, Optional +import datetime +from typing import Dict, Optional, List import httpx -from .schemas import Message, Metamessage +from .schemas import Message, Metamessage, Document class GetPage: """Base class for receiving Paginated API results""" @@ -44,7 +44,8 @@ def __init__(self, client, options: Dict, response: Dict): user_id=session["user_id"], location_id=session["location_id"], is_active=session["is_active"], - session_data=session["session_data"], + metadata=session["metadata"], + created_at=session["created_at"], ) for session in response["items"] ] @@ -124,7 +125,70 @@ def next(self): return None return self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) +class GetDocumentPage(GetPage): + """Paginated results for Get Document requests""" + def __init__(self, collection, response: Dict) -> None: + """Constructor for Page Result from Document Get Request + + Args: + collection (Collection): Collection the returned documents are associated with + response (Dict): Response from API with pagination information + """ + super().__init__(response) + self.collection = collection + self.items = [ + Document( + id=document["id"], + collection_id=collection.id, + content=document["content"], + metadata=document["metadata"], + created_at=document["created_at"], + ) + for document in response["items"] + ] + def next(self): + """Get the next page of results + Returns: + GetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ + if self.page >= self.pages: + return None + return self.collection.get_documents(page=self.page + 1, page_size=self.page_size) + +class GetCollectionPage(GetPage): + """Paginated results for Get Collection requests""" + + def __init__(self, client, options: Dict, response: Dict): + """Constructor for page result from Get Collection Request + + Args: + client ( Client): Honcho Client + options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required + response (Dict): Response from API with pagination information + """ + super().__init__(response) + self.client = client + self.user_id = options["user_id"] + self.items = [ + Collection( + client=client, + id=collection["id"], + user_id=collection["user_id"], + name=collection["name"], + created_at=collection["created_at"], + ) + for collection in response["items"] + ] + + def next(self): + """Get the next page of results + Returns: + GetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + """ + if self.page >= self.pages: + return None + return self.client.get_collections(user_id=self.user_id, page=self.page + 1, page_size=self.page_size) class Client: """Honcho API Client Object""" @@ -161,7 +225,8 @@ def get_session(self, user_id: str, session_id: uuid.UUID): user_id=data["user_id"], location_id=data["location_id"], is_active=data["is_active"], - session_data=data["session_data"], + metadata=data["metadata"], + created_at=data["created_at"] ) def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): @@ -215,20 +280,20 @@ def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None get_session_response = new_sessions def create_session( - self, user_id: str, location_id: str = "default", session_data: Dict = {} + self, user_id: str, location_id: str = "default", metadata: Dict = {} ): """Create a session for a user Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session - session_data (Dict, optional): Optional session metadata + metadata (Dict, optional): Optional session metadata Returns: Session: The Session object of the new Session """ - data = {"location_id": location_id, "session_data": session_data} + data = {"location_id": location_id, "metadata": metadata} url = f"{self.common_prefix}/users/{user_id}/sessions" response = self.client.post(url, json=data) response.raise_for_status() @@ -238,10 +303,103 @@ def create_session( id=data["id"], user_id=user_id, location_id=location_id, - session_data=session_data, + metadata=metadata, is_active=data["is_active"], + created_at=data["created_at"], + ) + + def create_collection( + self, user_id, name: str, + ): + """Create a collection for a user + + Args: + user_id (str): The User ID representing the user, managed by the user + name (str): unique name for the collection for the user + + Returns: + Collection: The Collection object of the new Collection + + """ + data = {"name": name} + url = f"{self.common_prefix}/users/{user_id}/collections" + response = self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Collection( + self, + id=data["id"], + user_id=user_id, + name=name, + created_at=data["created_at"], ) + def get_collection(self, user_id: str, name: str): + """Get a specific collection for a user by ID + + Args: + user_id (str): The User ID representing the user, managed by the user + name (str): The name of the collection to get + + Returns: + Collection: The Session object of the requested Session + + """ + url = f"{self.common_prefix}/users/{user_id}/collections/name/{name}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return Collection( + client=self, + id=data["id"], + user_id=data["user_id"], + name=data["name"], + created_at=data["created_at"] + ) + + def get_collections(self, user_id: str, page: int = 1, page_size: int = 50): + """Return collections associated with a user paginated + + Args: + user_id (str): The User ID representing the user + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return + + Returns: + GetCollectionPage: Page or results for get_collections query + + """ + url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + options = {"user_id": user_id} + return GetCollectionPage(self, options, data) + + def get_collections_generator(self, user_id: str): + """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + + Args: + user_id (str): The User ID representing the user, managed by the user + + Yields: + Collection: The Session object of the requested Session + + """ + page = 1 + page_size = 50 + get_collection_response = self.get_collections(user_id, page, page_size) + while True: + # get_collection_response = self.get_collections(user_id, location_id, page, page_size) + for collection in get_collection_response.items: + yield collection + + new_collections = get_collection_response.next() + if not new_collections: + break + + get_collection_response = new_collections + class Session: """Represents a single session for a user in an app""" @@ -252,20 +410,20 @@ def __init__( id: uuid.UUID, user_id: str, location_id: str, - session_data: dict | str, + metadata: dict, is_active: bool, + created_at ): """Constructor for Session""" - self.base_url = client.base_url - self.client = client.client - self.app_id = client.app_id - self.id = id - self.user_id = user_id - self.location_id = location_id - self.session_data = ( - session_data if isinstance(session_data, dict) else json.loads(session_data) - ) - self._is_active = is_active + self.base_url: str = client.base_url + self.client: httpx.Client = client.client + self.app_id: str = client.app_id + self.id: uuid.UUID = id + self.user_id: str = user_id + self.location_id: str = location_id + self.metadata: dict = metadata + self._is_active: bool = is_active + self.created_at: datetime.datetime = created_at @property def common_prefix(self): @@ -274,7 +432,7 @@ def common_prefix(self): def __str__(self): """String representation of Session""" - return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" + return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" @property def is_active(self): @@ -444,21 +602,21 @@ def get_metamessages_generator(self, metamessage_type: Optional[str] = None, mes get_metamessages_page = new_messages - def update(self, session_data: Dict): - """Update the session_data of a session + def update(self, metadata: Dict): + """Update the metadata of a session Args: - session_data (Dict): The Session object containing any new session_data + metadata (Dict): The Session object containing any new metadata Returns: boolean: Whether the session was successfully updated """ - info = {"session_data": session_data} + info = {"metadata": metadata} url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" response = self.client.put(url, json=info) success = response.status_code < 400 - self.session_data = session_data + self.metadata = metadata return success def close(self): @@ -468,3 +626,202 @@ def close(self): response.raise_for_status() self._is_active = False +class Collection: + """Represents a single collection for a user in an app""" + + def __init__( + self, + client: Client, + id: uuid.UUID, + user_id: str, + name: str, + created_at: datetime.datetime, + ): + """Constructor for Collection""" + self.base_url: str = client.base_url + self.client: httpx.Client = client.client + self.app_id: str = client.app_id + self.id: uuid.UUID = id + self.user_id: str = user_id + self.name: str = name + self.created_at: datetime.datetime = created_at + + @property + def common_prefix(self): + """Shortcut for common API prefix. made a property to prevent tampering""" + return f"{self.base_url}/apps/{self.app_id}" + + def __str__(self): + """String representation of Collection""" + return f"Collection(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, name={self.name}, created_at={self.created_at})" + + def update(self, name: str): + """Update the name of the collection + + Args: + name (str): The new name of the document + + Returns: + boolean: Whether the session was successfully updated + """ + info = {"name": name} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" + response = self.client.put(url, json=info) + response.raise_for_status() + success = response.status_code < 400 + self.name = name + return success + + def delete(self): + """Delete a collection and all associated documents""" + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" + response = self.client.delete(url) + response.raise_for_status() + + def create_document(self, content: str, metadata: Dict = {}): + """Adds a document to the collection + + Args: + metadata (Dict): The metadata of the document + content (str): The content of the document + + Returns: + Document: The Document object of the added document + + """ + data = {"metadata": metadata, "content": content} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + response = self.client.post(url, json=data) + response.raise_for_status() + data = response.json() + return Document( + collection_id=self.id, + id=data["id"], + metadata=metadata, + content=content, + created_at=data["created_at"] + ) + + def get_document(self, document_id: uuid.UUID) -> Document: + """Get a specific document for a collection based on ID + + Args: + document_id (uuid.UUID): The ID of the Document to retrieve + + Returns: + Document: The Document object + + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document_id}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return Document( + collection_id=self.id, + id=data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"] + ) + + def get_documents(self, page: int = 1, page_size: int = 50) -> GetDocumentPage: + """Get all documents for a collection + + Args: + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page + + Returns: + GetDocumentPage: Page of Document objects + + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return GetDocumentPage(self, data) + + def get_documents_generator(self): + """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + + Yields: + Document: The Document object of the next Document + + """ + page = 1 + page_size = 50 + get_documents_page= self.get_documents(page, page_size) + while True: + for document in get_documents_page.items: + yield document + + new_documents = get_documents_page.next() + if not new_documents: + break + + get_documents_page = new_documents + + def query(self, query: str, top_k: int = 5) -> List[Document]: + """query the documents by cosine distance + Args: + query (str): The query to run + top_k (int, optional): The number of results to return. Defaults to 5. + + Returns: + List[Document]: The response from the query with matching documents + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/query?query={query}&top_k={top_k}" + response = self.client.get(url) + response.raise_for_status() + data = [ + Document( + collection_id=self.id, + content=document["content"], + id=document["id"], + created_at=document["created_at"], + metadata=document["metadata"] + ) + for document in response.json() + ] + return data + + def update_document(self, document: Document, metadata: Optional[Dict], content: Optional[str]) -> Document: + """Update a document in the collection + + Args: + document (Document): The Document to update + metadata (Dict): The metadata of the document + content (str): The content of the document + + Returns: + Document: The newly updated Document + """ + if metadata is None and content is None: + raise ValueError("metadata and content cannot both be None") + data = {"metadata": metadata, "content": content} + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" + response = self.client.put(url, json=data) + response.raise_for_status() + data = response.json() + return Document( + data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"], + collection_id=data["collection_id"], + ) + + def delete_document(self, document: Document) -> bool: + """Delete a document from the collection + + Args: + document (Document): The Document to delete + + Returns: + boolean: Whether the document was successfully deleted + """ + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" + response = self.client.delete(url) + response.raise_for_status() + success = response.status_code < 400 + return success diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index ff695b5..1455bca 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.2" +version = "0.0.3" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0" diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index 8e5904d..edd4191 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -1,5 +1,5 @@ import pytest -from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncSession, Message, Metamessage +from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncGetDocumentPage, AsyncSession, Message, Metamessage, Document from honcho import AsyncClient as Honcho from uuid import uuid1 @@ -14,7 +14,7 @@ async def test_session_creation_retrieval(): assert retrieved_session.id == created_session.id assert retrieved_session.is_active is True assert retrieved_session.location_id == "default" - assert retrieved_session.session_data == {} + assert retrieved_session.metadata == {} @pytest.mark.asyncio @@ -40,7 +40,7 @@ async def test_session_update(): created_session = await client.create_session(user_id) assert await created_session.update({"foo": "bar"}) retrieved_session = await client.get_session(user_id, created_session.id) - assert retrieved_session.session_data == {"foo": "bar"} + assert retrieved_session.metadata == {"foo": "bar"} @pytest.mark.asyncio @@ -271,4 +271,97 @@ async def test_paginated_metamessages_generator(): await gen.__anext__() +@pytest.mark.asyncio +async def test_collections(): + col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = await client.create_collection(user_id, col_name) + + # Add documents + doc1 = await collection.create_document(content="This is a test of documents - 1", metadata={"foo": "bar"}) + doc2 = await collection.create_document(content="This is a test of documents - 2", metadata={}) + doc3 = await collection.create_document(content="This is a test of documents - 3", metadata={}) + + # Get all documents + page = await collection.get_documents(page=1, page_size=3) + # Verify size + assert page is not None + assert isinstance(page, AsyncGetDocumentPage) + assert len(page.items) == 3 + # delete a doc + result = await collection.delete_document(doc1) + assert result is True + # Get all documents with a generator this time + gen = collection.get_documents_generator() + # Verfy size + item = await gen.__anext__() + item2 = await gen.__anext__() + with pytest.raises(StopAsyncIteration): + await gen.__anext__() + # delete the collection + result = await collection.delete() + # confirm documents are gone + with pytest.raises(Exception): + new_col = await client.get_collection(user_id, "test") + +@pytest.mark.asyncio +async def test_collection_name_collision(): + col_name = str(uuid1()) + new_col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = await client.create_collection(user_id, col_name) + # Make another collection + with pytest.raises(Exception): + await client.create_collection(user_id, col_name) + + # Change the name of original collection + result = await collection.update(new_col_name) + assert result is True + + # Try again to add another collection + collection2 = await client.create_collection(user_id, col_name) + assert collection2 is not None + assert collection2.name == col_name + assert collection.name == new_col_name + + # Get all collections + page = await client.get_collections(user_id) + assert page is not None + assert len(page.items) == 2 + +@pytest.mark.asyncio +async def test_collection_query(): + col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = await client.create_collection(user_id, col_name) + + # Add documents + doc1 = await collection.create_document(content="The user loves puppies", metadata={}) + doc2 = await collection.create_document(content="The user owns a dog", metadata={}) + doc3 = await collection.create_document(content="The user is a doctor", metadata={}) + + result = await collection.query(query="does the user own pets", top_k=2) + + assert result is not None + assert len(result) == 2 + assert isinstance(result[0], Document) + + doc3 = await collection.update_document(doc3, metadata={"test": "test"}, content="the user has owned pets in the past") + assert doc3 is not None + assert doc3.metadata == {"test": "test"} + assert doc3.content == "the user has owned pets in the past" + + result = await collection.query(query="does the user own pets", top_k=2) + assert result is not None + assert len(result) == 2 + assert isinstance(result[0], Document) diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index aba875f..f9b32fb 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,5 +1,5 @@ import pytest -from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, Session, Message, Metamessage +from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, Session, Message, Metamessage, GetDocumentPage, Document from honcho import Client as Honcho from uuid import uuid1 @@ -13,7 +13,7 @@ def test_session_creation_retrieval(): assert retrieved_session.id == created_session.id assert retrieved_session.is_active is True assert retrieved_session.location_id == "default" - assert retrieved_session.session_data == {} + assert retrieved_session.metadata == {} def test_session_multiple_retrieval(): @@ -37,7 +37,7 @@ def test_session_update(): created_session = client.create_session(user_id) assert created_session.update({"foo": "bar"}) retrieved_session = client.get_session(user_id, created_session.id) - assert retrieved_session.session_data == {"foo": "bar"} + assert retrieved_session.metadata == {"foo": "bar"} def test_session_deletion(): @@ -257,4 +257,94 @@ def test_paginated_metamessages_generator(): gen.__next__() +def test_collections(): + col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = client.create_collection(user_id, col_name) + + # Add documents + doc1 = collection.create_document(content="This is a test of documents - 1", metadata={"foo": "bar"}) + doc2 = collection.create_document(content="This is a test of documents - 2", metadata={}) + doc3 = collection.create_document(content="This is a test of documents - 3", metadata={}) + + # Get all documents + page = collection.get_documents(page=1, page_size=3) + # Verify size + assert page is not None + assert isinstance(page, GetDocumentPage) + assert len(page.items) == 3 + # delete a doc + result = collection.delete_document(doc1) + assert result is True + # Get all documents with a generator this time + gen = collection.get_documents_generator() + # Verfy size + item = gen.__next__() + item2 = gen.__next__() + with pytest.raises(StopIteration): + gen.__next__() + # delete the collection + result = collection.delete() + # confirm documents are gone + with pytest.raises(Exception): + new_col = client.get_collection(user_id, "test") + +def test_collection_name_collision(): + col_name = str(uuid1()) + new_col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = client.create_collection(user_id, col_name) + # Make another collection + with pytest.raises(Exception): + client.create_collection(user_id, col_name) + + # Change the name of original collection + result = collection.update(new_col_name) + assert result is True + + # Try again to add another collection + collection2 = client.create_collection(user_id, col_name) + assert collection2 is not None + assert collection2.name == col_name + assert collection.name == new_col_name + + # Get all collections + page = client.get_collections(user_id) + assert page is not None + assert len(page.items) == 2 + +def test_collection_query(): + col_name = str(uuid1()) + app_id = str(uuid1()) + user_id = str(uuid1()) + client = Honcho(app_id, "http://localhost:8000") + # Make a collection + collection = client.create_collection(user_id, col_name) + + # Add documents + doc1 = collection.create_document(content="The user loves puppies", metadata={}) + doc2 = collection.create_document(content="The user owns a dog", metadata={}) + doc3 = collection.create_document(content="The user is a doctor", metadata={}) + + result = collection.query(query="does the user own pets", top_k=2) + + assert result is not None + assert len(result) == 2 + assert isinstance(result[0], Document) + + doc3 = collection.update_document(doc3, metadata={"test": "test"}, content="the user has owned pets in the past") + assert doc3 is not None + assert doc3.metadata == {"test": "test"} + assert doc3.content == "the user has owned pets in the past" + + result = collection.query(query="does the user own pets", top_k=2) + assert result is not None + assert len(result) == 2 + assert isinstance(result[0], Document) From 614242b2e969002a7ecfa0e7e0d21c065186d0c2 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:52:21 -0800 Subject: [PATCH 28/85] Add reverse parameters for paginated routes --- api/src/crud.py | 257 +++++++++++++++++++------ api/src/main.py | 382 ++++++++++++++++++++++++++++---------- sdk/honcho/client.py | 328 +++++++++++++++++++++----------- sdk/honcho/sync_client.py | 328 +++++++++++++++++++++----------- 4 files changed, 927 insertions(+), 368 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index 9b8fa0c..e18c0ff 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -12,29 +12,46 @@ openai_client = OpenAI() -def get_session(db: Session, app_id: str, session_id: uuid.UUID, user_id: Optional[str] = None) -> Optional[models.Session]: - stmt = select(models.Session).where(models.Session.app_id == app_id).where(models.Session.id == session_id) + +def get_session( + db: Session, app_id: str, session_id: uuid.UUID, user_id: Optional[str] = None +) -> Optional[models.Session]: + stmt = ( + select(models.Session) + .where(models.Session.app_id == app_id) + .where(models.Session.id == session_id) + ) if user_id is not None: stmt = stmt.where(models.Session.user_id == user_id) session = db.scalars(stmt).one_or_none() return session + def get_sessions( - db: Session, app_id: str, user_id: str, location_id: str | None = None + db: Session, + app_id: str, + user_id: str, + location_id: Optional[str] = None, + reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Session) .where(models.Session.app_id == app_id) .where(models.Session.user_id == user_id) .where(models.Session.is_active.is_(True)) - .order_by(models.Session.created_at) ) + if reverse: + stmt = stmt.order_by(models.Session.created_at.desc()) + else: + stmt = stmt.order_by(models.Session.created_at) + if location_id is not None: stmt = stmt.where(models.Session.location_id == location_id) return stmt + def create_session( db: Session, session: schemas.SessionCreate, app_id: str, user_id: str ) -> models.Session: @@ -51,18 +68,29 @@ def create_session( def update_session( - db: Session, session: schemas.SessionUpdate, app_id: str, user_id: str, session_id: uuid.UUID + db: Session, + session: schemas.SessionUpdate, + app_id: str, + user_id: str, + session_id: uuid.UUID, ) -> bool: - honcho_session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) + honcho_session = get_session( + db, app_id=app_id, session_id=session_id, user_id=user_id + ) if honcho_session is None: raise ValueError("Session not found or does not belong to user") - if session.metadata is not None: # Need to explicitly be there won't make it empty by default + if ( + session.metadata is not None + ): # Need to explicitly be there won't make it empty by default honcho_session.h_metadata = session.metadata db.commit() db.refresh(honcho_session) return honcho_session -def delete_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID) -> bool: + +def delete_session( + db: Session, app_id: str, user_id: str, session_id: uuid.UUID +) -> bool: stmt = ( select(models.Session) .where(models.Session.id == session_id) @@ -76,10 +104,17 @@ def delete_session(db: Session, app_id: str, user_id: str, session_id: uuid.UUID db.commit() return True + def create_message( - db: Session, message: schemas.MessageCreate, app_id: str, user_id: str, session_id: uuid.UUID + db: Session, + message: schemas.MessageCreate, + app_id: str, + user_id: str, + session_id: uuid.UUID, ) -> models.Message: - honcho_session = get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) + honcho_session = get_session( + db, app_id=app_id, session_id=session_id, user_id=user_id + ) if honcho_session is None: raise ValueError("Session not found or does not belong to user") @@ -93,8 +128,13 @@ def create_message( db.refresh(honcho_message) return honcho_message + def get_messages( - db: Session, app_id: str, user_id: str, session_id: uuid.UUID + db: Session, + app_id: str, + user_id: str, + session_id: uuid.UUID, + reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Message) @@ -102,12 +142,18 @@ def get_messages( .where(models.Session.app_id == app_id) .where(models.Session.user_id == user_id) .where(models.Message.session_id == session_id) - .order_by(models.Message.created_at) ) + + if reverse: + stmt = stmt.order_by(models.Message.created_at.desc()) + else: + stmt = stmt.order_by(models.Message.created_at) + return stmt + def get_message( - db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID + db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID ) -> Optional[models.Message]: stmt = ( select(models.Message) @@ -116,15 +162,24 @@ def get_message( .where(models.Session.user_id == user_id) .where(models.Message.session_id == session_id) .where(models.Message.id == message_id) - ) return db.scalars(stmt).one_or_none() + ######################################################## # metamessage methods ######################################################## -def get_metamessages(db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: Optional[uuid.UUID], metamessage_type: Optional[str] = None) -> Select: + +def get_metamessages( + db: Session, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: Optional[uuid.UUID], + metamessage_type: Optional[str] = None, + reverse: Optional[bool] = False, +) -> Select: stmt = ( select(models.Metamessage) .join(models.Message, models.Message.id == models.Metamessage.message_id) @@ -132,19 +187,32 @@ def get_metamessages(db: Session, app_id: str, user_id: str, session_id: uuid.UU .where(models.Session.app_id == app_id) .where(models.Session.user_id == user_id) .where(models.Message.session_id == session_id) - .order_by(models.Metamessage.created_at) ) + if message_id is not None: stmt = stmt.where(models.Metamessage.message_id == message_id) + if metamessage_type is not None: stmt = stmt.where(models.Metamessage.metamessage_type == metamessage_type) + + if reverse: + stmt = stmt.order_by(models.Metamessage.created_at.desc()) + else: + stmt = stmt.order_by(models.Metamessage.created_at) + return stmt + def get_metamessage( - db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID -) -> Optional[models.Metamessage]: + db: Session, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: uuid.UUID, + metamessage_id: uuid.UUID, +) -> Optional[models.Metamessage]: stmt = ( - select(models.Metamessage) + select(models.Metamessage) .join(models.Message, models.Message.id == models.Metamessage.message_id) .join(models.Session, models.Message.session_id == models.Session.id) .where(models.Session.app_id == app_id) @@ -152,10 +220,10 @@ def get_metamessage( .where(models.Message.session_id == session_id) .where(models.Metamessage.message_id == message_id) .where(models.Metamessage.id == metamessage_id) - ) return db.scalars(stmt).one_or_none() + def create_metamessage( db: Session, metamessage: schemas.MetamessageCreate, @@ -163,7 +231,13 @@ def create_metamessage( user_id: str, session_id: uuid.UUID, ): - message = get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=metamessage.message_id) + message = get_message( + db, + app_id=app_id, + session_id=session_id, + user_id=user_id, + message_id=metamessage.message_id, + ) if message is None: raise ValueError("Session not found or does not belong to user") @@ -178,24 +252,36 @@ def create_metamessage( db.refresh(honcho_metamessage) return honcho_metamessage + ######################################################## # collection methods ######################################################## # Should be very similar to the session methods -def get_collections(db: Session, app_id: str, user_id: str) -> Select: + +def get_collections( + db: Session, app_id: str, user_id: str, reverse: Optional[bool] = False +) -> Select: """Get a distinct list of the names of collections associated with a user""" stmt = ( select(models.Collection) .where(models.Collection.app_id == app_id) .where(models.Collection.user_id == user_id) - .order_by(models.Collection.created_at) ) + + if reverse: + stmt = stmt.order_by(models.Collection.created_at.desc()) + else: + stmt = stmt.order_by(models.Collection.created_at) + return stmt -def get_collection_by_id(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID) -> Optional[models.Collection]: - stmt = ( + +def get_collection_by_id( + db: Session, app_id: str, user_id: str, collection_id: uuid.UUID +) -> Optional[models.Collection]: + stmt = ( select(models.Collection) .where(models.Collection.app_id == app_id) .where(models.Collection.user_id == user_id) @@ -204,8 +290,11 @@ def get_collection_by_id(db: Session, app_id: str, user_id: str, collection_id: collection = db.scalars(stmt).one_or_none() return collection -def get_collection_by_name(db: Session, app_id: str, user_id: str, name: str) -> Optional[models.Collection]: - stmt = ( + +def get_collection_by_name( + db: Session, app_id: str, user_id: str, name: str +) -> Optional[models.Collection]: + stmt = ( select(models.Collection) .where(models.Collection.app_id == app_id) .where(models.Collection.user_id == user_id) @@ -214,6 +303,7 @@ def get_collection_by_name(db: Session, app_id: str, user_id: str, name: str) -> collection = db.scalars(stmt).one_or_none() return collection + def create_collection( db: Session, collection: schemas.CollectionCreate, app_id: str, user_id: str ) -> models.Collection: @@ -231,10 +321,17 @@ def create_collection( db.refresh(honcho_collection) return honcho_collection + def update_collection( - db: Session, collection: schemas.CollectionUpdate, app_id: str, user_id: str, collection_id: uuid.UUID + db: Session, + collection: schemas.CollectionUpdate, + app_id: str, + user_id: str, + collection_id: uuid.UUID, ) -> models.Collection: - honcho_collection = get_collection_by_id(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + honcho_collection = get_collection_by_id( + db, app_id=app_id, user_id=user_id, collection_id=collection_id + ) if honcho_collection is None: raise ValueError("collection not found or does not belong to user") try: @@ -246,6 +343,7 @@ def update_collection( db.refresh(honcho_collection) return honcho_collection + def delete_collection( db: Session, app_id: str, user_id: str, collection_id: uuid.UUID ) -> bool: @@ -266,14 +364,20 @@ def delete_collection( db.commit() return True + ######################################################## # document methods ######################################################## # Should be similar to the messages methods outside of query + def get_documents( - db: Session, app_id: str, user_id: str, collection_id: uuid.UUID + db: Session, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Document) @@ -281,14 +385,24 @@ def get_documents( .where(models.Collection.app_id == app_id) .where(models.Collection.user_id == user_id) .where(models.Document.collection_id == collection_id) - .order_by(models.Document.created_at) ) + + if reverse: + stmt = stmt.order_by(models.Document.created_at.desc()) + else: + stmt = stmt.order_by(models.Document.created_at) + return stmt + def get_document( - db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID + db: Session, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, ) -> Optional[models.Document]: - stmt = ( + stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) .where(models.Collection.app_id == app_id) @@ -301,36 +415,48 @@ def get_document( return document -def query_documents(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, query: str, top_k: int = 5) -> Sequence[models.Document]: +def query_documents( + db: Session, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + query: str, + top_k: int = 5, +) -> Sequence[models.Document]: response = openai_client.embeddings.create( - input=query, - model="text-embedding-3-small" + input=query, model="text-embedding-3-small" ) embedding_query = response.data[0].embedding stmt = ( - select(models.Document) - .join(models.Collection, models.Collection.id == models.Document.collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) - .where(models.Document.collection_id == collection_id) - .order_by(models.Document.embedding.cosine_distance(embedding_query)) - .limit(top_k) - ) + select(models.Document) + .join(models.Collection, models.Collection.id == models.Document.collection_id) + .where(models.Collection.app_id == app_id) + .where(models.Collection.user_id == user_id) + .where(models.Document.collection_id == collection_id) + .order_by(models.Document.embedding.cosine_distance(embedding_query)) + .limit(top_k) + ) # if metadata is not None: - # stmt = stmt.where(models.Document.h_metadata.contains(metadata)) + # stmt = stmt.where(models.Document.h_metadata.contains(metadata)) return db.scalars(stmt).all() + def create_document( - db: Session, document: schemas.DocumentCreate, app_id: str, user_id: str, collection_id: uuid.UUID + db: Session, + document: schemas.DocumentCreate, + app_id: str, + user_id: str, + collection_id: uuid.UUID, ) -> models.Document: """Embed a message as a vector and create a document""" - collection = get_collection_by_id(db, app_id=app_id, collection_id=collection_id, user_id=user_id) + collection = get_collection_by_id( + db, app_id=app_id, collection_id=collection_id, user_id=user_id + ) if collection is None: raise ValueError("Session not found or does not belong to user") response = openai_client.embeddings.create( - input=document.content, - model="text-embedding-3-small" + input=document.content, model="text-embedding-3-small" ) embedding = response.data[0].embedding @@ -339,25 +465,36 @@ def create_document( collection_id=collection_id, content=document.content, h_metadata=document.metadata, - embedding=embedding + embedding=embedding, ) db.add(honcho_document) db.commit() db.refresh(honcho_document) return honcho_document + def update_document( - db: Session, document: schemas.DocumentUpdate, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID + db: Session, + document: schemas.DocumentUpdate, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, ) -> bool: - honcho_document = get_document(db, app_id=app_id, collection_id=collection_id, user_id=user_id, document_id=document_id) + honcho_document = get_document( + db, + app_id=app_id, + collection_id=collection_id, + user_id=user_id, + document_id=document_id, + ) if honcho_document is None: raise ValueError("Session not found or does not belong to user") if document.content is not None: honcho_document.content = document.content response = openai_client.embeddings.create( - input=document.content, - model="text-embedding-3-small" - ) + input=document.content, model="text-embedding-3-small" + ) embedding = response.data[0].embedding honcho_document.embedding = embedding honcho_document.created_at = datetime.datetime.now() @@ -368,7 +505,14 @@ def update_document( db.refresh(honcho_document) return honcho_document -def delete_document(db: Session, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID) -> bool: + +def delete_document( + db: Session, + app_id: str, + user_id: str, + collection_id: uuid.UUID, + document_id: uuid.UUID, +) -> bool: stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) @@ -383,4 +527,3 @@ def delete_document(db: Session, app_id: str, user_id: str, collection_id: uuid. db.delete(document) db.commit() return True - diff --git a/api/src/main.py b/api/src/main.py index 7a16e9c..0c658e9 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -13,7 +13,7 @@ from . import crud, models, schemas from .db import SessionLocal, engine -models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done +models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done app = FastAPI() @@ -30,6 +30,7 @@ add_pagination(app) + def get_db(): """FastAPI Dependency Generator for Database""" db = SessionLocal() @@ -38,17 +39,20 @@ def get_db(): finally: db.close() + ######################################################## # Session Routes ######################################################## + @router.get("/sessions", response_model=Page[schemas.Session]) def get_sessions( request: Request, app_id: str, user_id: str, location_id: Optional[str] = None, - db: Session = Depends(get_db) + reverse: Optional[bool] = False, + db: Session = Depends(get_db), ): """Get All Sessions for a User @@ -58,18 +62,27 @@ def get_sessions( location_id (str, optional): Optional Location ID representing the location of a session Returns: - list[schemas.Session]: List of Session objects + list[schemas.Session]: List of Session objects """ - return paginate(db, crud.get_sessions(db, app_id=app_id, user_id=user_id, location_id=location_id)) + return paginate( + db, + crud.get_sessions( + db, app_id=app_id, user_id=user_id, location_id=location_id, reverse=reverse + ), + ) @router.post("/sessions", response_model=schemas.Session) def create_session( - request: Request, app_id: str, user_id: str, session: schemas.SessionCreate, db: Session = Depends(get_db) + request: Request, + app_id: str, + user_id: str, + session: schemas.SessionCreate, + db: Session = Depends(get_db), ): """Create a Session for a User - + Args: app_id (str): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user @@ -77,22 +90,23 @@ def create_session( Returns: schemas.Session: The Session object of the new Session - + """ value = crud.create_session(db, app_id=app_id, user_id=user_id, session=session) return value + @router.put("/sessions/{session_id}", response_model=schemas.Session) def update_session( - request: Request, + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, session: schemas.SessionUpdate, db: Session = Depends(get_db), - ): +): """Update the metadata of a Session - + Args: app_id (str): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user @@ -104,20 +118,25 @@ def update_session( """ if session.metadata is None: - raise HTTPException(status_code=400, detail="Session metadata cannot be empty") # TODO TEST if I can set the metadata to be blank with this + raise HTTPException( + status_code=400, detail="Session metadata cannot be empty" + ) # TODO TEST if I can set the metadata to be blank with this try: - return crud.update_session(db, app_id=app_id, user_id=user_id, session_id=session_id, session=session) + return crud.update_session( + db, app_id=app_id, user_id=user_id, session_id=session_id, session=session + ) except ValueError: raise HTTPException(status_code=404, detail="Session not found") + @router.delete("/sessions/{session_id}") def delete_session( - request: Request, + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, db: Session = Depends(get_db), - ): +): """Delete a session by marking it as inactive Args: @@ -132,14 +151,23 @@ def delete_session( HTTPException: If the session is not found """ - response = crud.delete_session(db, app_id=app_id, user_id=user_id, session_id=session_id) + response = crud.delete_session( + db, app_id=app_id, user_id=user_id, session_id=session_id + ) if response: return {"message": "Session deleted successfully"} else: raise HTTPException(status_code=404, detail="Session not found") + @router.get("/sessions/{session_id}", response_model=schemas.Session) -def get_session(request: Request, app_id: str, user_id: str, session_id: uuid.UUID, db: Session = Depends(get_db)): +def get_session( + request: Request, + app_id: str, + user_id: str, + session_id: uuid.UUID, + db: Session = Depends(get_db), +): """Get a specific session for a user by ID Args: @@ -147,27 +175,28 @@ def get_session(request: Request, app_id: str, user_id: str, session_id: uuid.UU user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve - Returns: + Returns: schemas.Session: The Session object of the requested Session Raises: HTTPException: If the session is not found """ - honcho_session = crud.get_session(db, app_id=app_id, session_id=session_id, user_id=user_id) + honcho_session = crud.get_session( + db, app_id=app_id, session_id=session_id, user_id=user_id + ) if honcho_session is None: raise HTTPException(status_code=404, detail="Session not found") return honcho_session + ######################################################## # Message Routes ######################################################## -@router.post( - "/sessions/{session_id}/messages", - response_model=schemas.Message -) + +@router.post("/sessions/{session_id}/messages", response_model=schemas.Message) def create_message_for_session( - request: Request, + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, @@ -190,27 +219,29 @@ def create_message_for_session( """ try: - return crud.create_message(db, message=message, app_id=app_id, user_id=user_id, session_id=session_id) + return crud.create_message( + db, message=message, app_id=app_id, user_id=user_id, session_id=session_id + ) except ValueError: raise HTTPException(status_code=404, detail="Session not found") -@router.get( - "/sessions/{session_id}/messages", - response_model=Page[schemas.Message] -) -def get_messages_for_session( - request: Request, + +@router.get("/sessions/{session_id}/messages", response_model=Page[schemas.Message]) +def get_messages( + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, + reverse: Optional[bool] = False, db: Session = Depends(get_db), ): """Get all messages for a session - + Args: app_id (str): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve + reverse (bool): Whether to reverse the order of the messages Returns: list[schemas.Message]: List of Message objects @@ -219,14 +250,23 @@ def get_messages_for_session( HTTPException: If the session is not found """ - try: - return paginate(db, crud.get_messages(db, app_id=app_id, user_id=user_id, session_id=session_id)) + try: + return paginate( + db, + crud.get_messages( + db, + app_id=app_id, + user_id=user_id, + session_id=session_id, + reverse=reverse, + ), + ) except ValueError: raise HTTPException(status_code=404, detail="Session not found") + @router.get( - "sessions/{session_id}/messages/{message_id}", - response_model=schemas.Message + "sessions/{session_id}/messages/{message_id}", response_model=schemas.Message ) def get_message( request: Request, @@ -236,24 +276,23 @@ def get_message( message_id: uuid.UUID, db: Session = Depends(get_db), ): - """ - - """ - honcho_message = crud.get_message(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id) + """ """ + honcho_message = crud.get_message( + db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id + ) if honcho_message is None: raise HTTPException(status_code=404, detail="Session not found") return honcho_message + ######################################################## # metamessage routes ######################################################## -@router.post( - "/sessions/{session_id}/metamessages", - response_model=schemas.Metamessage -) + +@router.post("/sessions/{session_id}/metamessages", response_model=schemas.Metamessage) def create_metamessage( - request: Request, + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, @@ -276,29 +315,37 @@ def create_metamessage( """ try: - return crud.create_metamessage(db, metamessage=metamessage, app_id=app_id, user_id=user_id, session_id=session_id) + return crud.create_metamessage( + db, + metamessage=metamessage, + app_id=app_id, + user_id=user_id, + session_id=session_id, + ) except ValueError: raise HTTPException(status_code=404, detail="Session not found") + @router.get( - "/sessions/{session_id}/metamessages", - response_model=Page[schemas.Metamessage] + "/sessions/{session_id}/metamessages", response_model=Page[schemas.Metamessage] ) def get_metamessages( - request: Request, + request: Request, app_id: str, user_id: str, session_id: uuid.UUID, - message_id: Optional[uuid.UUID] = None, + message_id: Optional[uuid.UUID] = None, metamessage_type: Optional[str] = None, + reverse: Optional[bool] = False, db: Session = Depends(get_db), ): """Get all messages for a session - + Args: app_id (str): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve + reverse (bool): Whether to reverse the order of the metamessages Returns: list[schemas.Message]: List of Message objects @@ -307,13 +354,36 @@ def get_metamessages( HTTPException: If the session is not found """ - try: - return paginate(db, crud.get_metamessages(db, app_id=app_id, user_id=user_id, session_id=session_id, message_id=message_id, metamessage_type=metamessage_type)) + try: + return paginate( + db, + crud.get_metamessages( + db, + app_id=app_id, + user_id=user_id, + session_id=session_id, + message_id=message_id, + metamessage_type=metamessage_type, + reverse=reverse, + ), + ) except ValueError: raise HTTPException(status_code=404, detail="Session not found") -@router.get("/sessions/{session_id}/metamessages/{metamessage_id}", response_model=schemas.Metamessage) -def get_metamessage(request: Request, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID, db: Session = Depends(get_db)): + +@router.get( + "/sessions/{session_id}/metamessages/{metamessage_id}", + response_model=schemas.Metamessage, +) +def get_metamessage( + request: Request, + app_id: str, + user_id: str, + session_id: uuid.UUID, + message_id: uuid.UUID, + metamessage_id: uuid.UUID, + db: Session = Depends(get_db), +): """Get a specific session for a user by ID Args: @@ -321,29 +391,42 @@ def get_metamessage(request: Request, app_id: str, user_id: str, session_id: uui user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve - Returns: + Returns: schemas.Session: The Session object of the requested Session Raises: HTTPException: If the session is not found """ - honcho_metamessage = crud.get_metamessage(db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id, metamessage_id=metamessage_id) + honcho_metamessage = crud.get_metamessage( + db, + app_id=app_id, + session_id=session_id, + user_id=user_id, + message_id=message_id, + metamessage_id=metamessage_id, + ) if honcho_metamessage is None: raise HTTPException(status_code=404, detail="Session not found") return honcho_metamessage + ######################################################## # collection routes ######################################################## + @router.get("/collections/all", response_model=Page[schemas.Collection]) def get_collections( request: Request, app_id: str, user_id: str, + reverse: Optional[bool] = False, db: Session = Depends(get_db), ): - return paginate(db, crud.get_collections(db, app_id=app_id, user_id=user_id)) + return paginate( + db, crud.get_collections(db, app_id=app_id, user_id=user_id, reverse=reverse) + ) + @router.get("/collections/id/{collection_id}", response_model=schemas.Collection) def get_collection_by_id( @@ -351,38 +434,54 @@ def get_collection_by_id( app_id: str, user_id: str, collection_id: uuid.UUID, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ) -> schemas.Collection: - honcho_collection = crud.get_collection_by_id(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + honcho_collection = crud.get_collection_by_id( + db, app_id=app_id, user_id=user_id, collection_id=collection_id + ) if honcho_collection is None: - raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) return honcho_collection + @router.get("/collections/name/{name}", response_model=schemas.Collection) def get_collection_by_name( request: Request, app_id: str, user_id: str, name: str, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ) -> schemas.Collection: - honcho_collection = crud.get_collection_by_name(db, app_id=app_id, user_id=user_id, name=name) + honcho_collection = crud.get_collection_by_name( + db, app_id=app_id, user_id=user_id, name=name + ) if honcho_collection is None: - raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) return honcho_collection + @router.post("/collections", response_model=schemas.Collection) def create_collection( request: Request, app_id: str, user_id: str, collection: schemas.CollectionCreate, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): try: - return crud.create_collection(db, collection=collection, app_id=app_id, user_id=user_id) + return crud.create_collection( + db, collection=collection, app_id=app_id, user_id=user_id + ) except ValueError: - raise HTTPException(status_code=406, detail="Error invalid collection configuration - name may already exist") + raise HTTPException( + status_code=406, + detail="Error invalid collection configuration - name may already exist", + ) + @router.put("/collections/{collection_id}", response_model=schemas.Collection) def update_collection( @@ -391,63 +490,113 @@ def update_collection( user_id: str, collection_id: uuid.UUID, collection: schemas.CollectionUpdate, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): if collection.name is None: - raise HTTPException(status_code=400, detail="invalid request - name cannot be None") + raise HTTPException( + status_code=400, detail="invalid request - name cannot be None" + ) try: - honcho_collection = crud.update_collection(db, collection=collection, app_id=app_id, user_id=user_id, collection_id=collection_id) + honcho_collection = crud.update_collection( + db, + collection=collection, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + ) except ValueError: - raise HTTPException(status_code=406, detail="Error invalid collection configuration - name may already exist") + raise HTTPException( + status_code=406, + detail="Error invalid collection configuration - name may already exist", + ) return honcho_collection + @router.delete("/collections/{collection_id}") def delete_collection( request: Request, app_id: str, user_id: str, collection_id: uuid.UUID, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): - response = crud.delete_collection(db, app_id=app_id, user_id=user_id, collection_id=collection_id) + response = crud.delete_collection( + db, app_id=app_id, user_id=user_id, collection_id=collection_id + ) if response: return {"message": "Collection deleted successfully"} else: - raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) + ######################################################## # Document routes ######################################################## -@router.get("/collections/{collection_id}/documents", response_model=Page[schemas.Document]) + +@router.get( + "/collections/{collection_id}/documents", response_model=Page[schemas.Document] +) def get_documents( request: Request, app_id: str, user_id: str, collection_id: uuid.UUID, - db: Session = Depends(get_db) + reverse: Optional[bool] = False, + db: Session = Depends(get_db), ): try: - return paginate(db, crud.get_documents(db, app_id=app_id, user_id=user_id, collection_id=collection_id)) - except ValueError: # TODO can probably remove this exception ok to return empty here - raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + return paginate( + db, + crud.get_documents( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + reverse=reverse, + ), + ) + except ( + ValueError + ): # TODO can probably remove this exception ok to return empty here + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) + + +router.get( + "/collections/{collection_id}/documents/{document_id}", + response_model=schemas.Document, +) + -router.get("/collections/{collection_id}/documents/{document_id}", response_model=schemas.Document) def get_document( request: Request, app_id: str, user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): - honcho_document = crud.get_document(db, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + honcho_document = crud.get_document( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) if honcho_document is None: - raise HTTPException(status_code=404, detail="document not found or does not belong to user") + raise HTTPException( + status_code=404, detail="document not found or does not belong to user" + ) return honcho_document -@router.get("/collections/{collection_id}/query", response_model=Sequence[schemas.Document]) +@router.get( + "/collections/{collection_id}/query", response_model=Sequence[schemas.Document] +) def query_documents( request: Request, app_id: str, @@ -455,11 +604,19 @@ def query_documents( collection_id: uuid.UUID, query: str, top_k: int = 5, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): if top_k is not None and top_k > 50: - top_k = 50 # TODO see if we need to paginate this - return crud.query_documents(db=db, app_id=app_id, user_id=user_id, collection_id=collection_id, query=query, top_k=top_k) + top_k = 50 # TODO see if we need to paginate this + return crud.query_documents( + db=db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + query=query, + top_k=top_k, + ) + @router.post("/collections/{collection_id}/documents", response_model=schemas.Document) def create_document( @@ -468,14 +625,26 @@ def create_document( user_id: str, collection_id: uuid.UUID, document: schemas.DocumentCreate, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): try: - return crud.create_document(db, document=document, app_id=app_id, user_id=user_id, collection_id=collection_id) + return crud.create_document( + db, + document=document, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + ) except ValueError: - raise HTTPException(status_code=404, detail="collection not found or does not belong to user") + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) + -@router.put("/collections/{collection_id}/documents/{document_id}", response_model=schemas.Document) +@router.put( + "/collections/{collection_id}/documents/{document_id}", + response_model=schemas.Document, +) def update_document( request: Request, app_id: str, @@ -483,11 +652,21 @@ def update_document( collection_id: uuid.UUID, document_id: uuid.UUID, document: schemas.DocumentUpdate, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): - if document.content is None and document.metadata is None: - raise HTTPException(status_code=400, detail="content and metadata cannot both be None") - return crud.update_document(db, document=document, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + if document.content is None and document.metadata is None: + raise HTTPException( + status_code=400, detail="content and metadata cannot both be None" + ) + return crud.update_document( + db, + document=document, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) + @router.delete("/collections/{collection_id}/documents/{document_id}") def delete_document( @@ -496,12 +675,21 @@ def delete_document( user_id: str, collection_id: uuid.UUID, document_id: uuid.UUID, - db: Session = Depends(get_db) + db: Session = Depends(get_db), ): - response = crud.delete_document(db, app_id=app_id, user_id=user_id, collection_id=collection_id, document_id=document_id) + response = crud.delete_document( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) if response: return {"message": "Document deleted successfully"} else: - raise HTTPException(status_code=404, detail="document not found or does not belong to user") + raise HTTPException( + status_code=404, detail="document not found or does not belong to user" + ) + app.include_router(router) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index a21532b..6c8b17d 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -4,8 +4,10 @@ import httpx from .schemas import Message, Metamessage, Document + class AsyncGetPage: """Base class for receiving Paginated API results""" + def __init__(self, response: Dict) -> None: """Constructor for Page with relevant information about the results and pages @@ -16,18 +18,19 @@ def __init__(self, response: Dict) -> None: self.page = response["page"] self.page_size = response["size"] self.pages = response["pages"] - self.items =[] + self.items = [] async def next(self): """Shortcut method to Get the next page of results""" pass + class AsyncGetSessionPage(AsyncGetPage): """Paginated Results for Get Session Requests""" def __init__(self, client, options: Dict, response: Dict): """Constructor for Page Result from Session Get Request - + Args: client (AsyncClient): Honcho Client options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional @@ -37,6 +40,7 @@ def __init__(self, client, options: Dict, response: Dict): self.client = client self.user_id = options["user_id"] self.location_id = options["location_id"] + self.reverse = options["reverse"] self.items = [ AsyncSession( client=client, @@ -49,7 +53,7 @@ def __init__(self, client, options: Dict, response: Dict): ) for session in response["items"] ] - + async def next(self): """Get the next page of results Returns: @@ -57,22 +61,30 @@ async def next(self): """ if self.page >= self.pages: return None - return await self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) + return await self.client.get_sessions( + user_id=self.user_id, + location_id=self.location_id, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, + ) + class AsyncGetMessagePage(AsyncGetPage): """Paginated Results for Get Session Requests""" - def __init__(self, session, response: Dict): + def __init__(self, session, options, response: Dict): """Constructor for Page Result from Session Get Request - + Args: session (AsyncSession): Session the returned messages are associated with response (Dict): Response from API with pagination information """ super().__init__(response) self.session = session + self.reverse = options["reverse"] self.items = [ - Message( + Message( session_id=session.id, id=message["id"], is_user=message["is_user"], @@ -89,13 +101,15 @@ async def next(self): """ if self.page >= self.pages: return None - return await self.session.get_messages((self.page + 1), self.page_size) + return await self.session.get_messages( + (self.page + 1), self.page_size, self.reverse + ) + class AsyncGetMetamessagePage(AsyncGetPage): - def __init__(self, session, options: Dict, response: Dict) -> None: """Constructor for Page Result from Metamessage Get Request - + Args: session (AsyncSession): Session the returned messages are associated with options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both optional @@ -104,16 +118,19 @@ def __init__(self, session, options: Dict, response: Dict) -> None: super().__init__(response) self.session = session self.message_id = options["message_id"] if "message_id" in options else None - self.metamessage_type = options["metamessage_type"] if "metamessage_type" in options else None + self.metamessage_type = ( + options["metamessage_type"] if "metamessage_type" in options else None + ) + self.reverse = options["reverse"] self.items = [ - Metamessage( - id=metamessage["id"], - message_id=metamessage["message_id"], - metamessage_type=metamessage["metamessage_type"], - content=metamessage["content"], - created_at=metamessage["created_at"], - ) - for metamessage in response["items"] + Metamessage( + id=metamessage["id"], + message_id=metamessage["message_id"], + metamessage_type=metamessage["metamessage_type"], + content=metamessage["content"], + created_at=metamessage["created_at"], + ) + for metamessage in response["items"] ] async def next(self): @@ -123,19 +140,28 @@ async def next(self): """ if self.page >= self.pages: return None - return await self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) + return await self.session.get_metamessages( + metamessage_type=self.metamessage_type, + message=self.message_id, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, + ) + class AsyncGetDocumentPage(AsyncGetPage): """Paginated results for Get Document requests""" - def __init__(self, collection, response: Dict) -> None: + + def __init__(self, collection, options, response: Dict) -> None: """Constructor for Page Result from Document Get Request - + Args: collection (AsyncCollection): Collection the returned documents are associated with response (Dict): Response from API with pagination information """ super().__init__(response) self.collection = collection + self.reverse = options["reverse"] self.items = [ Document( id=document["id"], @@ -143,7 +169,7 @@ def __init__(self, collection, response: Dict) -> None: content=document["content"], metadata=document["metadata"], created_at=document["created_at"], - ) + ) for document in response["items"] ] @@ -154,14 +180,17 @@ async def next(self): """ if self.page >= self.pages: return None - return await self.collection.get_documents(page=self.page + 1, page_size=self.page_size) + return await self.collection.get_documents( + page=self.page + 1, page_size=self.page_size, reverse=self.reverse + ) + class AsyncGetCollectionPage(AsyncGetPage): """Paginated results for Get Collection requests""" def __init__(self, client, options: Dict, response: Dict): """Constructor for page result from Get Collection Request - + Args: client (Async Client): Honcho Client options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required @@ -170,6 +199,7 @@ def __init__(self, client, options: Dict, response: Dict): super().__init__(response) self.client = client self.user_id = options["user_id"] + self.reverse = options["reverse"] self.items = [ AsyncCollection( client=client, @@ -180,7 +210,7 @@ def __init__(self, client, options: Dict, response: Dict): ) for collection in response["items"] ] - + async def next(self): """Get the next page of results Returns: @@ -188,7 +218,13 @@ async def next(self): """ if self.page >= self.pages: return None - return await self.client.get_collections(user_id=self.user_id, page=self.page + 1, page_size=self.page_size) + return await self.client.get_collections( + user_id=self.user_id, + page=self.page + 1, + page_size=self.page_size, + reverse=self.reverse, + ) + class AsyncClient: """Honcho API Client Object""" @@ -196,7 +232,7 @@ class AsyncClient: def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API - self.app_id = app_id # Representing ID of the client application + self.app_id = app_id # Representing ID of the client application self.client = httpx.AsyncClient() @property @@ -226,10 +262,17 @@ async def get_session(self, user_id: str, session_id: uuid.UUID): location_id=data["location_id"], is_active=data["is_active"], metadata=data["metadata"], - created_at=data["created_at"] + created_at=data["created_at"], ) - async def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): + async def get_sessions( + self, + user_id: str, + location_id: Optional[str] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, + ): """Return sessions associated with a user paginated Args: @@ -242,19 +285,22 @@ async def get_sessions(self, user_id: str, location_id: Optional[str] = None, pa AsyncGetSessionPage: Page or results for get_sessions query """ - url = f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}" + ( - f"&location_id={location_id}" if location_id else "" + url = ( + f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}&reverse={reverse}" + + (f"&location_id={location_id}" if location_id else "") ) response = await self.client.get(url) response.raise_for_status() data = response.json() - options = { - "location_id": location_id, - "user_id": user_id - } + options = {"location_id": location_id, "user_id": user_id, "reverse": reverse} return AsyncGetSessionPage(self, options, data) - async def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): + async def get_sessions_generator( + self, + user_id: str, + location_id: Optional[str] = None, + reverse: bool = False, + ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app Args: @@ -267,7 +313,9 @@ async def get_sessions_generator(self, user_id: str, location_id: Optional[str] """ page = 1 page_size = 50 - get_session_response = await self.get_sessions(user_id, location_id, page, page_size) + get_session_response = await self.get_sessions( + user_id, location_id, page, page_size, reverse + ) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for session in get_session_response.items: @@ -276,7 +324,7 @@ async def get_sessions_generator(self, user_id: str, location_id: Optional[str] new_sessions = await get_session_response.next() if not new_sessions: break - + get_session_response = new_sessions async def create_session( @@ -309,7 +357,9 @@ async def create_session( ) async def create_collection( - self, user_id: str, name: str, + self, + user_id: str, + name: str, ): """Create a collection for a user @@ -354,29 +404,32 @@ async def get_collection(self, user_id: str, name: str): id=data["id"], user_id=data["user_id"], name=data["name"], - created_at=data["created_at"] + created_at=data["created_at"], ) - async def get_collections(self, user_id: str, page: int = 1, page_size: int = 50): + async def get_collections( + self, user_id: str, page: int = 1, page_size: int = 50, reverse: bool = False + ): """Return collections associated with a user paginated Args: user_id (str): The User ID representing the user to get the collection for page (int, optional): The page of results to return page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results Returns: AsyncGetCollectionPage: Page or results for get_collections query """ - url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}&reverse={reverse}" response = await self.client.get(url) response.raise_for_status() data = response.json() - options = {"user_id": user_id} + options = {"user_id": user_id, "reverse": reverse} return AsyncGetCollectionPage(self, options, data) - async def get_collections_generator(self, user_id: str): + async def get_collections_generator(self, user_id: str, reverse: bool = False): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app Args: @@ -388,7 +441,9 @@ async def get_collections_generator(self, user_id: str): """ page = 1 page_size = 50 - get_collection_response = await self.get_collections(user_id, page, page_size) + get_collection_response = await self.get_collections( + user_id, page, page_size, reverse + ) while True: # get_collection_response = self.get_collections(user_id, location_id, page, page_size) for collection in get_collection_response.items: @@ -397,7 +452,7 @@ async def get_collections_generator(self, user_id: str): new_collections = await get_collection_response.next() if not new_collections: break - + get_collection_response = new_collections @@ -412,7 +467,7 @@ def __init__( location_id: str, metadata: dict, is_active: bool, - created_at: datetime.datetime + created_at: datetime.datetime, ): """Constructor for Session""" self.base_url: str = client.base_url @@ -434,7 +489,6 @@ def __str__(self): """String representation of Session""" return f"AsyncSession(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" - @property def is_active(self): """Returns whether the session is active - made property to prevent tampering""" @@ -458,7 +512,13 @@ async def create_message(self, is_user: bool, content: str): response = await self.client.post(url, json=data) response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content, created_at=data["created_at"]) + return Message( + session_id=self.id, + id=data["id"], + is_user=is_user, + content=content, + created_at=data["created_at"], + ) async def get_message(self, message_id: uuid.UUID) -> Message: """Get a specific message for a session based on ID @@ -474,26 +534,36 @@ async def get_message(self, message_id: uuid.UUID) -> Message: response = await self.client.get(url) response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=data["is_user"], content=data["content"], created_at=data["created_at"]) + return Message( + session_id=self.id, + id=data["id"], + is_user=data["is_user"], + content=data["content"], + created_at=data["created_at"], + ) - async def get_messages(self, page: int = 1, page_size: int = 50) -> AsyncGetMessagePage: + async def get_messages( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ) -> AsyncGetMessagePage: """Get all messages for a session Args: page (int, optional): The page of results to return page_size (int, optional): The number of results to return per page + reverse (bool): Whether to reverse the order of the results Returns: AsyncGetMessagePage: Page of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}&reverse={reverse}" response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetMessagePage(self, data) - - async def get_messages_generator(self): + options = {"reverse": reverse} + return AsyncGetMessagePage(self, options, data) + + async def get_messages_generator(self, reverse: bool = False): """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app Yields: @@ -502,7 +572,7 @@ async def get_messages_generator(self): """ page = 1 page_size = 50 - get_messages_page= await self.get_messages(page, page_size) + get_messages_page = await self.get_messages(page, page_size, reverse) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for message in get_messages_page.items: @@ -511,10 +581,12 @@ async def get_messages_generator(self): new_messages = await get_messages_page.next() if not new_messages: break - + get_messages_page = new_messages - async def create_metamessage(self, message: Message, metamessage_type: str, content: str): + async def create_metamessage( + self, message: Message, metamessage_type: str, content: str + ): """Adds a metamessage to a session and links it to a specific message Args: @@ -528,13 +600,24 @@ async def create_metamessage(self, message: Message, metamessage_type: str, cont """ if not self.is_active: raise Exception("Session is inactive") - data = {"metamessage_type": metamessage_type, "content": content, "message_id": message.id} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + data = { + "metamessage_type": metamessage_type, + "content": content, + "message_id": message.id, + } + url = ( + f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + ) response = await self.client.post(url, json=data) response.raise_for_status() data = response.json() - return Metamessage(id=data["id"], message_id=message.id, metamessage_type=metamessage_type, content=content, created_at=data["created_at"]) - + return Metamessage( + id=data["id"], + message_id=message.id, + metamessage_type=metamessage_type, + content=content, + created_at=data["created_at"], + ) async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific metamessage @@ -550,9 +633,22 @@ async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: response = await self.client.get(url) response.raise_for_status() data = response.json() - return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) + return Metamessage( + id=data["id"], + message_id=data["message_id"], + metamessage_type=data["metamessage_type"], + content=data["content"], + created_at=data["created_at"], + ) - async def get_metamessages(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, page: int = 1, page_size: int = 50) -> AsyncGetMetamessagePage: + async def get_metamessages( + self, + metamessage_type: Optional[str] = None, + message: Optional[Message] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, + ) -> AsyncGetMetamessagePage: """Get all messages for a session Args: @@ -563,7 +659,7 @@ async def get_metamessages(self, metamessage_type: Optional[str] = None, message list[Dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}&reverse={reverse}" if metamessage_type: url += f"&metamessage_type={metamessage_type}" if message: @@ -572,12 +668,18 @@ async def get_metamessages(self, metamessage_type: Optional[str] = None, message response.raise_for_status() data = response.json() options = { - "metamessage_type": metamessage_type, - "message_id": message.id if message else None - } + "metamessage_type": metamessage_type, + "message_id": message.id if message else None, + "reverse": reverse, + } return AsyncGetMetamessagePage(self, options, data) - - async def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): + + async def get_metamessages_generator( + self, + metamessage_type: Optional[str] = None, + message: Optional[Message] = None, + reverse: bool = False, + ): """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app Args: @@ -590,19 +692,23 @@ async def get_metamessages_generator(self, metamessage_type: Optional[str] = Non """ page = 1 page_size = 50 - get_metamessages_page = await self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) + get_metamessages_page = await self.get_metamessages( + metamessage_type=metamessage_type, + message=message, + page=page, + page_size=page_size, + reverse=reverse, + ) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for metamessage in get_metamessages_page.items: yield metamessage new_messages = await get_metamessages_page.next() if not new_messages: break - + get_metamessages_page = new_messages - async def update(self, metadata: Dict): """Update the metadata of a session @@ -626,6 +732,7 @@ async def close(self): response.raise_for_status() self._is_active = False + class AsyncCollection: """Represents a single collection for a user in an app""" @@ -634,8 +741,8 @@ def __init__( client: AsyncClient, id: uuid.UUID, user_id: str, - name: str, - created_at: datetime.datetime, + name: str, + created_at: datetime.datetime, ): """Constructor for Collection""" self.base_url: str = client.base_url @@ -690,17 +797,19 @@ async def create_document(self, content: str, metadata: Dict = {}): """ data = {"metadata": metadata, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + url = ( + f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + ) response = await self.client.post(url, json=data) response.raise_for_status() data = response.json() return Document( - collection_id=self.id, - id=data["id"], - metadata=metadata, - content=content, - created_at=data["created_at"] - ) + collection_id=self.id, + id=data["id"], + metadata=metadata, + content=content, + created_at=data["created_at"], + ) async def get_document(self, document_id: uuid.UUID) -> Document: """Get a specific document for a collection based on ID @@ -717,14 +826,16 @@ async def get_document(self, document_id: uuid.UUID) -> Document: response.raise_for_status() data = response.json() return Document( - collection_id=self.id, - id=data["id"], - metadata=data["metadata"], - content=data["content"], - created_at=data["created_at"] - ) + collection_id=self.id, + id=data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"], + ) - async def get_documents(self, page: int = 1, page_size: int = 50) -> AsyncGetDocumentPage: + async def get_documents( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ) -> AsyncGetDocumentPage: """Get all documents for a collection Args: @@ -735,13 +846,14 @@ async def get_documents(self, page: int = 1, page_size: int = 50) -> AsyncGetDoc AsyncGetDocumentPage: Page of Document objects """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}&reverse={reverse}" response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetDocumentPage(self, data) - - async def get_documents_generator(self): + options = {"reverse": reverse} + return AsyncGetDocumentPage(self, options, data) + + async def get_documents_generator(self, reverse: bool = False): """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app Yields: @@ -750,7 +862,7 @@ async def get_documents_generator(self): """ page = 1 page_size = 50 - get_documents_page= await self.get_documents(page, page_size) + get_documents_page = await self.get_documents(page, page_size, reverse) while True: for document in get_documents_page.items: yield document @@ -758,11 +870,11 @@ async def get_documents_generator(self): new_documents = await get_documents_page.next() if not new_documents: break - + get_documents_page = new_documents async def query(self, query: str, top_k: int = 5) -> List[Document]: - """query the documents by cosine distance + """query the documents by cosine distance Args: query (str): The query string to compare other embeddings too top_k (int, optional): The number of results to return. Defaults to 5 max 50 @@ -774,18 +886,20 @@ async def query(self, query: str, top_k: int = 5) -> List[Document]: response = await self.client.get(url) response.raise_for_status() data = [ - Document( - collection_id=self.id, - content=document["content"], - id=document["id"], - created_at=document["created_at"], - metadata=document["metadata"] - ) - for document in response.json() + Document( + collection_id=self.id, + content=document["content"], + id=document["id"], + created_at=document["created_at"], + metadata=document["metadata"], + ) + for document in response.json() ] return data - async def update_document(self, document: Document, content: Optional[str], metadata: Optional[Dict]) -> Document: + async def update_document( + self, document: Document, content: Optional[str], metadata: Optional[Dict] + ) -> Document: """Update a document in the collection Args: diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 72c5261..5606bb6 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -4,8 +4,10 @@ import httpx from .schemas import Message, Metamessage, Document + class GetPage: """Base class for receiving Paginated API results""" + def __init__(self, response: Dict) -> None: """Constructor for Page with relevant information about the results and pages @@ -16,18 +18,19 @@ def __init__(self, response: Dict) -> None: self.page = response["page"] self.page_size = response["size"] self.pages = response["pages"] - self.items =[] + self.items = [] def next(self): """Shortcut method to Get the next page of results""" pass + class GetSessionPage(GetPage): """Paginated Results for Get Session Requests""" def __init__(self, client, options: Dict, response: Dict): """Constructor for Page Result from Session Get Request - + Args: client (Client): Honcho Client options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional @@ -37,6 +40,7 @@ def __init__(self, client, options: Dict, response: Dict): self.client = client self.user_id = options["user_id"] self.location_id = options["location_id"] + self.reverse = options["reverse"] self.items = [ Session( client=client, @@ -49,7 +53,7 @@ def __init__(self, client, options: Dict, response: Dict): ) for session in response["items"] ] - + def next(self): """Get the next page of results Returns: @@ -57,22 +61,30 @@ def next(self): """ if self.page >= self.pages: return None - return self.client.get_sessions(self.user_id, self.location_id, self.page + 1, self.page_size) + return self.client.get_sessions( + user_id=self.user_id, + location_id=self.location_id, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, + ) + class GetMessagePage(GetPage): """Paginated Results for Get Session Requests""" - def __init__(self, session, response: Dict): + def __init__(self, session, options, response: Dict): """Constructor for Page Result from Session Get Request - + Args: session (Session): Session the returned messages are associated with response (Dict): Response from API with pagination information """ super().__init__(response) self.session = session + self.reverse = options["reverse"] self.items = [ - Message( + Message( session_id=session.id, id=message["id"], is_user=message["is_user"], @@ -89,13 +101,15 @@ def next(self): """ if self.page >= self.pages: return None - return self.session.get_messages((self.page + 1), self.page_size) + return self.session.get_messages( + (self.page + 1), self.page_size, self.reverse + ) + class GetMetamessagePage(GetPage): - def __init__(self, session, options: Dict, response: Dict) -> None: """Constructor for Page Result from Metamessage Get Request - + Args: session (Session): Session the returned messages are associated with options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both optional @@ -104,16 +118,19 @@ def __init__(self, session, options: Dict, response: Dict) -> None: super().__init__(response) self.session = session self.message_id = options["message_id"] if "message_id" in options else None - self.metamessage_type = options["metamessage_type"] if "metamessage_type" in options else None + self.metamessage_type = ( + options["metamessage_type"] if "metamessage_type" in options else None + ) + self.reverse = options["reverse"] self.items = [ - Metamessage( - id=metamessage["id"], - message_id=metamessage["message_id"], - metamessage_type=metamessage["metamessage_type"], - content=metamessage["content"], - created_at=metamessage["created_at"], - ) - for metamessage in response["items"] + Metamessage( + id=metamessage["id"], + message_id=metamessage["message_id"], + metamessage_type=metamessage["metamessage_type"], + content=metamessage["content"], + created_at=metamessage["created_at"], + ) + for metamessage in response["items"] ] def next(self): @@ -123,19 +140,28 @@ def next(self): """ if self.page >= self.pages: return None - return self.session.get_metamessages(metamessage_type=self.metamessage_type, message=self.message_id, page=(self.page + 1), page_size=self.page_size) + return self.session.get_metamessages( + metamessage_type=self.metamessage_type, + message=self.message_id, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, + ) + class GetDocumentPage(GetPage): """Paginated results for Get Document requests""" - def __init__(self, collection, response: Dict) -> None: + + def __init__(self, collection, options, response: Dict) -> None: """Constructor for Page Result from Document Get Request - + Args: collection (Collection): Collection the returned documents are associated with response (Dict): Response from API with pagination information """ super().__init__(response) self.collection = collection + self.reverse = options["reverse"] self.items = [ Document( id=document["id"], @@ -143,7 +169,7 @@ def __init__(self, collection, response: Dict) -> None: content=document["content"], metadata=document["metadata"], created_at=document["created_at"], - ) + ) for document in response["items"] ] @@ -154,14 +180,17 @@ def next(self): """ if self.page >= self.pages: return None - return self.collection.get_documents(page=self.page + 1, page_size=self.page_size) + return self.collection.get_documents( + page=self.page + 1, page_size=self.page_size, reverse=self.reverse + ) + class GetCollectionPage(GetPage): """Paginated results for Get Collection requests""" def __init__(self, client, options: Dict, response: Dict): """Constructor for page result from Get Collection Request - + Args: client ( Client): Honcho Client options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required @@ -170,6 +199,7 @@ def __init__(self, client, options: Dict, response: Dict): super().__init__(response) self.client = client self.user_id = options["user_id"] + self.reverse = options["reverse"] self.items = [ Collection( client=client, @@ -180,7 +210,7 @@ def __init__(self, client, options: Dict, response: Dict): ) for collection in response["items"] ] - + def next(self): """Get the next page of results Returns: @@ -188,7 +218,13 @@ def next(self): """ if self.page >= self.pages: return None - return self.client.get_collections(user_id=self.user_id, page=self.page + 1, page_size=self.page_size) + return self.client.get_collections( + user_id=self.user_id, + page=self.page + 1, + page_size=self.page_size, + reverse=self.reverse, + ) + class Client: """Honcho API Client Object""" @@ -196,7 +232,7 @@ class Client: def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" self.base_url = base_url # Base URL for the instance of the Honcho API - self.app_id = app_id # Representing ID of the client application + self.app_id = app_id # Representing ID of the client application self.client = httpx.Client() @property @@ -226,10 +262,17 @@ def get_session(self, user_id: str, session_id: uuid.UUID): location_id=data["location_id"], is_active=data["is_active"], metadata=data["metadata"], - created_at=data["created_at"] + created_at=data["created_at"], ) - def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50): + def get_sessions( + self, + user_id: str, + location_id: Optional[str] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, + ): """Return sessions associated with a user paginated Args: @@ -242,19 +285,22 @@ def get_sessions(self, user_id: str, location_id: Optional[str] = None, page: in GetSessionPage: Page or results for get_sessions query """ - url = f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}" + ( - f"&location_id={location_id}" if location_id else "" + url = ( + f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}&reverse={reverse}" + + (f"&location_id={location_id}" if location_id else "") ) response = self.client.get(url) response.raise_for_status() data = response.json() - options = { - "location_id": location_id, - "user_id": user_id - } + options = {"location_id": location_id, "user_id": user_id, "reverse": reverse} return GetSessionPage(self, options, data) - def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None): + def get_sessions_generator( + self, + user_id: str, + location_id: Optional[str] = None, + reverse: bool = False, + ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app Args: @@ -267,7 +313,9 @@ def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None """ page = 1 page_size = 50 - get_session_response = self.get_sessions(user_id, location_id, page, page_size) + get_session_response = self.get_sessions( + user_id, location_id, page, page_size, reverse + ) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for session in get_session_response.items: @@ -276,7 +324,7 @@ def get_sessions_generator(self, user_id: str, location_id: Optional[str] = None new_sessions = get_session_response.next() if not new_sessions: break - + get_session_response = new_sessions def create_session( @@ -309,7 +357,9 @@ def create_session( ) def create_collection( - self, user_id: str, name: str, + self, + user_id: str, + name: str, ): """Create a collection for a user @@ -354,29 +404,32 @@ def get_collection(self, user_id: str, name: str): id=data["id"], user_id=data["user_id"], name=data["name"], - created_at=data["created_at"] + created_at=data["created_at"], ) - def get_collections(self, user_id: str, page: int = 1, page_size: int = 50): + def get_collections( + self, user_id: str, page: int = 1, page_size: int = 50, reverse: bool = False + ): """Return collections associated with a user paginated Args: user_id (str): The User ID representing the user to get the collection for page (int, optional): The page of results to return page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results Returns: GetCollectionPage: Page or results for get_collections query """ - url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}&reverse={reverse}" response = self.client.get(url) response.raise_for_status() data = response.json() - options = {"user_id": user_id} + options = {"user_id": user_id, "reverse": reverse} return GetCollectionPage(self, options, data) - def get_collections_generator(self, user_id: str): + def get_collections_generator(self, user_id: str, reverse: bool = False): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app Args: @@ -388,7 +441,9 @@ def get_collections_generator(self, user_id: str): """ page = 1 page_size = 50 - get_collection_response = self.get_collections(user_id, page, page_size) + get_collection_response = self.get_collections( + user_id, page, page_size, reverse + ) while True: # get_collection_response = self.get_collections(user_id, location_id, page, page_size) for collection in get_collection_response.items: @@ -397,7 +452,7 @@ def get_collections_generator(self, user_id: str): new_collections = get_collection_response.next() if not new_collections: break - + get_collection_response = new_collections @@ -412,7 +467,7 @@ def __init__( location_id: str, metadata: dict, is_active: bool, - created_at: datetime.datetime + created_at: datetime.datetime, ): """Constructor for Session""" self.base_url: str = client.base_url @@ -434,7 +489,6 @@ def __str__(self): """String representation of Session""" return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" - @property def is_active(self): """Returns whether the session is active - made property to prevent tampering""" @@ -458,7 +512,13 @@ def create_message(self, is_user: bool, content: str): response = self.client.post(url, json=data) response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=is_user, content=content, created_at=data["created_at"]) + return Message( + session_id=self.id, + id=data["id"], + is_user=is_user, + content=content, + created_at=data["created_at"], + ) def get_message(self, message_id: uuid.UUID) -> Message: """Get a specific message for a session based on ID @@ -474,26 +534,36 @@ def get_message(self, message_id: uuid.UUID) -> Message: response = self.client.get(url) response.raise_for_status() data = response.json() - return Message(session_id=self.id, id=data["id"], is_user=data["is_user"], content=data["content"], created_at=data["created_at"]) + return Message( + session_id=self.id, + id=data["id"], + is_user=data["is_user"], + content=data["content"], + created_at=data["created_at"], + ) - def get_messages(self, page: int = 1, page_size: int = 50) -> GetMessagePage: + def get_messages( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ) -> GetMessagePage: """Get all messages for a session Args: page (int, optional): The page of results to return page_size (int, optional): The number of results to return per page + reverse (bool): Whether to reverse the order of the results Returns: GetMessagePage: Page of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}&reverse={reverse}" response = self.client.get(url) response.raise_for_status() data = response.json() - return GetMessagePage(self, data) - - def get_messages_generator(self): + options = {"reverse": reverse} + return GetMessagePage(self, options, data) + + def get_messages_generator(self, reverse: bool = False): """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app Yields: @@ -502,7 +572,7 @@ def get_messages_generator(self): """ page = 1 page_size = 50 - get_messages_page= self.get_messages(page, page_size) + get_messages_page = self.get_messages(page, page_size, reverse) while True: # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for message in get_messages_page.items: @@ -511,10 +581,12 @@ def get_messages_generator(self): new_messages = get_messages_page.next() if not new_messages: break - + get_messages_page = new_messages - def create_metamessage(self, message: Message, metamessage_type: str, content: str): + def create_metamessage( + self, message: Message, metamessage_type: str, content: str + ): """Adds a metamessage to a session and links it to a specific message Args: @@ -528,13 +600,24 @@ def create_metamessage(self, message: Message, metamessage_type: str, content: s """ if not self.is_active: raise Exception("Session is inactive") - data = {"metamessage_type": metamessage_type, "content": content, "message_id": message.id} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + data = { + "metamessage_type": metamessage_type, + "content": content, + "message_id": message.id, + } + url = ( + f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" + ) response = self.client.post(url, json=data) response.raise_for_status() data = response.json() - return Metamessage(id=data["id"], message_id=message.id, metamessage_type=metamessage_type, content=content, created_at=data["created_at"]) - + return Metamessage( + id=data["id"], + message_id=message.id, + metamessage_type=metamessage_type, + content=content, + created_at=data["created_at"], + ) def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific metamessage @@ -550,9 +633,22 @@ def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: response = self.client.get(url) response.raise_for_status() data = response.json() - return Metamessage(id=data["id"], message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], created_at=data["created_at"]) + return Metamessage( + id=data["id"], + message_id=data["message_id"], + metamessage_type=data["metamessage_type"], + content=data["content"], + created_at=data["created_at"], + ) - def get_metamessages(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, page: int = 1, page_size: int = 50) -> GetMetamessagePage: + def get_metamessages( + self, + metamessage_type: Optional[str] = None, + message: Optional[Message] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, + ) -> GetMetamessagePage: """Get all messages for a session Args: @@ -563,7 +659,7 @@ def get_metamessages(self, metamessage_type: Optional[str] = None, message: Opti list[Dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}&reverse={reverse}" if metamessage_type: url += f"&metamessage_type={metamessage_type}" if message: @@ -572,12 +668,18 @@ def get_metamessages(self, metamessage_type: Optional[str] = None, message: Opti response.raise_for_status() data = response.json() options = { - "metamessage_type": metamessage_type, - "message_id": message.id if message else None - } + "metamessage_type": metamessage_type, + "message_id": message.id if message else None, + "reverse": reverse, + } return GetMetamessagePage(self, options, data) - - def get_metamessages_generator(self, metamessage_type: Optional[str] = None, message: Optional[Message] = None): + + def get_metamessages_generator( + self, + metamessage_type: Optional[str] = None, + message: Optional[Message] = None, + reverse: bool = False, + ): """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app Args: @@ -590,19 +692,23 @@ def get_metamessages_generator(self, metamessage_type: Optional[str] = None, mes """ page = 1 page_size = 50 - get_metamessages_page = self.get_metamessages(metamessage_type=metamessage_type, message=message, page=page, page_size=page_size) + get_metamessages_page = self.get_metamessages( + metamessage_type=metamessage_type, + message=message, + page=page, + page_size=page_size, + reverse=reverse, + ) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for metamessage in get_metamessages_page.items: yield metamessage new_messages = get_metamessages_page.next() if not new_messages: break - + get_metamessages_page = new_messages - def update(self, metadata: Dict): """Update the metadata of a session @@ -626,6 +732,7 @@ def close(self): response.raise_for_status() self._is_active = False + class Collection: """Represents a single collection for a user in an app""" @@ -634,8 +741,8 @@ def __init__( client: Client, id: uuid.UUID, user_id: str, - name: str, - created_at: datetime.datetime, + name: str, + created_at: datetime.datetime, ): """Constructor for Collection""" self.base_url: str = client.base_url @@ -690,17 +797,19 @@ def create_document(self, content: str, metadata: Dict = {}): """ data = {"metadata": metadata, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + url = ( + f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" + ) response = self.client.post(url, json=data) response.raise_for_status() data = response.json() return Document( - collection_id=self.id, - id=data["id"], - metadata=metadata, - content=content, - created_at=data["created_at"] - ) + collection_id=self.id, + id=data["id"], + metadata=metadata, + content=content, + created_at=data["created_at"], + ) def get_document(self, document_id: uuid.UUID) -> Document: """Get a specific document for a collection based on ID @@ -717,14 +826,16 @@ def get_document(self, document_id: uuid.UUID) -> Document: response.raise_for_status() data = response.json() return Document( - collection_id=self.id, - id=data["id"], - metadata=data["metadata"], - content=data["content"], - created_at=data["created_at"] - ) + collection_id=self.id, + id=data["id"], + metadata=data["metadata"], + content=data["content"], + created_at=data["created_at"], + ) - def get_documents(self, page: int = 1, page_size: int = 50) -> GetDocumentPage: + def get_documents( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ) -> GetDocumentPage: """Get all documents for a collection Args: @@ -735,13 +846,14 @@ def get_documents(self, page: int = 1, page_size: int = 50) -> GetDocumentPage: GetDocumentPage: Page of Document objects """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}" + url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}&reverse={reverse}" response = self.client.get(url) response.raise_for_status() data = response.json() - return GetDocumentPage(self, data) - - def get_documents_generator(self): + options = {"reverse": reverse} + return GetDocumentPage(self, options, data) + + def get_documents_generator(self, reverse: bool = False): """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app Yields: @@ -750,7 +862,7 @@ def get_documents_generator(self): """ page = 1 page_size = 50 - get_documents_page= self.get_documents(page, page_size) + get_documents_page = self.get_documents(page, page_size, reverse) while True: for document in get_documents_page.items: yield document @@ -758,11 +870,11 @@ def get_documents_generator(self): new_documents = get_documents_page.next() if not new_documents: break - + get_documents_page = new_documents def query(self, query: str, top_k: int = 5) -> List[Document]: - """query the documents by cosine distance + """query the documents by cosine distance Args: query (str): The query string to compare other embeddings too top_k (int, optional): The number of results to return. Defaults to 5 max 50 @@ -774,18 +886,20 @@ def query(self, query: str, top_k: int = 5) -> List[Document]: response = self.client.get(url) response.raise_for_status() data = [ - Document( - collection_id=self.id, - content=document["content"], - id=document["id"], - created_at=document["created_at"], - metadata=document["metadata"] - ) - for document in response.json() + Document( + collection_id=self.id, + content=document["content"], + id=document["id"], + created_at=document["created_at"], + metadata=document["metadata"], + ) + for document in response.json() ] return data - def update_document(self, document: Document, content: Optional[str], metadata: Optional[Dict]) -> Document: + def update_document( + self, document: Document, content: Optional[str], metadata: Optional[Dict] + ) -> Document: """Update a document in the collection Args: From ef636ce217c648f9f89513389fc5069b4de9b784 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:56:02 -0800 Subject: [PATCH 29/85] Address dependabot --- api/poetry.lock | 52 +++++++++++++++++++++++----------------------- api/pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index fe56dc9..9a68f07 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -17,25 +17,26 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "anyio" -version = "3.7.1" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [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)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "certifi" @@ -123,35 +124,34 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.105.0" +version = "0.109.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.105.0-py3-none-any.whl", hash = "sha256:f19ebf6fdc82a3281d10f2cb4774bdfa90238e3b40af3525a0c09fd08ad1c480"}, - {file = "fastapi-0.105.0.tar.gz", hash = "sha256:4d12838819aa52af244580675825e750ad67c9df4614f557a769606af902cf22"}, + {file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"}, + {file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"}, ] [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" +starlette = ">=0.36.3,<0.37.0" typing-extensions = ">=4.8.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)"] +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.7)", "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 = "fastapi-pagination" -version = "0.12.15" +version = "0.12.16" description = "FastAPI pagination" category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "fastapi_pagination-0.12.15-py3-none-any.whl", hash = "sha256:bcfea8622b48135ef759b926d9d09fa8e16bc8adab26ec2b65d1647e72d39988"}, - {file = "fastapi_pagination-0.12.15.tar.gz", hash = "sha256:a7e5e48cd9d183f29532455a1689dfac575877b7ff10d112ddb56cb3d047a457"}, + {file = "fastapi_pagination-0.12.16-py3-none-any.whl", hash = "sha256:1179edea6c8d3b6b70d3f373047470b08a948bfef817ff8e722d46969f87998c"}, + {file = "fastapi_pagination-0.12.16.tar.gz", hash = "sha256:3c74d77d42451518e9d85aa1c3633b725f42d9746d68d1e9267f6c0493750497"}, ] [package.dependencies] @@ -340,14 +340,14 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "limits" -version = "3.8.0" +version = "3.9.0" description = "Rate limiting utilities" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "limits-3.8.0-py3-none-any.whl", hash = "sha256:6e3c75712359dfaea28bee23832bd814bbe66a42c92bbd848154dfba0d4c4503"}, - {file = "limits-3.8.0.tar.gz", hash = "sha256:7dd4955dec3c7a219be04e661251ae243a48050e84053bf68b31dd07890f28c2"}, + {file = "limits-3.9.0-py3-none-any.whl", hash = "sha256:6dce07d1a4d7bd3361d36f59f3f43c4f39675001daeeae2617c3be42d718daa8"}, + {file = "limits-3.9.0.tar.gz", hash = "sha256:7b44aa4d05c539276928372681190136914958cccbb99c30ecc5df72a179661a"}, ] [package.dependencies] @@ -785,14 +785,14 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "starlette" -version = "0.27.0" +version = "0.36.3" description = "The little ASGI library that shines." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, - {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, + {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, + {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, ] [package.dependencies] @@ -800,7 +800,7 @@ anyio = ">=3.4.0,<5" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] [[package]] name = "tqdm" @@ -954,4 +954,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "90a0874f29e706994647a141418ed4eca5bd621518396d525d27039ad586e4bc" +content-hash = "49ec8fef5f21cb5bf2a8bbd007f016bd5bd88f8bdf604f3a820c59c07f984060" diff --git a/api/pyproject.toml b/api/pyproject.toml index 6034c3f..2c828a9 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.8" -fastapi = "^0.105.0" +fastapi = "^0.109.0" uvicorn = "^0.24.0.post1" python-dotenv = "^1.0.0" sqlalchemy = "^2.0.25" From 10b65b2cdc04a2c521e496bdf9b8450f96f5551d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:53:23 -0800 Subject: [PATCH 30/85] Formatting --- api/src/models.py | 68 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/api/src/models.py b/api/src/models.py index ea4b86b..ce20747 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -12,70 +12,98 @@ load_dotenv() -DATABASE_TYPE = os.getenv("DATABASE_TYPE", 'postgres') +DATABASE_TYPE = os.getenv("DATABASE_TYPE", "postgres") + +ColumnType = JSONB if DATABASE_TYPE == "postgres" else JSON -ColumnType = JSONB if DATABASE_TYPE == 'postgres' else JSON class Session(Base): __tablename__ = "sessions" - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) app_id: Mapped[str] = mapped_column(String(512), index=True) user_id: Mapped[str] = mapped_column(String(512), index=True) - location_id: Mapped[str] = mapped_column(String(512), index=True) + location_id: Mapped[str] = mapped_column(String(512), index=True, default="default") is_active: Mapped[bool] = mapped_column(default=True) - h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) - created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) + created_at: Mapped[datetime.datetime] = mapped_column( + default=datetime.datetime.utcnow + ) messages = relationship("Message", back_populates="session") def __repr__(self) -> str: return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})" + class Message(Base): __tablename__ = "messages" - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("sessions.id")) is_user: Mapped[bool] - content: Mapped[str] = mapped_column(String(65535)) + content: Mapped[str] = mapped_column(String(65535)) - created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) + created_at: Mapped[datetime.datetime] = mapped_column( + default=datetime.datetime.utcnow + ) session = relationship("Session", back_populates="messages") metamessages = relationship("Metamessage", back_populates="message") + def __repr__(self) -> str: return f"Message(id={self.id}, session_id={self.session_id}, is_user={self.is_user}, content={self.content[10:]})" + class Metamessage(Base): __tablename__ = "metamessages" - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) - metamessage_type: Mapped[str] = mapped_column(String(512), index=True) - content: Mapped[str] = mapped_column(String(65535)) + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) + metamessage_type: Mapped[str] = mapped_column(String(512), index=True) + content: Mapped[str] = mapped_column(String(65535)) message_id = Column(Uuid, ForeignKey("messages.id")) message = relationship("Message", back_populates="metamessages") - created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) + created_at: Mapped[datetime.datetime] = mapped_column( + default=datetime.datetime.utcnow + ) def __repr__(self) -> str: return f"Metamessages(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content[10:]})" + class Collection(Base): __tablename__ = "collections" - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) name: Mapped[str] = mapped_column(String(512), index=True) app_id: Mapped[str] = mapped_column(String(512), index=True) user_id: Mapped[str] = mapped_column(String(512), index=True) - created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) - documents = relationship("Document", back_populates="collection", cascade="all, delete, delete-orphan") + created_at: Mapped[datetime.datetime] = mapped_column( + default=datetime.datetime.utcnow + ) + documents = relationship( + "Document", back_populates="collection", cascade="all, delete, delete-orphan" + ) __table_args__ = ( - UniqueConstraint('name', 'app_id', 'user_id', name="unique_name_app_user"), + UniqueConstraint("name", "app_id", "user_id", name="unique_name_app_user"), ) + class Document(Base): __tablename__ = "documents" - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, index=True, default=uuid.uuid4) + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) content: Mapped[str] = mapped_column(String(65535)) embedding = mapped_column(Vector(1536)) - created_at: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow) - + created_at: Mapped[datetime.datetime] = mapped_column( + default=datetime.datetime.utcnow + ) + collection_id = Column(Uuid, ForeignKey("collections.id")) collection = relationship("Collection", back_populates="documents") From f82bd7f3c57e990c9e46a5b73253e9e377befc63 Mon Sep 17 00:00:00 2001 From: vintro Date: Tue, 20 Feb 2024 12:54:38 -0500 Subject: [PATCH 31/85] initial commit on honcho dspy personas --- .../honcho-dspy-personas/.env.template | 2 + .../discord/honcho-dspy-personas/.gitignore | 5 + example/discord/honcho-dspy-personas/bot.py | 79 + example/discord/honcho-dspy-personas/chain.py | 114 + example/discord/honcho-dspy-personas/graph.py | 83 + .../langchain_prompts/state_check.yaml | 10 + .../langchain_prompts/state_commentary.yaml | 8 + .../langchain_prompts/state_labeling.yaml | 9 + .../discord/honcho-dspy-personas/metric.py | 23 + .../discord/honcho-dspy-personas/poetry.lock | 2191 +++++++++++++++++ .../honcho-dspy-personas/pyproject.toml | 20 + 11 files changed, 2544 insertions(+) create mode 100644 example/discord/honcho-dspy-personas/.env.template create mode 100644 example/discord/honcho-dspy-personas/.gitignore create mode 100644 example/discord/honcho-dspy-personas/bot.py create mode 100644 example/discord/honcho-dspy-personas/chain.py create mode 100644 example/discord/honcho-dspy-personas/graph.py create mode 100644 example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml create mode 100644 example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml create mode 100644 example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml create mode 100644 example/discord/honcho-dspy-personas/metric.py create mode 100644 example/discord/honcho-dspy-personas/poetry.lock create mode 100644 example/discord/honcho-dspy-personas/pyproject.toml diff --git a/example/discord/honcho-dspy-personas/.env.template b/example/discord/honcho-dspy-personas/.env.template new file mode 100644 index 0000000..0aafc86 --- /dev/null +++ b/example/discord/honcho-dspy-personas/.env.template @@ -0,0 +1,2 @@ +BOT_TOKEN= +OPENAI_API_KEY= \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/.gitignore b/example/discord/honcho-dspy-personas/.gitignore new file mode 100644 index 0000000..f133efa --- /dev/null +++ b/example/discord/honcho-dspy-personas/.gitignore @@ -0,0 +1,5 @@ +.env + +.venv + +.DS_Store \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py new file mode 100644 index 0000000..caab5ee --- /dev/null +++ b/example/discord/honcho-dspy-personas/bot.py @@ -0,0 +1,79 @@ +import os +from uuid import uuid1 +import discord +from honcho import Client as HonchoClient +from graph import langchain_message_converter, chat + + +intents = discord.Intents.default() +intents.messages = True +intents.message_content = True +intents.members = True + +app_id = "vince/dspy-personas" + +#honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev + +bot = discord.Bot(intents=intents) + +@bot.event +async def on_ready(): + print(f'We have logged in as {bot.user}') + +@bot.event +async def on_member_join(member): + await member.send( + f"*Hello {member.name}, welcome to the server! This is a demo bot built with Honcho,* " + "*implementing a naive user modeling method.* " + "*To get started, just type a message in this channel and the bot will respond.* " + "*Over time, it will classify the \"state\" you're in and optimize conversations based on that state.* " + "*You can use the /restart command to restart the conversation at any time.* " + "*If you have any questions or feedback, feel free to ask in the #honcho channel.* " + "*Enjoy!*" + ) + + +@bot.event +async def on_message(message): + if message.author == bot.user or message.guild is not None: + return + + user_id = f"discord_{str(message.author.id)}" + location_id=str(message.channel.id) + + sessions = list(honcho.get_sessions_generator(user_id, location_id)) + + if len(sessions) > 0: + session = sessions[0] + else: + session = honcho.create_session(user_id, location_id) + + history = list(session.get_messages(page_size=10)) + chat_history = langchain_message_converter(history) + + inp = message.content + user_message = session.create_message(is_user=True, content=inp) + + async with message.channel.typing(): + response = await chat( + chat_history=chat_history, + user_message=user_message, + session=session, + input=inp + ) + await message.channel.send(response) + + session.create_message(is_user=False, content=response) + +@bot.slash_command(name = "restart", description = "Restart the Conversation") +async def restart(ctx): + user_id=f"discord_{str(ctx.author.id)}" + location_id=str(ctx.channel_id) + sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions[0].close() if len(sessions) > 0 else None + + msg = "Great! The conversation has been restarted. What would you like to talk about?" + await ctx.respond(msg) + +bot.run(os.environ["BOT_TOKEN"]) diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py new file mode 100644 index 0000000..8d19707 --- /dev/null +++ b/example/discord/honcho-dspy-personas/chain.py @@ -0,0 +1,114 @@ +import os +from typing import List, Union +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, load_prompt +from langchain_core.messages import AIMessage, HumanMessage + +from honcho import Message + +# langchain prompts +SYSTEM_STATE_COMMENTARY = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_commentary.yaml')) +SYSTEM_STATE_LABELING = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_labeling.yaml')) +SYSTEM_STATE_CHECK = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_check.yaml')) + +# quick utility function to convert messages from honcho to langchain +def langchain_message_converter(messages: List[Message]) -> List[Union[AIMessage, HumanMessage]]: + new_messages = [] + for message in messages: + if message.is_user: + new_messages.append(HumanMessage(content=message.content)) + else: + new_messages.append(AIMessage(content=message.content)) + return new_messages + + +# convert chat history and user input into a string +def format_chat_history(chat_history: List[Message], user_input=None): + messages = [("user: " + message.content if isinstance(message, HumanMessage) else "ai: " + message.content) for message in chat_history] + if user_input: + messages.append(f"user: {user_input}") + + return "\n".join(messages) + + + +class StateExtractor: + """Wrapper class for all the DSPy and LangChain code for user state labeling and pipeline optimization""" + lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name = "gpt-4") + lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name = "gpt-3.5-turbo") + system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_COMMENTARY) + system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_LABELING) + system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_CHECK) + + def __init__(self) -> None: + pass + + @classmethod + async def generate_state_commentary(cls, chat_history: List[Message], input: str) -> str: + """Generate a commentary on the current state of the user""" + # format prompt + state_commentary = ChatPromptTemplate.from_messages([ + cls.system_state_commentary + ]) + # LCEL + chain = state_commentary | cls.lc_gpt_4 + # inference + response = await chain.ainvoke({ + "chat_history": format_chat_history(chat_history, user_input=input), + "user_input": input + }) + # return output + return response.content + + @classmethod + async def generate_state_label(cls, state_commetary: str) -> str: + """Generate a state label from a commetary on the user's state""" + # format prompt + state_labeling = ChatPromptTemplate.from_messages([ + cls.system_state_labeling + ]) + # LCEL + chain = state_labeling | cls.lc_gpt_4 + # inference + response = await chain.ainvoke({ + "state_commetary": state_commetary + }) + # return output + return response.content + + @classmethod + async def check_state_exists(cls, existing_states: List[str], state: str): + """Check if a user state is new or already is stored""" + + # convert existing_states to a formatted string + existing_states = "\n".join(existing_states) + + # format prompt + state_check = ChatPromptTemplate.from_messages([ + cls.system_state_check + ]) + # LCEL + chain = state_check | cls.lc_gpt_turbo + # inference + response = await chain.ainvoke({ + "existing_states": existing_states, + "state": state, + }) + # return output + return response.output + + @classmethod + async def generate_state(cls, existing_states: List[str], chat_history: List[Message], input: str): + """"Determine the user's state from the current conversation state""" + + # Generate label + state_commetary = cls.generate_state_commentary(chat_history, input) + state_label = cls.generate_state_label(state_commetary) + + # Determine if state is new + existing_state = cls.check_state_exists(existing_states, state_label) + is_state_new = existing_state is None + + # return existing state if we found one + return is_state_new, existing_state or state_label + \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py new file mode 100644 index 0000000..6283a0e --- /dev/null +++ b/example/discord/honcho-dspy-personas/graph.py @@ -0,0 +1,83 @@ +import os +import dspy +from typing import List +from dspy.teleprompt import BootstrapFewShot +from dotenv import load_dotenv +from chain import StateExtractor, format_chat_history + +from honcho import Message, Session + +load_dotenv() + +# Configure DSPy +dspy_gpt4 = dspy.OpenAI(model="gpt-4") +dspy.settings.configure(lm=dspy_gpt4) + + + +# DSPy Signatures +class Thought(dspy.Signature): + """Generate a thought about the user's needs""" + user_input = dspy.InputField() + thought = dspy.OutputField(desc="a prediction about the user's mental state") + +class Response(dspy.Signature): + """Generate a response for the user based on the thought provided""" + user_input = dspy.InputField() + thought = dspy.InputField() + response = dspy.OutputField(desc="keep the conversation going, be engaging") + +# DSPy Module +class ChatWithThought(dspy.Module): + generate_thought = dspy.Predict(Thought) + generate_response = dspy.Predict(Response) + + def forward(self, user_message: Message, session: Session, chat_input: str): + session.create_message(is_user=True, content=chat_input) + + # call the thought predictor + thought = self.generate_thought(user_input=chat_input) + session.create_metamessage(user_message, metamessage_type="thought", content=thought.thought) + + # call the response predictor + response = self.generate_response(user_input=chat_input, thought=thought.thought) + session.create_message(is_user=False, content=response.response) + + return response.response + +user_state_storage = {} +async def chat(user_message: Message, session: Session, chat_history: List[Message], input: str, optimization_threshold=5): + # first we need to take the user input and determine the user's state/dimension/persona + is_state_new, user_state = await StateExtractor.generate_state(chat_history, input) + + # Save the user_state if it's new + if is_state_new: + user_state_storage[user_state] = { + "chat_module": ChatWithThought(), + "examples": [] + } + + # then, we need to select the pipeline for that derived state/dimension/persona + # way this would work is to define the optimizer and optimize a chain once examples in a certain dimension exceed a threshold + # need a way to store the optimized chain and call it given a state/dimension/persona + # this is the reward model for a user within a state/dimension/persona + user_state_data = user_state_storage[user_state] + + # Optimize the state's chat module if we've reached the optimization threshold + examples = user_state_data["examples"] + if len(examples) >= optimization_threshold: + metric = None # TODO: Define this + + # Optimize chat module + optimizer = BootstrapFewShot(metric=metric) + compiled_chat_module = optimizer.compile(trainset=examples) + + user_state_data["chat_module"] = compiled_chat_module + + # use that pipeline to generate a response + chat_module = user_state_data["chat_module"] + chat_input = format_chat_history(chat_history, user_input=input) + + response = chat_module(user_message=user_message, session=session, input=chat_input) + + return response diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml new file mode 100644 index 0000000..ac6bdd1 --- /dev/null +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml @@ -0,0 +1,10 @@ +_type: prompt +input_variables: + ["existing_states", "state"] +template: > + Given the list of existing states, determine whether or not the new state is represented in the list of existing states. + + existing states: ```{existing_states}``` + new state: ```{state}``` + + If the new state represented in the existing states, return the existing state value. If the new state is NOT represented in existing states, return "None". Output a single value only. \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml new file mode 100644 index 0000000..d0fbf2e --- /dev/null +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml @@ -0,0 +1,8 @@ +_type: prompt +input_variables: + ["chat_history", "user_input"] +template: > + Your job is to make a prediction about the task the user might be engaging in. Some people might be researching, exploring curiosities, or just asking questions for general inquiry. Provide commentary that would shed light on the "mode" the user might be in. + + chat history: ```{chat_history}``` + user input: ```{user_input}``` \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml new file mode 100644 index 0000000..61e0353 --- /dev/null +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml @@ -0,0 +1,9 @@ +_type: prompt +input_variables: + ["state_commentary"] +template: > + Your job is to label the task the user might be engaging in. Some people might be conducting research, exploring a interest, or just asking questions for general inquiry. + + commentary: ```{state_commentary}``` + + Output your prediction as a concise, single word label. \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/metric.py b/example/discord/honcho-dspy-personas/metric.py new file mode 100644 index 0000000..9ff3ce5 --- /dev/null +++ b/example/discord/honcho-dspy-personas/metric.py @@ -0,0 +1,23 @@ +import dspy + +gpt4T = dspy.OpenAI(model='gpt-4-1106-preview', max_tokens=1000, model_type='chat') + +class MessageResponseAssess(dspy.Signature): + """Assess the quality of a response along the specified dimension.""" + user_message = dspy.InputField() + ai_response = dspy.InputField() + assessment_dimension = dspy.InputField() + assessment_answer = dspy.OutputField(desc="Good or not") + + +def assess_response_quality(user_message, ai_response, assessment_dimension): + with dspy.context(lm=gpt4T): + assessment_result = dspy.Predict(MessageResponseAssess)( + user_message=user_message, + ai_response=ai_response, + assessment_dimension=assessment_dimension + ) + + is_positive = assessment_result.assessment_answer.lower() == 'good' + + return is_positive \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/poetry.lock b/example/discord/honcho-dspy-personas/poetry.lock new file mode 100644 index 0000000..3e03a95 --- /dev/null +++ b/example/discord/honcho-dspy-personas/poetry.lock @@ -0,0 +1,2191 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.6" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, + {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, + {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, + {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, + {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, + {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, + {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, + {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, + {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, + {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, + {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, +] + +[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 = "alembic" +version = "1.13.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[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.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +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.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[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 = "colorlog" +version = "6.8.2" +description = "Add colours to the output of Python's logging module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, + {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "datasets" +version = "2.14.7" +description = "HuggingFace community-driven open-source library of datasets" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "datasets-2.14.7-py3-none-any.whl", hash = "sha256:1a64041a7da4f4130f736fc371c1f528b8ddd208cebe156400f65719bdbba79d"}, + {file = "datasets-2.14.7.tar.gz", hash = "sha256:394cf9b4ec0694b25945977b16ad5d18d5c15fb0e94141713eb8ead7452caf9e"}, +] + +[package.dependencies] +aiohttp = "*" +dill = ">=0.3.0,<0.3.8" +fsspec = {version = ">=2023.1.0,<=2023.10.0", extras = ["http"]} +huggingface-hub = ">=0.14.0,<1.0.0" +multiprocess = "*" +numpy = ">=1.17" +packaging = "*" +pandas = "*" +pyarrow = ">=8.0.0" +pyarrow-hotfix = "*" +pyyaml = ">=5.1" +requests = ">=2.19.0" +tqdm = ">=4.62.1" +xxhash = "*" + +[package.extras] +apache-beam = ["apache-beam (>=2.26.0,<2.44.0)"] +audio = ["librosa", "soundfile (>=0.12.1)"] +benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"] +dev = ["Pillow (>=6.2.1)", "absl-py", "apache-beam (>=2.26.0,<2.44.0)", "black (>=23.1,<24.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "pyyaml (>=5.3.1)", "rarfile (>=4.0)", "ruff (>=0.0.241)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy (<2.0.0)", "tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow (>=2.3,!=2.6.0,!=2.6.1)", "tensorflow-macos", "tiktoken", "torch", "transformers", "zstandard"] +docs = ["s3fs", "tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow-macos", "torch", "transformers"] +jax = ["jax (>=0.2.8,!=0.3.2,<=0.3.25)", "jaxlib (>=0.1.65,<=0.3.25)"] +metrics-tests = ["Werkzeug (>=1.0.1)", "accelerate", "bert-score (>=0.3.6)", "jiwer", "langdetect", "mauve-text", "nltk", "requests-file (>=1.5.1)", "rouge-score", "sacrebleu", "sacremoses", "scikit-learn", "scipy", "sentencepiece", "seqeval", "six (>=1.15.0,<1.16.0)", "spacy (>=3.0.0)", "texttable (>=1.6.3)", "tldextract", "tldextract (>=3.1.0)", "toml (>=0.10.1)", "typer (<0.5.0)"] +quality = ["black (>=23.1,<24.0)", "pyyaml (>=5.3.1)", "ruff (>=0.0.241)"] +s3 = ["s3fs"] +tensorflow = ["tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow-macos"] +tensorflow-gpu = ["tensorflow-gpu (>=2.2.0,!=2.6.0,!=2.6.1)"] +tests = ["Pillow (>=6.2.1)", "absl-py", "apache-beam (>=2.26.0,<2.44.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy (<2.0.0)", "tensorflow (>=2.3,!=2.6.0,!=2.6.1)", "tensorflow-macos", "tiktoken", "torch", "transformers", "zstandard"] +torch = ["torch"] +vision = ["Pillow (>=6.2.1)"] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "dspy-ai" +version = "2.1.10" +description = "DSPy" +optional = false +python-versions = ">=3.9" +files = [ + {file = "dspy-ai-2.1.10.tar.gz", hash = "sha256:d2a344c073fc84d0f3d4ec4546de7093b3aa070275369a82d169dd18df674d43"}, + {file = "dspy_ai-2.1.10-py3-none-any.whl", hash = "sha256:8d00609fbccc4433dce9ad9a97664b21ca1e24dd341d91147257985a52e76de0"}, +] + +[package.dependencies] +backoff = ">=2.2.1,<2.3.0" +datasets = ">=2.14.6,<2.15.0" +joblib = ">=1.3.2,<1.4.0" +openai = ">=0.28.1,<2.0.0" +optuna = ">=3.4.0,<3.5.0" +pandas = ">=2.1.1,<2.2.0" +regex = ">=2023.10.3,<2023.11.0" +requests = ">=2.31.0,<2.32.0" +tqdm = ">=4.66.1,<4.67.0" +ujson = ">=5.8.0,<5.9.0" + +[package.extras] +chromadb = ["chromadb (>=0.4.14,<0.5.0)"] +docs = ["autodoc-pydantic", "docutils (<0.17)", "furo (>=2023.3.27)", "m2r2", "myst-nb", "myst-parser", "pydantic (<2.0.0)", "sphinx (>=4.3.0)", "sphinx-autobuild", "sphinx-automodapi (==0.16.0)", "sphinx-reredirects (>=0.1.2)", "sphinx-rtd-theme"] +marqo = ["marqo"] +mongodb = ["pymongo (>=3.12.0,<3.13.0)"] +pinecone = ["pinecone-client (>=2.2.4,<2.3.0)"] +qdrant = ["fastembed (>=0.1.0,<0.2.0)", "qdrant-client (>=1.6.2,<1.7.0)"] +weaviate = ["weaviate-client (>=3.26.1,<3.27.0)"] + +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "fsspec" +version = "2023.10.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.10.0-py3-none-any.whl", hash = "sha256:346a8f024efeb749d2a5fca7ba8854474b1ff9af7c3faaf636a4548781136529"}, + {file = "fsspec-2023.10.0.tar.gz", hash = "sha256:330c66757591df346ad3091a53bd907e15348c2ba17d63fd54f5c39c4457d2a5"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} +requests = {version = "*", optional = true, markers = "extra == \"http\""} + +[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 = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +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 = "honcho-ai" +version = "0.0.3" +description = "Python Client SDK for Honcho" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "honcho_ai-0.0.3-py3-none-any.whl", hash = "sha256:a817ec62c4fd8dad1d629927511ce98a3f626f4bc55474187b80010e208e61ba"}, + {file = "honcho_ai-0.0.3.tar.gz", hash = "sha256:ca52bb8c5036bfdbeee0c71ca754c580c672b28a4824240123b783f8679ca18e"}, +] + +[package.dependencies] +httpx = ">=0.26.0,<0.27.0" + +[[package]] +name = "httpcore" +version = "1.0.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.24.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "huggingface-hub" +version = "0.20.3" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "huggingface_hub-0.20.3-py3-none-any.whl", hash = "sha256:d988ae4f00d3e307b0c80c6a05ca6dbb7edba8bba3079f74cda7d9c2e562a7b6"}, + {file = "huggingface_hub-0.20.3.tar.gz", hash = "sha256:94e7f8e074475fbc67d6a71957b678e1b4a74ff1b64a644fd6cbb83da962d05d"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +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", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +inference = ["aiohttp", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)"] +quality = ["mypy (==1.5.1)", "ruff (>=0.1.3)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "joblib" +version = "1.3.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "langchain-core" +version = "0.1.23" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_core-0.1.23-py3-none-any.whl", hash = "sha256:d42fac013c39a8b0bcd7e337a4cb6c17c16046c60d768f89df582ad73ec3c5cb"}, + {file = "langchain_core-0.1.23.tar.gz", hash = "sha256:34359cc8b6f8c3d45098c54a6a9b35c9f538ef58329cd943a2249d6d7b4e5806"}, +] + +[package.dependencies] +anyio = ">=3,<5" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.0.87,<0.0.88" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + +[[package]] +name = "langchain-openai" +version = "0.0.6" +description = "An integration package connecting OpenAI and LangChain" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_openai-0.0.6-py3-none-any.whl", hash = "sha256:2ef040e4447a26a9d3bd45dfac9cefa00797ea58555a3d91ab4f88699eb3a005"}, + {file = "langchain_openai-0.0.6.tar.gz", hash = "sha256:f5c4ebe46f2c8635c8f0c26cc8df27700aacafea025410e418d5a080039974dd"}, +] + +[package.dependencies] +langchain-core = ">=0.1.16,<0.2" +numpy = ">=1,<2" +openai = ">=1.10.0,<2.0.0" +tiktoken = ">=0.5.2,<1" + +[[package]] +name = "langsmith" +version = "0.0.87" +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.87-py3-none-any.whl", hash = "sha256:8903d3811b9fc89eb18f5961c8e6935fbd2d0f119884fbf30dc70b8f8f4121fc"}, + {file = "langsmith-0.0.87.tar.gz", hash = "sha256:36c4cc47e5b54be57d038036a30fb19ce6e4c73048cd7a464b8f25b459694d34"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + +[[package]] +name = "mako" +version = "1.3.2" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, + {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "multiprocess" +version = "0.70.15" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multiprocess-0.70.15-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aa36c7ed16f508091438687fe9baa393a7a8e206731d321e443745e743a0d4e5"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:20e024018c46d0d1602024c613007ac948f9754659e3853b0aa705e83f6931d8"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:e576062981c91f0fe8a463c3d52506e598dfc51320a8dd8d78b987dfca91c5db"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e73f497e6696a0f5433ada2b3d599ae733b87a6e8b008e387c62ac9127add177"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73db2e7b32dcc7f9b0f075c2ffa45c90b6729d3f1805f27e88534c8d321a1be5"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:4271647bd8a49c28ecd6eb56a7fdbd3c212c45529ad5303b40b3c65fc6928e5f"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cf981fb998d6ec3208cb14f0cf2e9e80216e834f5d51fd09ebc937c32b960902"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:18f9f2c7063346d1617bd1684fdcae8d33380ae96b99427260f562e1a1228b67"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:0eac53214d664c49a34695e5824872db4006b1a465edd7459a251809c3773370"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1a51dd34096db47fb21fa2b839e615b051d51b97af9a67afbcdaa67186b44883"}, + {file = "multiprocess-0.70.15-py310-none-any.whl", hash = "sha256:7dd58e33235e83cf09d625e55cffd7b0f0eede7ee9223cdd666a87624f60c21a"}, + {file = "multiprocess-0.70.15-py311-none-any.whl", hash = "sha256:134f89053d82c9ed3b73edd3a2531eb791e602d4f4156fc92a79259590bd9670"}, + {file = "multiprocess-0.70.15-py37-none-any.whl", hash = "sha256:f7d4a1629bccb433114c3b4885f69eccc200994323c80f6feee73b0edc9199c5"}, + {file = "multiprocess-0.70.15-py38-none-any.whl", hash = "sha256:bee9afba476c91f9ebee7beeee0601face9eff67d822e893f9a893725fbd6316"}, + {file = "multiprocess-0.70.15-py39-none-any.whl", hash = "sha256:3e0953f5d52b4c76f1c973eaf8214554d146f2be5decb48e928e55c7a2d19338"}, + {file = "multiprocess-0.70.15.tar.gz", hash = "sha256:f20eed3036c0ef477b07a4177cf7c1ba520d9a2677870a4f47fe026f0cd6787e"}, +] + +[package.dependencies] +dill = ">=0.3.7" + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "openai" +version = "1.12.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, + {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + +[[package]] +name = "optuna" +version = "3.4.0" +description = "A hyperparameter optimization framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "optuna-3.4.0-py3-none-any.whl", hash = "sha256:4854a6e6ec68eae3f1cbf18525abde1fcf2a22dd4f3b79912e5d43f539ba8eb1"}, + {file = "optuna-3.4.0.tar.gz", hash = "sha256:aa4a2d294ca047d7dc16292707c018cc619bdde1ec435d34721311428942d2db"}, +] + +[package.dependencies] +alembic = ">=1.5.0" +colorlog = "*" +numpy = "*" +packaging = ">=20.0" +PyYAML = "*" +sqlalchemy = ">=1.3.0" +tqdm = "*" + +[package.extras] +benchmark = ["asv (>=0.5.0)", "botorch", "cma", "scikit-optimize", "virtualenv"] +checking = ["black", "blackdoc", "flake8", "isort", "mypy", "mypy-boto3-s3", "types-PyYAML", "types-redis", "types-setuptools", "types-tqdm", "typing-extensions (>=3.10.0.0)"] +document = ["ase", "botorch", "cma", "cmaes (>=0.10.0)", "distributed", "fvcore", "lightgbm", "matplotlib (!=3.6.0)", "mlflow", "pandas", "pillow", "plotly (>=4.9.0)", "scikit-learn", "scikit-optimize", "sphinx", "sphinx-copybutton", "sphinx-gallery", "sphinx-plotly-directive", "sphinx-rtd-theme (>=1.2.0)", "torch", "torchaudio", "torchvision"] +integration = ["botorch (>=0.4.0)", "catboost (>=0.26)", "catboost (>=0.26,<1.2)", "cma", "distributed", "fastai", "lightgbm", "mlflow", "pandas", "pytorch-ignite", "pytorch-lightning (>=1.6.0)", "scikit-learn (>=0.24.2)", "scikit-optimize", "shap", "tensorflow", "torch", "torchaudio", "torchvision", "wandb", "xgboost"] +optional = ["boto3", "botorch", "cmaes (>=0.10.0)", "google-cloud-storage", "matplotlib (!=3.6.0)", "pandas", "plotly (>=4.9.0)", "redis", "scikit-learn (>=0.24.2)"] +test = ["coverage", "fakeredis[lua]", "kaleido", "moto", "pytest", "scipy (>=1.9.2)"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pandas" +version = "2.1.4" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b728fb8deba8905b319f96447a27033969f3ea1fea09d07d296c9030ab2ed1d"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00028e6737c594feac3c2df15636d73ace46b8314d236100b57ed7e4b9ebe8d9"}, + {file = "pandas-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:426dc0f1b187523c4db06f96fb5c8d1a845e259c99bda74f7de97bd8a3bb3139"}, + {file = "pandas-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:f237e6ca6421265643608813ce9793610ad09b40154a3344a088159590469e46"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7d852d16c270e4331f6f59b3e9aa23f935f5c4b0ed2d0bc77637a8890a5d092"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7d5f2f54f78164b3d7a40f33bf79a74cdee72c31affec86bfcabe7e0789821"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa6e92e639da0d6e2017d9ccff563222f4eb31e4b2c3cf32a2a392fc3103c0d"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d797591b6846b9db79e65dc2d0d48e61f7db8d10b2a9480b4e3faaddc421a171"}, + {file = "pandas-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2d3e7b00f703aea3945995ee63375c61b2e6aa5aa7871c5d622870e5e137623"}, + {file = "pandas-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc9bf7ade01143cddc0074aa6995edd05323974e6e40d9dbde081021ded8510e"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:482d5076e1791777e1571f2e2d789e940dedd927325cc3cb6d0800c6304082f6"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a706cfe7955c4ca59af8c7a0517370eafbd98593155b48f10f9811da440248b"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0513a132a15977b4a5b89aabd304647919bc2169eac4c8536afb29c07c23540"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f17f2b6fc076b2a0078862547595d66244db0f41bf79fc5f64a5c4d635bead"}, + {file = "pandas-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:45d63d2a9b1b37fa6c84a68ba2422dc9ed018bdaa668c7f47566a01188ceeec1"}, + {file = "pandas-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f69b0c9bb174a2342818d3e2778584e18c740d56857fc5cdb944ec8bbe4082cf"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f06bda01a143020bad20f7a85dd5f4a1600112145f126bc9e3e42077c24ef34"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab5796839eb1fd62a39eec2916d3e979ec3130509930fea17fe6f81e18108f6a"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbaf9e8d3a63a9276d707b4d25930a262341bca9874fcb22eff5e3da5394732"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ebfd771110b50055712b3b711b51bee5d50135429364d0498e1213a7adc2be8"}, + {file = "pandas-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ea107e0be2aba1da619cc6ba3f999b2bfc9669a83554b1904ce3dd9507f0860"}, + {file = "pandas-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:d65148b14788b3758daf57bf42725caa536575da2b64df9964c563b015230984"}, + {file = "pandas-2.1.4.tar.gz", hash = "sha256:fcb68203c833cc735321512e13861358079a96c174a61f5116a1de89c58c0ef7"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] + +[[package]] +name = "py-cord" +version = "2.4.1" +description = "A Python wrapper for the Discord API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "py-cord-2.4.1.tar.gz", hash = "sha256:0266c9d9a9d2397622a0e5ead09826690e688ba3cf14c470167b81e6cd2d8a56"}, + {file = "py_cord-2.4.1-py3-none-any.whl", hash = "sha256:862a372c364cd263e2c8e696c64887f969c02cbdf0fdd6b09f0283e9dd67a290"}, +] + +[package.dependencies] +aiohttp = ">=3.6.0,<3.9.0" + +[package.extras] +docs = ["furo", "myst-parser (==0.18.1)", "sphinx (==5.3.0)", "sphinx-autodoc-typehints (==1.22)", "sphinx-copybutton (==0.5.1)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "sphinxext-opengraph (==0.8.1)"] +speed = ["aiohttp[speedups]", "orjson (>=3.5.4)"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + +[[package]] +name = "pyarrow" +version = "15.0.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyarrow-15.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0a524532fd6dd482edaa563b686d754c70417c2f72742a8c990b322d4c03a15d"}, + {file = "pyarrow-15.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a6bdb314affa9c2e0d5dddf3d9cbb9ef4a8dddaa68669975287d47ece67642"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66958fd1771a4d4b754cd385835e66a3ef6b12611e001d4e5edfcef5f30391e2"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f500956a49aadd907eaa21d4fff75f73954605eaa41f61cb94fb008cf2e00c6"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6f87d9c4f09e049c2cade559643424da84c43a35068f2a1c4653dc5b1408a929"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85239b9f93278e130d86c0e6bb455dcb66fc3fd891398b9d45ace8799a871a1e"}, + {file = "pyarrow-15.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b8d43e31ca16aa6e12402fcb1e14352d0d809de70edd185c7650fe80e0769e3"}, + {file = "pyarrow-15.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:fa7cd198280dbd0c988df525e50e35b5d16873e2cdae2aaaa6363cdb64e3eec5"}, + {file = "pyarrow-15.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8780b1a29d3c8b21ba6b191305a2a607de2e30dab399776ff0aa09131e266340"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0ec198ccc680f6c92723fadcb97b74f07c45ff3fdec9dd765deb04955ccf19"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036a7209c235588c2f07477fe75c07e6caced9b7b61bb897c8d4e52c4b5f9555"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2bd8a0e5296797faf9a3294e9fa2dc67aa7f10ae2207920dbebb785c77e9dbe5"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e8ebed6053dbe76883a822d4e8da36860f479d55a762bd9e70d8494aed87113e"}, + {file = "pyarrow-15.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d53a9d1b2b5bd7d5e4cd84d018e2a45bc9baaa68f7e6e3ebed45649900ba99"}, + {file = "pyarrow-15.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9950a9c9df24090d3d558b43b97753b8f5867fb8e521f29876aa021c52fda351"}, + {file = "pyarrow-15.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:003d680b5e422d0204e7287bb3fa775b332b3fce2996aa69e9adea23f5c8f970"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f75fce89dad10c95f4bf590b765e3ae98bcc5ba9f6ce75adb828a334e26a3d40"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca9cb0039923bec49b4fe23803807e4ef39576a2bec59c32b11296464623dc2"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ed5a78ed29d171d0acc26a305a4b7f83c122d54ff5270810ac23c75813585e4"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6eda9e117f0402dfcd3cd6ec9bfee89ac5071c48fc83a84f3075b60efa96747f"}, + {file = "pyarrow-15.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a3a6180c0e8f2727e6f1b1c87c72d3254cac909e609f35f22532e4115461177"}, + {file = "pyarrow-15.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:19a8918045993349b207de72d4576af0191beef03ea655d8bdb13762f0cd6eac"}, + {file = "pyarrow-15.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0ec076b32bacb6666e8813a22e6e5a7ef1314c8069d4ff345efa6246bc38593"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db1769e5d0a77eb92344c7382d6543bea1164cca3704f84aa44e26c67e320fb"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2617e3bf9df2a00020dd1c1c6dce5cc343d979efe10bc401c0632b0eef6ef5b"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:d31c1d45060180131caf10f0f698e3a782db333a422038bf7fe01dace18b3a31"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:c8c287d1d479de8269398b34282e206844abb3208224dbdd7166d580804674b7"}, + {file = "pyarrow-15.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:07eb7f07dc9ecbb8dace0f58f009d3a29ee58682fcdc91337dfeb51ea618a75b"}, + {file = "pyarrow-15.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:47af7036f64fce990bb8a5948c04722e4e3ea3e13b1007ef52dfe0aa8f23cf7f"}, + {file = "pyarrow-15.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93768ccfff85cf044c418bfeeafce9a8bb0cee091bd8fd19011aff91e58de540"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6ee87fd6892700960d90abb7b17a72a5abb3b64ee0fe8db6c782bcc2d0dc0b4"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001fca027738c5f6be0b7a3159cc7ba16a5c52486db18160909a0831b063c4e4"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d1c48648f64aec09accf44140dccb92f4f94394b8d79976c426a5b79b11d4fa7"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:972a0141be402bb18e3201448c8ae62958c9c7923dfaa3b3d4530c835ac81aed"}, + {file = "pyarrow-15.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f01fc5cf49081426429127aa2d427d9d98e1cb94a32cb961d583a70b7c4504e6"}, + {file = "pyarrow-15.0.0.tar.gz", hash = "sha256:876858f549d540898f927eba4ef77cd549ad8d24baa3207cf1b72e5788b50e83"}, +] + +[package.dependencies] +numpy = ">=1.16.6,<2" + +[[package]] +name = "pyarrow-hotfix" +version = "0.6" +description = "" +optional = false +python-versions = ">=3.5" +files = [ + {file = "pyarrow_hotfix-0.6-py3-none-any.whl", hash = "sha256:dcc9ae2d220dff0083be6a9aa8e0cdee5182ad358d4931fce825c545e5c89178"}, + {file = "pyarrow_hotfix-0.6.tar.gz", hash = "sha256:79d3e030f7ff890d408a100ac16d6f00b14d44a502d7897cd9fc3e3a534e9945"}, +] + +[[package]] +name = "pydantic" +version = "2.6.1" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, + {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.2" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.2" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, + {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, + {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, + {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, + {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, + {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, + {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, + {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, + {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, + {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, + {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, + {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, + {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, + {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, +] + +[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 = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[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.10.3" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, +] + +[[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 = "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.27" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, + {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, + {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, + {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, + {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, + {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, + {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, + {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, + {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, +] + +[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.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +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 (>=8)"] +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 = "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.6.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:277de84ccd8fa12730a6b4067456e5cf72fef6300bea61d506c09e45658d41ac"}, + {file = "tiktoken-0.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c44433f658064463650d61387623735641dcc4b6c999ca30bc0f8ba3fccaf5c"}, + {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afb9a2a866ae6eef1995ab656744287a5ac95acc7e0491c33fad54d053288ad3"}, + {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c62c05b3109fefca26fedb2820452a050074ad8e5ad9803f4652977778177d9f"}, + {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ef917fad0bccda07bfbad835525bbed5f3ab97a8a3e66526e48cdc3e7beacf7"}, + {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e095131ab6092d0769a2fda85aa260c7c383072daec599ba9d8b149d2a3f4d8b"}, + {file = "tiktoken-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:05b344c61779f815038292a19a0c6eb7098b63c8f865ff205abb9ea1b656030e"}, + {file = "tiktoken-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cefb9870fb55dca9e450e54dbf61f904aab9180ff6fe568b61f4db9564e78871"}, + {file = "tiktoken-0.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:702950d33d8cabc039845674107d2e6dcabbbb0990ef350f640661368df481bb"}, + {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d49d076058f23254f2aff9af603863c5c5f9ab095bc896bceed04f8f0b013a"}, + {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:430bc4e650a2d23a789dc2cdca3b9e5e7eb3cd3935168d97d43518cbb1f9a911"}, + {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:293cb8669757301a3019a12d6770bd55bec38a4d3ee9978ddbe599d68976aca7"}, + {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bd1a288b7903aadc054b0e16ea78e3171f70b670e7372432298c686ebf9dd47"}, + {file = "tiktoken-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac76e000183e3b749634968a45c7169b351e99936ef46f0d2353cd0d46c3118d"}, + {file = "tiktoken-0.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17cc8a4a3245ab7d935c83a2db6bb71619099d7284b884f4b2aea4c74f2f83e3"}, + {file = "tiktoken-0.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:284aebcccffe1bba0d6571651317df6a5b376ff6cfed5aeb800c55df44c78177"}, + {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c1a3a5d33846f8cd9dd3b7897c1d45722f48625a587f8e6f3d3e85080559be8"}, + {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6318b2bb2337f38ee954fd5efa82632c6e5ced1d52a671370fa4b2eff1355e91"}, + {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f5f0f2ed67ba16373f9a6013b68da298096b27cd4e1cf276d2d3868b5c7efd1"}, + {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:75af4c0b16609c2ad02581f3cdcd1fb698c7565091370bf6c0cf8624ffaba6dc"}, + {file = "tiktoken-0.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:45577faf9a9d383b8fd683e313cf6df88b6076c034f0a16da243bb1c139340c3"}, + {file = "tiktoken-0.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c1492ab90c21ca4d11cef3a236ee31a3e279bb21b3fc5b0e2210588c4209e68"}, + {file = "tiktoken-0.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e2b380c5b7751272015400b26144a2bab4066ebb8daae9c3cd2a92c3b508fe5a"}, + {file = "tiktoken-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f497598b9f58c99cbc0eb764b4a92272c14d5203fc713dd650b896a03a50ad"}, + {file = "tiktoken-0.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e65e8bd6f3f279d80f1e1fbd5f588f036b9a5fa27690b7f0cc07021f1dfa0839"}, + {file = "tiktoken-0.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5f1495450a54e564d236769d25bfefbf77727e232d7a8a378f97acddee08c1ae"}, + {file = "tiktoken-0.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6c4e4857d99f6fb4670e928250835b21b68c59250520a1941618b5b4194e20c3"}, + {file = "tiktoken-0.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:168d718f07a39b013032741867e789971346df8e89983fe3c0ef3fbd5a0b1cb9"}, + {file = "tiktoken-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:47fdcfe11bd55376785a6aea8ad1db967db7f66ea81aed5c43fad497521819a4"}, + {file = "tiktoken-0.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fb7d2ccbf1a7784810aff6b80b4012fb42c6fc37eaa68cb3b553801a5cc2d1fc"}, + {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ccb7a111ee76af5d876a729a347f8747d5ad548e1487eeea90eaf58894b3138"}, + {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2048e1086b48e3c8c6e2ceeac866561374cd57a84622fa49a6b245ffecb7744"}, + {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07f229a5eb250b6403a61200199cecf0aac4aa23c3ecc1c11c1ca002cbb8f159"}, + {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:432aa3be8436177b0db5a2b3e7cc28fd6c693f783b2f8722539ba16a867d0c6a"}, + {file = "tiktoken-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:8bfe8a19c8b5c40d121ee7938cd9c6a278e5b97dc035fd61714b4f0399d2f7a1"}, + {file = "tiktoken-0.6.0.tar.gz", hash = "sha256:ace62a4ede83c75b0374a2ddfa4b76903cf483e9cb06247f566be3bf14e6beed"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, +] + +[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.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "ujson" +version = "5.8.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ujson-5.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4511560d75b15ecb367eef561554959b9d49b6ec3b8d5634212f9fed74a6df1"}, + {file = "ujson-5.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9399eaa5d1931a0ead49dce3ffacbea63f3177978588b956036bfe53cdf6af75"}, + {file = "ujson-5.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4e7bb7eba0e1963f8b768f9c458ecb193e5bf6977090182e2b4f4408f35ac76"}, + {file = "ujson-5.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40931d7c08c4ce99adc4b409ddb1bbb01635a950e81239c2382cfe24251b127a"}, + {file = "ujson-5.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d53039d39de65360e924b511c7ca1a67b0975c34c015dd468fca492b11caa8f7"}, + {file = "ujson-5.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bdf04c6af3852161be9613e458a1fb67327910391de8ffedb8332e60800147a2"}, + {file = "ujson-5.8.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a70f776bda2e5072a086c02792c7863ba5833d565189e09fabbd04c8b4c3abba"}, + {file = "ujson-5.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f26629ac531d712f93192c233a74888bc8b8212558bd7d04c349125f10199fcf"}, + {file = "ujson-5.8.0-cp310-cp310-win32.whl", hash = "sha256:7ecc33b107ae88405aebdb8d82c13d6944be2331ebb04399134c03171509371a"}, + {file = "ujson-5.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:3b27a8da7a080add559a3b73ec9ebd52e82cc4419f7c6fb7266e62439a055ed0"}, + {file = "ujson-5.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:193349a998cd821483a25f5df30b44e8f495423840ee11b3b28df092ddfd0f7f"}, + {file = "ujson-5.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ddeabbc78b2aed531f167d1e70387b151900bc856d61e9325fcdfefb2a51ad8"}, + {file = "ujson-5.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ce24909a9c25062e60653073dd6d5e6ec9d6ad7ed6e0069450d5b673c854405"}, + {file = "ujson-5.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a2a3c7620ebe43641e926a1062bc04e92dbe90d3501687957d71b4bdddaec4"}, + {file = "ujson-5.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b852bdf920fe9f84e2a2c210cc45f1b64f763b4f7d01468b33f7791698e455e"}, + {file = "ujson-5.8.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:20768961a6a706170497129960762ded9c89fb1c10db2989c56956b162e2a8a3"}, + {file = "ujson-5.8.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e0147d41e9fb5cd174207c4a2895c5e24813204499fd0839951d4c8784a23bf5"}, + {file = "ujson-5.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e3673053b036fd161ae7a5a33358ccae6793ee89fd499000204676baafd7b3aa"}, + {file = "ujson-5.8.0-cp311-cp311-win32.whl", hash = "sha256:a89cf3cd8bf33a37600431b7024a7ccf499db25f9f0b332947fbc79043aad879"}, + {file = "ujson-5.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3659deec9ab9eb19e8646932bfe6fe22730757c4addbe9d7d5544e879dc1b721"}, + {file = "ujson-5.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:102bf31c56f59538cccdfec45649780ae00657e86247c07edac434cb14d5388c"}, + {file = "ujson-5.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:299a312c3e85edee1178cb6453645217ba23b4e3186412677fa48e9a7f986de6"}, + {file = "ujson-5.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e385a7679b9088d7bc43a64811a7713cc7c33d032d020f757c54e7d41931ae"}, + {file = "ujson-5.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad24ec130855d4430a682c7a60ca0bc158f8253ec81feed4073801f6b6cb681b"}, + {file = "ujson-5.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16fde596d5e45bdf0d7de615346a102510ac8c405098e5595625015b0d4b5296"}, + {file = "ujson-5.8.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6d230d870d1ce03df915e694dcfa3f4e8714369cce2346686dbe0bc8e3f135e7"}, + {file = "ujson-5.8.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9571de0c53db5cbc265945e08f093f093af2c5a11e14772c72d8e37fceeedd08"}, + {file = "ujson-5.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7cba16b26efe774c096a5e822e4f27097b7c81ed6fb5264a2b3f5fd8784bab30"}, + {file = "ujson-5.8.0-cp312-cp312-win32.whl", hash = "sha256:48c7d373ff22366eecfa36a52b9b55b0ee5bd44c2b50e16084aa88b9de038916"}, + {file = "ujson-5.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ac97b1e182d81cf395ded620528c59f4177eee024b4b39a50cdd7b720fdeec6"}, + {file = "ujson-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2a64cc32bb4a436e5813b83f5aab0889927e5ea1788bf99b930fad853c5625cb"}, + {file = "ujson-5.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e54578fa8838ddc722539a752adfce9372474114f8c127bb316db5392d942f8b"}, + {file = "ujson-5.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9721cd112b5e4687cb4ade12a7b8af8b048d4991227ae8066d9c4b3a6642a582"}, + {file = "ujson-5.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d9707e5aacf63fb919f6237d6490c4e0244c7f8d3dc2a0f84d7dec5db7cb54c"}, + {file = "ujson-5.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0be81bae295f65a6896b0c9030b55a106fb2dec69ef877253a87bc7c9c5308f7"}, + {file = "ujson-5.8.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae7f4725c344bf437e9b881019c558416fe84ad9c6b67426416c131ad577df67"}, + {file = "ujson-5.8.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9ab282d67ef3097105552bf151438b551cc4bedb3f24d80fada830f2e132aeb9"}, + {file = "ujson-5.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94c7bd9880fa33fcf7f6d7f4cc032e2371adee3c5dba2922b918987141d1bf07"}, + {file = "ujson-5.8.0-cp38-cp38-win32.whl", hash = "sha256:bf5737dbcfe0fa0ac8fa599eceafae86b376492c8f1e4b84e3adf765f03fb564"}, + {file = "ujson-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:11da6bed916f9bfacf13f4fc6a9594abd62b2bb115acfb17a77b0f03bee4cfd5"}, + {file = "ujson-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:69b3104a2603bab510497ceabc186ba40fef38ec731c0ccaa662e01ff94a985c"}, + {file = "ujson-5.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9249fdefeb021e00b46025e77feed89cd91ffe9b3a49415239103fc1d5d9c29a"}, + {file = "ujson-5.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2873d196725a8193f56dde527b322c4bc79ed97cd60f1d087826ac3290cf9207"}, + {file = "ujson-5.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4dafa9010c366589f55afb0fd67084acd8added1a51251008f9ff2c3e44042"}, + {file = "ujson-5.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a42baa647a50fa8bed53d4e242be61023bd37b93577f27f90ffe521ac9dc7a3"}, + {file = "ujson-5.8.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f3554eaadffe416c6f543af442066afa6549edbc34fe6a7719818c3e72ebfe95"}, + {file = "ujson-5.8.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fb87decf38cc82bcdea1d7511e73629e651bdec3a43ab40985167ab8449b769c"}, + {file = "ujson-5.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:407d60eb942c318482bbfb1e66be093308bb11617d41c613e33b4ce5be789adc"}, + {file = "ujson-5.8.0-cp39-cp39-win32.whl", hash = "sha256:0fe1b7edaf560ca6ab023f81cbeaf9946a240876a993b8c5a21a1c539171d903"}, + {file = "ujson-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f9b63530a5392eb687baff3989d0fb5f45194ae5b1ca8276282fb647f8dcdb3"}, + {file = "ujson-5.8.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:efeddf950fb15a832376c0c01d8d7713479fbeceaed1eaecb2665aa62c305aec"}, + {file = "ujson-5.8.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d8283ac5d03e65f488530c43d6610134309085b71db4f675e9cf5dff96a8282"}, + {file = "ujson-5.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb0142f6f10f57598655340a3b2c70ed4646cbe674191da195eb0985a9813b83"}, + {file = "ujson-5.8.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d459aca895eb17eb463b00441986b021b9312c6c8cc1d06880925c7f51009c"}, + {file = "ujson-5.8.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d524a8c15cfc863705991d70bbec998456a42c405c291d0f84a74ad7f35c5109"}, + {file = "ujson-5.8.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6f84a7a175c75beecde53a624881ff618e9433045a69fcfb5e154b73cdaa377"}, + {file = "ujson-5.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b748797131ac7b29826d1524db1cc366d2722ab7afacc2ce1287cdafccddbf1f"}, + {file = "ujson-5.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e72ba76313d48a1a3a42e7dc9d1db32ea93fac782ad8dde6f8b13e35c229130"}, + {file = "ujson-5.8.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f504117a39cb98abba4153bf0b46b4954cc5d62f6351a14660201500ba31fe7f"}, + {file = "ujson-5.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8c91b6f4bf23f274af9002b128d133b735141e867109487d17e344d38b87d94"}, + {file = "ujson-5.8.0.tar.gz", hash = "sha256:78e318def4ade898a461b3d92a79f9441e7e0e4d2ad5419abed4336d702c7425"}, +] + +[[package]] +name = "urllib3" +version = "2.2.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "xxhash" +version = "3.4.1" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +files = [ + {file = "xxhash-3.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91dbfa55346ad3e18e738742236554531a621042e419b70ad8f3c1d9c7a16e7f"}, + {file = "xxhash-3.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:665a65c2a48a72068fcc4d21721510df5f51f1142541c890491afc80451636d2"}, + {file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb11628470a6004dc71a09fe90c2f459ff03d611376c1debeec2d648f44cb693"}, + {file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bef2a7dc7b4f4beb45a1edbba9b9194c60a43a89598a87f1a0226d183764189"}, + {file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0f7b2d547d72c7eda7aa817acf8791f0146b12b9eba1d4432c531fb0352228"}, + {file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00f2fdef6b41c9db3d2fc0e7f94cb3db86693e5c45d6de09625caad9a469635b"}, + {file = "xxhash-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23cfd9ca09acaf07a43e5a695143d9a21bf00f5b49b15c07d5388cadf1f9ce11"}, + {file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a9ff50a3cf88355ca4731682c168049af1ca222d1d2925ef7119c1a78e95b3b"}, + {file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f1d7c69a1e9ca5faa75546fdd267f214f63f52f12692f9b3a2f6467c9e67d5e7"}, + {file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:672b273040d5d5a6864a36287f3514efcd1d4b1b6a7480f294c4b1d1ee1b8de0"}, + {file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4178f78d70e88f1c4a89ff1ffe9f43147185930bb962ee3979dba15f2b1cc799"}, + {file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9804b9eb254d4b8cc83ab5a2002128f7d631dd427aa873c8727dba7f1f0d1c2b"}, + {file = "xxhash-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c09c49473212d9c87261d22c74370457cfff5db2ddfc7fd1e35c80c31a8c14ce"}, + {file = "xxhash-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ebbb1616435b4a194ce3466d7247df23499475c7ed4eb2681a1fa42ff766aff6"}, + {file = "xxhash-3.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:25dc66be3db54f8a2d136f695b00cfe88018e59ccff0f3b8f545869f376a8a46"}, + {file = "xxhash-3.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58c49083801885273e262c0f5bbeac23e520564b8357fbb18fb94ff09d3d3ea5"}, + {file = "xxhash-3.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b526015a973bfbe81e804a586b703f163861da36d186627e27524f5427b0d520"}, + {file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ad4457644c91a966f6fe137d7467636bdc51a6ce10a1d04f365c70d6a16d7e"}, + {file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:248d3e83d119770f96003271fe41e049dd4ae52da2feb8f832b7a20e791d2920"}, + {file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2070b6d5bbef5ee031666cf21d4953c16e92c2f8a24a94b5c240f8995ba3b1d0"}, + {file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2746035f518f0410915e247877f7df43ef3372bf36cfa52cc4bc33e85242641"}, + {file = "xxhash-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ba6181514681c2591840d5632fcf7356ab287d4aff1c8dea20f3c78097088"}, + {file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aac5010869240e95f740de43cd6a05eae180c59edd182ad93bf12ee289484fa"}, + {file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4cb11d8debab1626181633d184b2372aaa09825bde709bf927704ed72765bed1"}, + {file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b29728cff2c12f3d9f1d940528ee83918d803c0567866e062683f300d1d2eff3"}, + {file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a15cbf3a9c40672523bdb6ea97ff74b443406ba0ab9bca10ceccd9546414bd84"}, + {file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e66df260fed01ed8ea790c2913271641c58481e807790d9fca8bfd5a3c13844"}, + {file = "xxhash-3.4.1-cp311-cp311-win32.whl", hash = "sha256:e867f68a8f381ea12858e6d67378c05359d3a53a888913b5f7d35fbf68939d5f"}, + {file = "xxhash-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:200a5a3ad9c7c0c02ed1484a1d838b63edcf92ff538770ea07456a3732c577f4"}, + {file = "xxhash-3.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:1d03f1c0d16d24ea032e99f61c552cb2b77d502e545187338bea461fde253583"}, + {file = "xxhash-3.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4bbba9b182697a52bc0c9f8ec0ba1acb914b4937cd4a877ad78a3b3eeabefb3"}, + {file = "xxhash-3.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9fd28a9da300e64e434cfc96567a8387d9a96e824a9be1452a1e7248b7763b78"}, + {file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6066d88c9329ab230e18998daec53d819daeee99d003955c8db6fc4971b45ca3"}, + {file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93805bc3233ad89abf51772f2ed3355097a5dc74e6080de19706fc447da99cd3"}, + {file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64da57d5ed586ebb2ecdde1e997fa37c27fe32fe61a656b77fabbc58e6fbff6e"}, + {file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97322e9a7440bf3c9805cbaac090358b43f650516486746f7fa482672593df"}, + {file = "xxhash-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe750d512982ee7d831838a5dee9e9848f3fb440e4734cca3f298228cc957a6"}, + {file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fd79d4087727daf4d5b8afe594b37d611ab95dc8e29fe1a7517320794837eb7d"}, + {file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:743612da4071ff9aa4d055f3f111ae5247342931dedb955268954ef7201a71ff"}, + {file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b41edaf05734092f24f48c0958b3c6cbaaa5b7e024880692078c6b1f8247e2fc"}, + {file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a90356ead70d715fe64c30cd0969072de1860e56b78adf7c69d954b43e29d9fa"}, + {file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac56eebb364e44c85e1d9e9cc5f6031d78a34f0092fea7fc80478139369a8b4a"}, + {file = "xxhash-3.4.1-cp312-cp312-win32.whl", hash = "sha256:911035345932a153c427107397c1518f8ce456f93c618dd1c5b54ebb22e73747"}, + {file = "xxhash-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:f31ce76489f8601cc7b8713201ce94b4bd7b7ce90ba3353dccce7e9e1fee71fa"}, + {file = "xxhash-3.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:b5beb1c6a72fdc7584102f42c4d9df232ee018ddf806e8c90906547dfb43b2da"}, + {file = "xxhash-3.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6d42b24d1496deb05dee5a24ed510b16de1d6c866c626c2beb11aebf3be278b9"}, + {file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b685fab18876b14a8f94813fa2ca80cfb5ab6a85d31d5539b7cd749ce9e3624"}, + {file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419ffe34c17ae2df019a4685e8d3934d46b2e0bbe46221ab40b7e04ed9f11137"}, + {file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e041ce5714f95251a88670c114b748bca3bf80cc72400e9f23e6d0d59cf2681"}, + {file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc860d887c5cb2f524899fb8338e1bb3d5789f75fac179101920d9afddef284b"}, + {file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:312eba88ffe0a05e332e3a6f9788b73883752be63f8588a6dc1261a3eaaaf2b2"}, + {file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e01226b6b6a1ffe4e6bd6d08cfcb3ca708b16f02eb06dd44f3c6e53285f03e4f"}, + {file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9f3025a0d5d8cf406a9313cd0d5789c77433ba2004b1c75439b67678e5136537"}, + {file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6d3472fd4afef2a567d5f14411d94060099901cd8ce9788b22b8c6f13c606a93"}, + {file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:43984c0a92f06cac434ad181f329a1445017c33807b7ae4f033878d860a4b0f2"}, + {file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a55e0506fdb09640a82ec4f44171273eeabf6f371a4ec605633adb2837b5d9d5"}, + {file = "xxhash-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:faec30437919555b039a8bdbaba49c013043e8f76c999670aef146d33e05b3a0"}, + {file = "xxhash-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c9e1b646af61f1fc7083bb7b40536be944f1ac67ef5e360bca2d73430186971a"}, + {file = "xxhash-3.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:961d948b7b1c1b6c08484bbce3d489cdf153e4122c3dfb07c2039621243d8795"}, + {file = "xxhash-3.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:719a378930504ab159f7b8e20fa2aa1896cde050011af838af7e7e3518dd82de"}, + {file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74fb5cb9406ccd7c4dd917f16630d2e5e8cbbb02fc2fca4e559b2a47a64f4940"}, + {file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dab508ac39e0ab988039bc7f962c6ad021acd81fd29145962b068df4148c476"}, + {file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c59f3e46e7daf4c589e8e853d700ef6607afa037bfad32c390175da28127e8c"}, + {file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc07256eff0795e0f642df74ad096f8c5d23fe66bc138b83970b50fc7f7f6c5"}, + {file = "xxhash-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f749999ed80f3955a4af0eb18bb43993f04939350b07b8dd2f44edc98ffee9"}, + {file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7688d7c02149a90a3d46d55b341ab7ad1b4a3f767be2357e211b4e893efbaaf6"}, + {file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a8b4977963926f60b0d4f830941c864bed16aa151206c01ad5c531636da5708e"}, + {file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8106d88da330f6535a58a8195aa463ef5281a9aa23b04af1848ff715c4398fb4"}, + {file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4c76a77dbd169450b61c06fd2d5d436189fc8ab7c1571d39265d4822da16df22"}, + {file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:11f11357c86d83e53719c592021fd524efa9cf024dc7cb1dfb57bbbd0d8713f2"}, + {file = "xxhash-3.4.1-cp38-cp38-win32.whl", hash = "sha256:0c786a6cd74e8765c6809892a0d45886e7c3dc54de4985b4a5eb8b630f3b8e3b"}, + {file = "xxhash-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:aabf37fb8fa27430d50507deeab2ee7b1bcce89910dd10657c38e71fee835594"}, + {file = "xxhash-3.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6127813abc1477f3a83529b6bbcfeddc23162cece76fa69aee8f6a8a97720562"}, + {file = "xxhash-3.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef2e194262f5db16075caea7b3f7f49392242c688412f386d3c7b07c7733a70a"}, + {file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71be94265b6c6590f0018bbf73759d21a41c6bda20409782d8117e76cd0dfa8b"}, + {file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10e0a619cdd1c0980e25eb04e30fe96cf8f4324758fa497080af9c21a6de573f"}, + {file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa122124d2e3bd36581dd78c0efa5f429f5220313479fb1072858188bc2d5ff1"}, + {file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17032f5a4fea0a074717fe33477cb5ee723a5f428de7563e75af64bfc1b1e10"}, + {file = "xxhash-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca7783b20e3e4f3f52f093538895863f21d18598f9a48211ad757680c3bd006f"}, + {file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d77d09a1113899fad5f354a1eb4f0a9afcf58cefff51082c8ad643ff890e30cf"}, + {file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:21287bcdd299fdc3328cc0fbbdeaa46838a1c05391264e51ddb38a3f5b09611f"}, + {file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:dfd7a6cc483e20b4ad90224aeb589e64ec0f31e5610ab9957ff4314270b2bf31"}, + {file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:543c7fcbc02bbb4840ea9915134e14dc3dc15cbd5a30873a7a5bf66039db97ec"}, + {file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fe0a98d990e433013f41827b62be9ab43e3cf18e08b1483fcc343bda0d691182"}, + {file = "xxhash-3.4.1-cp39-cp39-win32.whl", hash = "sha256:b9097af00ebf429cc7c0e7d2fdf28384e4e2e91008130ccda8d5ae653db71e54"}, + {file = "xxhash-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:d699b921af0dcde50ab18be76c0d832f803034d80470703700cb7df0fbec2832"}, + {file = "xxhash-3.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:2be491723405e15cc099ade1280133ccfbf6322d2ef568494fb7d07d280e7eee"}, + {file = "xxhash-3.4.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:431625fad7ab5649368c4849d2b49a83dc711b1f20e1f7f04955aab86cd307bc"}, + {file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6dbd5fc3c9886a9e041848508b7fb65fd82f94cc793253990f81617b61fe49"}, + {file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ff8dbd0ec97aec842476cb8ccc3e17dd288cd6ce3c8ef38bff83d6eb927817"}, + {file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef73a53fe90558a4096e3256752268a8bdc0322f4692ed928b6cd7ce06ad4fe3"}, + {file = "xxhash-3.4.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:450401f42bbd274b519d3d8dcf3c57166913381a3d2664d6609004685039f9d3"}, + {file = "xxhash-3.4.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a162840cf4de8a7cd8720ff3b4417fbc10001eefdd2d21541a8226bb5556e3bb"}, + {file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b736a2a2728ba45017cb67785e03125a79d246462dfa892d023b827007412c52"}, + {file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0ae4c2e7698adef58710d6e7a32ff518b66b98854b1c68e70eee504ad061d8"}, + {file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6322c4291c3ff174dcd104fae41500e75dad12be6f3085d119c2c8a80956c51"}, + {file = "xxhash-3.4.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:dd59ed668801c3fae282f8f4edadf6dc7784db6d18139b584b6d9677ddde1b6b"}, + {file = "xxhash-3.4.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92693c487e39523a80474b0394645b393f0ae781d8db3474ccdcead0559ccf45"}, + {file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4603a0f642a1e8d7f3ba5c4c25509aca6a9c1cc16f85091004a7028607ead663"}, + {file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa45e8cbfbadb40a920fe9ca40c34b393e0b067082d94006f7f64e70c7490a6"}, + {file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:595b252943b3552de491ff51e5bb79660f84f033977f88f6ca1605846637b7c6"}, + {file = "xxhash-3.4.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:562d8b8f783c6af969806aaacf95b6c7b776929ae26c0cd941d54644ea7ef51e"}, + {file = "xxhash-3.4.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41ddeae47cf2828335d8d991f2d2b03b0bdc89289dc64349d712ff8ce59d0647"}, + {file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c44d584afdf3c4dbb3277e32321d1a7b01d6071c1992524b6543025fb8f4206f"}, + {file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7bddb3a5b86213cc3f2c61500c16945a1b80ecd572f3078ddbbe68f9dabdfb"}, + {file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ecb6c987b62437c2f99c01e97caf8d25660bf541fe79a481d05732e5236719c"}, + {file = "xxhash-3.4.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:696b4e18b7023527d5c50ed0626ac0520edac45a50ec7cf3fc265cd08b1f4c03"}, + {file = "xxhash-3.4.1.tar.gz", hash = "sha256:0379d6cf1ff987cd421609a264ce025e74f346e3e145dd106c0cc2e3ec3f99a9"}, +] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "86f366fc87ad2958cf4b80fc161a0c5eb374c34119d938c78821ad2fabdb2e72" diff --git a/example/discord/honcho-dspy-personas/pyproject.toml b/example/discord/honcho-dspy-personas/pyproject.toml new file mode 100644 index 0000000..74275ea --- /dev/null +++ b/example/discord/honcho-dspy-personas/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "honcho-dspy-personas" +version = "0.1.0" +description = "" +authors = ["vintro "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +honcho-ai = "^0.0.3" +dspy-ai = "^2.1.10" +python-dotenv = "^1.0.1" +langchain-core = "^0.1.23" +langchain-openai = "^0.0.6" +py-cord = "^2.4.1" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 53d952ff04926922a2d79de8f356c32484f05304 Mon Sep 17 00:00:00 2001 From: vintro Date: Tue, 20 Feb 2024 16:51:35 -0500 Subject: [PATCH 32/85] working, hit token limit and can't test dspy optimization --- example/discord/honcho-dspy-personas/bot.py | 9 +++--- example/discord/honcho-dspy-personas/chain.py | 28 ++++++++++-------- example/discord/honcho-dspy-personas/graph.py | 29 ++++++++++--------- .../langchain_prompts/state_check.yaml | 2 +- .../discord/honcho-dspy-personas/poetry.lock | 16 +++++----- .../honcho-dspy-personas/pyproject.toml | 1 + .../{metric.py => response_metric.py} | 3 +- 7 files changed, 49 insertions(+), 39 deletions(-) rename example/discord/honcho-dspy-personas/{metric.py => response_metric.py} (84%) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index caab5ee..a79d7e9 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -2,15 +2,16 @@ from uuid import uuid1 import discord from honcho import Client as HonchoClient -from graph import langchain_message_converter, chat - +from graph import chat +from chain import langchain_message_converter intents = discord.Intents.default() intents.messages = True intents.message_content = True intents.members = True -app_id = "vince/dspy-personas" +# app_id = str(uuid1()) +app_id = "vince-dspy-personas" #honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev @@ -49,7 +50,7 @@ async def on_message(message): else: session = honcho.create_session(user_id, location_id) - history = list(session.get_messages(page_size=10)) + history = list(session.get_messages_generator()) chat_history = langchain_message_converter(history) inp = message.content diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index 8d19707..43c7b5e 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -36,9 +36,9 @@ class StateExtractor: """Wrapper class for all the DSPy and LangChain code for user state labeling and pipeline optimization""" lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name = "gpt-4") lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name = "gpt-3.5-turbo") - system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_COMMENTARY) - system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_LABELING) - system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(SYSTEM_STATE_CHECK) + system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_COMMENTARY) + system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_LABELING) + system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_CHECK) def __init__(self) -> None: pass @@ -61,7 +61,7 @@ async def generate_state_commentary(cls, chat_history: List[Message], input: str return response.content @classmethod - async def generate_state_label(cls, state_commetary: str) -> str: + async def generate_state_label(cls, state_commentary: str) -> str: """Generate a state label from a commetary on the user's state""" # format prompt state_labeling = ChatPromptTemplate.from_messages([ @@ -71,7 +71,7 @@ async def generate_state_label(cls, state_commetary: str) -> str: chain = state_labeling | cls.lc_gpt_4 # inference response = await chain.ainvoke({ - "state_commetary": state_commetary + "state_commentary": state_commentary }) # return output return response.content @@ -95,20 +95,24 @@ async def check_state_exists(cls, existing_states: List[str], state: str): "state": state, }) # return output - return response.output + return response.content @classmethod async def generate_state(cls, existing_states: List[str], chat_history: List[Message], input: str): """"Determine the user's state from the current conversation state""" # Generate label - state_commetary = cls.generate_state_commentary(chat_history, input) - state_label = cls.generate_state_label(state_commetary) + state_commentary = await cls.generate_state_commentary(chat_history, input) + state_label = await cls.generate_state_label(state_commentary) # Determine if state is new - existing_state = cls.check_state_exists(existing_states, state_label) - is_state_new = existing_state is None + # if True, it doesn't exist, state is new + # if False, it does exist, state is not new, existing_state was returned + existing_state = await cls.check_state_exists(existing_states, state_label) + is_state_new = existing_state == "None" # return existing state if we found one - return is_state_new, existing_state or state_label - \ No newline at end of file + if is_state_new: + return is_state_new, state_label + else: + return is_state_new, existing_state diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 6283a0e..9976371 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -4,6 +4,7 @@ from dspy.teleprompt import BootstrapFewShot from dotenv import load_dotenv from chain import StateExtractor, format_chat_history +from response_metric import metric from honcho import Message, Session @@ -47,37 +48,39 @@ def forward(self, user_message: Message, session: Session, chat_input: str): user_state_storage = {} async def chat(user_message: Message, session: Session, chat_history: List[Message], input: str, optimization_threshold=5): - # first we need to take the user input and determine the user's state/dimension/persona - is_state_new, user_state = await StateExtractor.generate_state(chat_history, input) + # first we need to see if the user has any existing states + existing_states = list(user_state_storage.keys()) + + # then we need to take the user input and determine the user's state/dimension/persona + is_state_new, user_state = await StateExtractor.generate_state(existing_states=existing_states, chat_history=chat_history, input=input) + print(f"USER STATE: {user_state}") + print(f"IS STATE NEW: {is_state_new}") + + user_chat_module = ChatWithThought() # Save the user_state if it's new if is_state_new: user_state_storage[user_state] = { - "chat_module": ChatWithThought(), + "chat_module": {}, "examples": [] } - # then, we need to select the pipeline for that derived state/dimension/persona - # way this would work is to define the optimizer and optimize a chain once examples in a certain dimension exceed a threshold - # need a way to store the optimized chain and call it given a state/dimension/persona - # this is the reward model for a user within a state/dimension/persona user_state_data = user_state_storage[user_state] # Optimize the state's chat module if we've reached the optimization threshold examples = user_state_data["examples"] if len(examples) >= optimization_threshold: - metric = None # TODO: Define this - # Optimize chat module optimizer = BootstrapFewShot(metric=metric) - compiled_chat_module = optimizer.compile(trainset=examples) + compiled_chat_module = optimizer.compile(user_chat_module, trainset=examples) + + user_state_data["chat_module"] = compiled_chat_module.dump_state() + user_chat_module = compiled_chat_module - user_state_data["chat_module"] = compiled_chat_module # use that pipeline to generate a response - chat_module = user_state_data["chat_module"] chat_input = format_chat_history(chat_history, user_input=input) - response = chat_module(user_message=user_message, session=session, input=chat_input) + response = user_chat_module(user_message=user_message, session=session, chat_input=chat_input) return response diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml index ac6bdd1..6fe2f0f 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml @@ -7,4 +7,4 @@ template: > existing states: ```{existing_states}``` new state: ```{state}``` - If the new state represented in the existing states, return the existing state value. If the new state is NOT represented in existing states, return "None". Output a single value only. \ No newline at end of file + If the new state is sufficiently similar to a value in the list of existing states, return that existing state value. If the new state is NOT sufficiently similar to anything in existing states, return "None". Output a single value only. \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/poetry.lock b/example/discord/honcho-dspy-personas/poetry.lock index 3e03a95..7329a15 100644 --- a/example/discord/honcho-dspy-personas/poetry.lock +++ b/example/discord/honcho-dspy-personas/poetry.lock @@ -814,19 +814,19 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.23" +version = "0.1.24" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.23-py3-none-any.whl", hash = "sha256:d42fac013c39a8b0bcd7e337a4cb6c17c16046c60d768f89df582ad73ec3c5cb"}, - {file = "langchain_core-0.1.23.tar.gz", hash = "sha256:34359cc8b6f8c3d45098c54a6a9b35c9f538ef58329cd943a2249d6d7b4e5806"}, + {file = "langchain_core-0.1.24-py3-none-any.whl", hash = "sha256:1887bb2e0c12e0d94c1e805eb56d08dbb670232daf0906761f726bd507324319"}, + {file = "langchain_core-0.1.24.tar.gz", hash = "sha256:ce70f4b97695eb55637e00ee33d480fffc6db1f95726f99b076b55cb1a42927d"}, ] [package.dependencies] anyio = ">=3,<5" jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.0.87,<0.0.88" +langsmith = ">=0.1.0,<0.2.0" packaging = ">=23.2,<24.0" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -855,13 +855,13 @@ tiktoken = ">=0.5.2,<1" [[package]] name = "langsmith" -version = "0.0.87" +version = "0.1.3" 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.87-py3-none-any.whl", hash = "sha256:8903d3811b9fc89eb18f5961c8e6935fbd2d0f119884fbf30dc70b8f8f4121fc"}, - {file = "langsmith-0.0.87.tar.gz", hash = "sha256:36c4cc47e5b54be57d038036a30fb19ce6e4c73048cd7a464b8f25b459694d34"}, + {file = "langsmith-0.1.3-py3-none-any.whl", hash = "sha256:b290f951d1ebff9abe2b52cc09d63acea75a9ca6e003a617310fb024eaf00f63"}, + {file = "langsmith-0.1.3.tar.gz", hash = "sha256:197bd1f5baa83db69a0eab644bab1eba8dcdf0c2d8b7c900a45916f7b3dd50ab"}, ] [package.dependencies] @@ -2188,4 +2188,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "86f366fc87ad2958cf4b80fc161a0c5eb374c34119d938c78821ad2fabdb2e72" +content-hash = "85bddbf515ca00359d2b25db6dc48e2943a136e3b65a9ed8a9537ad79bfd4eec" diff --git a/example/discord/honcho-dspy-personas/pyproject.toml b/example/discord/honcho-dspy-personas/pyproject.toml index 74275ea..6f2082b 100644 --- a/example/discord/honcho-dspy-personas/pyproject.toml +++ b/example/discord/honcho-dspy-personas/pyproject.toml @@ -13,6 +13,7 @@ python-dotenv = "^1.0.1" langchain-core = "^0.1.23" langchain-openai = "^0.0.6" py-cord = "^2.4.1" +langsmith = "^0.1.3" [build-system] diff --git a/example/discord/honcho-dspy-personas/metric.py b/example/discord/honcho-dspy-personas/response_metric.py similarity index 84% rename from example/discord/honcho-dspy-personas/metric.py rename to example/discord/honcho-dspy-personas/response_metric.py index 9ff3ce5..65c4fbb 100644 --- a/example/discord/honcho-dspy-personas/metric.py +++ b/example/discord/honcho-dspy-personas/response_metric.py @@ -10,7 +10,8 @@ class MessageResponseAssess(dspy.Signature): assessment_answer = dspy.OutputField(desc="Good or not") -def assess_response_quality(user_message, ai_response, assessment_dimension): +def metric(user_message, ai_response, assessment_dimension): + """Assess the quality of a response along the specified dimension.""" with dspy.context(lm=gpt4T): assessment_result = dspy.Predict(MessageResponseAssess)( user_message=user_message, From 8c5845f4f7ff75b5c8c616e6aa6b9ea0e6a130b9 Mon Sep 17 00:00:00 2001 From: vintro Date: Tue, 20 Feb 2024 21:51:19 -0500 Subject: [PATCH 33/85] initial version working, need to test optimization --- example/discord/honcho-dspy-personas/bot.py | 2 +- example/discord/honcho-dspy-personas/chain.py | 2 +- example/discord/honcho-dspy-personas/graph.py | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index a79d7e9..fdee670 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -50,7 +50,7 @@ async def on_message(message): else: session = honcho.create_session(user_id, location_id) - history = list(session.get_messages_generator()) + history = list(session.get_messages_generator())[:5] chat_history = langchain_message_converter(history) inp = message.content diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index 43c7b5e..37316fd 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -54,7 +54,7 @@ async def generate_state_commentary(cls, chat_history: List[Message], input: str chain = state_commentary | cls.lc_gpt_4 # inference response = await chain.ainvoke({ - "chat_history": format_chat_history(chat_history, user_input=input), + "chat_history": chat_history, "user_input": input }) # return output diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 9976371..c49c473 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -34,15 +34,12 @@ class ChatWithThought(dspy.Module): generate_response = dspy.Predict(Response) def forward(self, user_message: Message, session: Session, chat_input: str): - session.create_message(is_user=True, content=chat_input) - # call the thought predictor thought = self.generate_thought(user_input=chat_input) session.create_metamessage(user_message, metamessage_type="thought", content=thought.thought) # call the response predictor response = self.generate_response(user_input=chat_input, thought=thought.thought) - session.create_message(is_user=False, content=response.response) return response.response From 07651cb026b8d5d9811dd77454c5c2588ebb0727 Mon Sep 17 00:00:00 2001 From: vintro Date: Wed, 21 Feb 2024 20:30:13 -0500 Subject: [PATCH 34/85] optimizers working, but appending any example --- example/discord/honcho-dspy-personas/bot.py | 26 ++++++++++++++++ example/discord/honcho-dspy-personas/chain.py | 20 ++++++++----- example/discord/honcho-dspy-personas/graph.py | 30 ++++++++++++++----- .../langchain_prompts/state_commentary.yaml | 3 +- .../langchain_prompts/state_labeling.yaml | 10 +++++-- .../honcho-dspy-personas/response_metric.py | 17 ++++++++--- 6 files changed, 84 insertions(+), 22 deletions(-) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index fdee670..5efd9e2 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -9,6 +9,7 @@ intents.messages = True intents.message_content = True intents.members = True +intents.reactions = True # Enable reactions intent # app_id = str(uuid1()) app_id = "vince-dspy-personas" @@ -18,6 +19,9 @@ bot = discord.Bot(intents=intents) +thumbs_up_messages = [] +thumbs_down_messages = [] + @bot.event async def on_ready(): print(f'We have logged in as {bot.user}') @@ -67,6 +71,28 @@ async def on_message(message): session.create_message(is_user=False, content=response) +@bot.event +async def on_reaction_add(reaction, user): + # Ensure the bot does not react to its own reactions + if user == bot.user: + return + + user_id = f"discord_{str(reaction.message.author.id)}" + location_id = str(reaction.message.channel.id) + + # Check if the reaction is a thumbs up + if str(reaction.emoji) == '👍': + thumbs_up_messages.append(reaction.message.content) + print(f"Added to thumbs up: {reaction.message.content}") + # Check if the reaction is a thumbs down + elif str(reaction.emoji) == '👎': + thumbs_down_messages.append(reaction.message.content) + print(f"Added to thumbs down: {reaction.message.content}") + + # TODO: we need to append these to the examples list within the user state json object + + + @bot.slash_command(name = "restart", description = "Restart the Conversation") async def restart(ctx): user_id=f"discord_{str(ctx.author.id)}" diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index 37316fd..aa114e1 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -44,8 +44,10 @@ def __init__(self) -> None: pass @classmethod - async def generate_state_commentary(cls, chat_history: List[Message], input: str) -> str: + async def generate_state_commentary(cls, existing_states: List[str], chat_history: List[Message], input: str) -> str: """Generate a commentary on the current state of the user""" + # format existing states + existing_states = "\n".join(existing_states) # format prompt state_commentary = ChatPromptTemplate.from_messages([ cls.system_state_commentary @@ -55,23 +57,27 @@ async def generate_state_commentary(cls, chat_history: List[Message], input: str # inference response = await chain.ainvoke({ "chat_history": chat_history, - "user_input": input + "user_input": input, + "existing_states": existing_states, }) # return output return response.content @classmethod - async def generate_state_label(cls, state_commentary: str) -> str: + async def generate_state_label(cls, existing_states: List[str], state_commentary: str) -> str: """Generate a state label from a commetary on the user's state""" + # format existing states + existing_states = "\n".join(existing_states) # format prompt state_labeling = ChatPromptTemplate.from_messages([ - cls.system_state_labeling + cls.system_state_labeling, ]) # LCEL chain = state_labeling | cls.lc_gpt_4 # inference response = await chain.ainvoke({ - "state_commentary": state_commentary + "state_commentary": state_commentary, + "existing_states": existing_states, }) # return output return response.content @@ -102,8 +108,8 @@ async def generate_state(cls, existing_states: List[str], chat_history: List[Mes """"Determine the user's state from the current conversation state""" # Generate label - state_commentary = await cls.generate_state_commentary(chat_history, input) - state_label = await cls.generate_state_label(state_commentary) + state_commentary = await cls.generate_state_commentary(existing_states, chat_history, input) + state_label = await cls.generate_state_label(existing_states, state_commentary) # Determine if state is new # if True, it doesn't exist, state is new diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index c49c473..7b46d05 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -1,6 +1,7 @@ import os import dspy -from typing import List +from dspy import Example +from typing import List, Optional from dspy.teleprompt import BootstrapFewShot from dotenv import load_dotenv from chain import StateExtractor, format_chat_history @@ -11,7 +12,7 @@ load_dotenv() # Configure DSPy -dspy_gpt4 = dspy.OpenAI(model="gpt-4") +dspy_gpt4 = dspy.OpenAI(model="gpt-4", max_tokens=1000) dspy.settings.configure(lm=dspy_gpt4) @@ -33,18 +34,23 @@ class ChatWithThought(dspy.Module): generate_thought = dspy.Predict(Thought) generate_response = dspy.Predict(Response) - def forward(self, user_message: Message, session: Session, chat_input: str): + def forward(self, chat_input: str, user_message: Optional[Message] = None, session: Optional[Session] = None): # call the thought predictor thought = self.generate_thought(user_input=chat_input) - session.create_metamessage(user_message, metamessage_type="thought", content=thought.thought) + + if session and user_message: + session.create_metamessage(user_message, metamessage_type="thought", content=thought.thought) # call the response predictor response = self.generate_response(user_input=chat_input, thought=thought.thought) - return response.response + # remove ai prefix + response = response.response.replace("ai:", "").strip() + + return response user_state_storage = {} -async def chat(user_message: Message, session: Session, chat_history: List[Message], input: str, optimization_threshold=5): +async def chat(user_message: Message, session: Session, chat_history: List[Message], input: str, optimization_threshold=3): # first we need to see if the user has any existing states existing_states = list(user_state_storage.keys()) @@ -66,6 +72,8 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa # Optimize the state's chat module if we've reached the optimization threshold examples = user_state_data["examples"] + print(f"Num examples: {len(examples)}") + if len(examples) >= optimization_threshold: # Optimize chat module optimizer = BootstrapFewShot(metric=metric) @@ -74,10 +82,18 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa user_state_data["chat_module"] = compiled_chat_module.dump_state() user_chat_module = compiled_chat_module + # save to file for debugging purposes + # compiled_chat_module.save("module.json") + # use that pipeline to generate a response chat_input = format_chat_history(chat_history, user_input=input) - response = user_chat_module(user_message=user_message, session=session, chat_input=chat_input) + dspy_gpt4.inspect_history(n=2) + + # append example + example = Example(chat_input=chat_input, assessment_dimension=user_state, response=response).with_inputs('chat_input') + examples.append(example) + user_state_storage[user_state]["examples"] = examples return response diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml index d0fbf2e..f1e2b90 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml @@ -1,8 +1,9 @@ _type: prompt input_variables: - ["chat_history", "user_input"] + ["existing_states", "chat_history", "user_input"] template: > Your job is to make a prediction about the task the user might be engaging in. Some people might be researching, exploring curiosities, or just asking questions for general inquiry. Provide commentary that would shed light on the "mode" the user might be in. + existing states: ```{existing_states}``` chat history: ```{chat_history}``` user input: ```{user_input}``` \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml index 61e0353..c3dd8fb 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml @@ -1,9 +1,13 @@ _type: prompt input_variables: - ["state_commentary"] + ["state_commentary", "existing_states"] template: > - Your job is to label the task the user might be engaging in. Some people might be conducting research, exploring a interest, or just asking questions for general inquiry. + Your job is to label the state the user might be in. Some people might be conducting research, exploring a interest, or just asking questions for general inquiry. commentary: ```{state_commentary}``` + Prior states, from oldest to most recent: ``` + {existing_states} + ```` + + Take into account the user's prior states when making your prediction. Output your prediction as a concise, single word label. - Output your prediction as a concise, single word label. \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/response_metric.py b/example/discord/honcho-dspy-personas/response_metric.py index 65c4fbb..1383c11 100644 --- a/example/discord/honcho-dspy-personas/response_metric.py +++ b/example/discord/honcho-dspy-personas/response_metric.py @@ -4,21 +4,30 @@ class MessageResponseAssess(dspy.Signature): """Assess the quality of a response along the specified dimension.""" - user_message = dspy.InputField() + chat_input = dspy.InputField() ai_response = dspy.InputField() + gold_response = dspy.InputField() assessment_dimension = dspy.InputField() assessment_answer = dspy.OutputField(desc="Good or not") -def metric(user_message, ai_response, assessment_dimension): +def metric(example, ai_response, trace=None): """Assess the quality of a response along the specified dimension.""" + + assessment_dimension = example.assessment_dimension + chat_input = example.chat_input + gold_response = example.response + with dspy.context(lm=gpt4T): assessment_result = dspy.Predict(MessageResponseAssess)( - user_message=user_message, + chat_input=chat_input, ai_response=ai_response, + gold_response=gold_response, assessment_dimension=assessment_dimension ) is_positive = assessment_result.assessment_answer.lower() == 'good' + + gpt4T.inspect_history(n=3) - return is_positive \ No newline at end of file + return is_positive From 364ba9a237fee4b58c4a6bf9a03a85cd781f6fe3 Mon Sep 17 00:00:00 2001 From: vintro Date: Wed, 21 Feb 2024 22:44:33 -0500 Subject: [PATCH 35/85] ready for user object (tbomk) --- example/discord/honcho-dspy-personas/bot.py | 5 ++++- example/discord/honcho-dspy-personas/graph.py | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index 5efd9e2..948bdb3 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -90,7 +90,10 @@ async def on_reaction_add(reaction, user): print(f"Added to thumbs down: {reaction.message.content}") # TODO: we need to append these to the examples list within the user state json object - + # append example + # example = Example(chat_input=chat_input, assessment_dimension=user_state, response=response).with_inputs('chat_input') + # examples.append(example) + # user_state_storage[user_state]["examples"] = examples @bot.slash_command(name = "restart", description = "Restart the Conversation") diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 7b46d05..9295a43 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -61,6 +61,7 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa user_chat_module = ChatWithThought() + # TODO: you'd want to initialize user state object from Honcho # Save the user_state if it's new if is_state_new: user_state_storage[user_state] = { @@ -71,6 +72,7 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa user_state_data = user_state_storage[user_state] # Optimize the state's chat module if we've reached the optimization threshold + # TODO: read in examples from Honcho User Object examples = user_state_data["examples"] print(f"Num examples: {len(examples)}") @@ -91,9 +93,4 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa response = user_chat_module(user_message=user_message, session=session, chat_input=chat_input) dspy_gpt4.inspect_history(n=2) - # append example - example = Example(chat_input=chat_input, assessment_dimension=user_state, response=response).with_inputs('chat_input') - examples.append(example) - user_state_storage[user_state]["examples"] = examples - return response From ad4cec37600309be7ce03c78944d95a974a569b6 Mon Sep 17 00:00:00 2001 From: Ayush Paul Date: Thu, 22 Feb 2024 17:00:41 -0500 Subject: [PATCH 36/85] Revert "add test actions and coverage" --- .github/workflows/run_coverage.yml | 51 ----------------- .github/workflows/run_tests.yml | 38 ------------ README.md | 12 ++-- sdk/poetry.lock | 92 +++++++----------------------- sdk/pyproject.toml | 1 - sdk/tests/test_sync.py | 2 - 6 files changed, 27 insertions(+), 169 deletions(-) delete mode 100644 .github/workflows/run_coverage.yml delete mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml deleted file mode 100644 index bc2d780..0000000 --- a/.github/workflows/run_coverage.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Run Coverage -on: [pull_request] -jobs: - test: - permissions: - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" - - name: Install poetry - run: | - pip install poetry - - name: Syncify Client - run: | - python scripts/syncronizer.py - - name: Start Server - run: | - cd api - poetry install --no-root - poetry run uvicorn src.main:app & - sleep 5 - cd .. - env: - DATABASE_TYPE: sqlite - CONNECTION_URI: sqlite:///api.db - - name: Run Tests - run: | - cd sdk - poetry install - poetry run coverage run -m pytest - poetry run coverage xml -o coverage.xml - cd .. - - name: Code Coverage - uses: irongut/CodeCoverageSummary@v1.3.0 - with: - filename: sdk/coverage.xml - badge: true - output: file - format: markdown - - name: Add Coverage PR Comment - uses: marocchino/sticky-pull-request-comment@v2 - with: - recreate: true - path: code-coverage-results.md - - name: Stop Server - run: | - kill $(jobs -p) || true \ No newline at end of file diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml deleted file mode 100644 index 05e3aa8..0000000 --- a/.github/workflows/run_tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Run Tests -on: [push, pull_request] -jobs: - test: - permissions: - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" - - name: Install poetry - run: | - pip install poetry - - name: Syncify Client - run: | - python scripts/syncronizer.py - - name: Start Server - run: | - cd api - poetry install --no-root - poetry run uvicorn src.main:app & - sleep 5 - cd .. - env: - DATABASE_TYPE: sqlite - CONNECTION_URI: sqlite:///api.db - - name: Run Tests - run: | - cd sdk - poetry install - poetry run pytest - cd .. - - name: Stop Server - run: | - kill $(jobs -p) || true \ No newline at end of file diff --git a/README.md b/README.md index bfd56d0..e67c360 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # Honcho - ![Static Badge](https://img.shields.io/badge/Version-0.0.3-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) [![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fplastic_labs)](https://twitter.com/plastic_labs) -[![Run Tests](https://github.com/plastic-labs/honcho/actions/workflows/api_testing.yml/badge.svg?branch=staging)](https://github.com/plastic-labs/honcho/actions/workflows/api_testing.yml) - A User context management solution for building AI Agents and LLM powered applications. @@ -51,7 +48,7 @@ poetry install # install dependencies 2. Copy the `.env.template` file and specify the type of database and connection_uri. For testing sqlite is fine. The below example uses an - in-memory sqlite database. + in-memory sqlite database. > Honcho has been tested with Postgresql and PGVector @@ -93,7 +90,8 @@ docker run --env-file .env -p 8000:8000 honcho-api:latest The API can also be deployed on fly.io. Follow the [Fly.io Docs](https://fly.io/docs/getting-started/) to setup your environment and the -`flyctl`. +`flyctl`. + Once `flyctl` is set up use the following commands to launch the application: @@ -136,12 +134,12 @@ See more information [here](https://python-poetry.org/docs/cli/#add) This project is completely open source and welcomes any and all open source contributions. The workflow for contributing is to make a fork of the repository. You can claim an issue in the issues tab or start a new thread to -indicate a feature or bug fix you are working on. +indicate a feature or bug fix you are working on. Once you have finished your contribution make a PR pointed at the `staging` branch, and it will be reviewed by a project manager. Feel free to join us in our [discord](http://discord.gg/plasticlabs) to discuss your changes or get -help. +help. Once your changes are accepted and merged into staging they will undergo a period of live testing before entering the upstream into `main` diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 965b0ac..e450ca8 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -26,6 +27,7 @@ trio = ["trio (>=0.23)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -37,6 +39,7 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -44,74 +47,11 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "coverage" -version = "7.4.1" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, -] - -[package.extras] -toml = ["tomli"] - [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -126,6 +66,7 @@ test = ["pytest (>=6)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -137,6 +78,7 @@ files = [ name = "httpcore" version = "1.0.2" description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -151,13 +93,14 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -168,20 +111,21 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = "==1.*" +httpcore = ">=1.0.0,<2.0.0" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -193,6 +137,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -204,6 +149,7 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -215,6 +161,7 @@ files = [ name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -230,6 +177,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -252,6 +200,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.23.4" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -270,6 +219,7 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -281,6 +231,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -292,6 +243,7 @@ files = [ name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -302,4 +254,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "cfdd3c0dc8dba3a70135da5b63a3b027968bb935d4846491c7bba2f30ac20a32" +content-hash = "6ccea662fa5a5bae88618123d5d05e0d4955c234b7e1a688d2fae2f90cd9f7f8" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index eadc19e..1455bca 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -14,7 +14,6 @@ httpx = "^0.26.0" [tool.poetry.group.test.dependencies] pytest = "^7.4.4" pytest-asyncio = "^0.23.4" -coverage = "^7.4.1" [build-system] requires = ["poetry-core"] diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 28ad2b9..a0367ad 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,6 +1,5 @@ import pytest from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, GetDocumentPage, Session, Message, Metamessage, Document - from honcho import Client as Honcho from uuid import uuid1 @@ -344,7 +343,6 @@ def test_collection_query(): assert doc3.metadata == {"test": "test"} assert doc3.content == "the user has owned pets in the past" - result = collection.query(query="does the user own pets", top_k=2) assert result is not None From dee6ba768cc67080254ecda7f0ee8258285dbee8 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:05:44 -0800 Subject: [PATCH 37/85] Refactor to add User and App Tables --- api/.vscode/settings.json | 3 + api/src/crud.py | 327 ++++++++++++++++++++++++++------------ api/src/main.py | 297 ++++++++++++++++++++++++++-------- api/src/models.py | 56 +++++-- api/src/schemas.py | 65 +++++++- 5 files changed, 570 insertions(+), 178 deletions(-) create mode 100644 api/.vscode/settings.json diff --git a/api/.vscode/settings.json b/api/.vscode/settings.json new file mode 100644 index 0000000..457f44d --- /dev/null +++ b/api/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic" +} \ No newline at end of file diff --git a/api/src/crud.py b/api/src/crud.py index e18c0ff..cde2359 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -12,13 +12,127 @@ openai_client = OpenAI() +######################################################## +# app methods +######################################################## + + +def get_app(db: Session, app_id: uuid.UUID) -> Optional[models.App]: + stmt = ( + select(models.App) + .where(models.App.id == app_id) + ) + app = db.scalars(stmt).one_or_none() + return app + +def get_app_by_name(db: Session, app_name: str) -> Optional[models.App]: + stmt = ( + select(models.App) + .where(models.App.name == app_name) + ) + app = db.scalars(stmt).one_or_none() + return app + + +# def get_apps(db: Session) -> Sequence[models.App]: +# return db.query(models.App).all() + +def create_app(db: Session, app: schemas.AppCreate) -> models.App: + honcho_app = models.App( + name=app.name, + h_metadata=app.metadata + ) + db.add(honcho_app) + db.commit() + db.refresh(honcho_app) + return honcho_app + +def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models.App: + honcho_app = get_app(db, app_id) + if honcho_app is None: + raise ValueError("App not found") + if app.name is not None: + honcho_app.content = app.name + if app.metadata is not None: + honcho_app.h_metadata = app.metadata + + db.commit() + db.refresh(honcho_app) + return honcho_app + +# def delete_app(db: Session, app_id: uuid.UUID) -> bool: +# existing_app = get_app(db, app_id) +# if existing_app is None: +# return False +# db.delete(existing_app) +# db.commit() +# return True + + +######################################################## +# user methods +######################################################## + +def create_user(db: Session, app_id: uuid.UUID, user: schemas.UserCreate) -> models.User: + honcho_user = models.User( + app_id=app_id, + name=user.name, + h_metadata=user.metadata, + ) + db.add(honcho_user) + db.commit() + db.refresh(honcho_user) + return honcho_user + +def get_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID) -> Optional[models.User]: + stmt = ( + select(models.User) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) + + ) + user = db.scalars(stmt).one_or_none() + return user + +def get_users(db: Session, app_id: uuid.UUID) -> Select: + stmt = ( + select(models.User) + .where(models.User.app_id == app_id) + ) + return stmt + +def update_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate) -> models.User: + honcho_user = get_user(db, app_id, user_id) + if honcho_user is None: + raise ValueError("User not found") + if user.name is not None: + honcho_user.content = user.name + if user.metadata is not None: + honcho_user.h_metadata = user.metadata + + db.commit() + db.refresh(honcho_user) + return honcho_user + +# def delete_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID) -> bool: +# existing_user = get_user(db, app_id, user_id) +# if existing_user is None: +# return False +# db.delete(existing_user) +# db.commit() +# return True + +######################################################## +# session methods +######################################################## def get_session( - db: Session, app_id: str, session_id: uuid.UUID, user_id: Optional[str] = None + db: Session, app_id: uuid.UUID, session_id: uuid.UUID, user_id: Optional[uuid.UUID] = None ) -> Optional[models.Session]: stmt = ( select(models.Session) - .where(models.Session.app_id == app_id) + .join(models.User, models.User.id == models.Session.user_id) + .where(models.User.app_id == app_id) .where(models.Session.id == session_id) ) if user_id is not None: @@ -26,19 +140,19 @@ def get_session( session = db.scalars(stmt).one_or_none() return session - def get_sessions( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, location_id: Optional[str] = None, reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Session) - .where(models.Session.app_id == app_id) + .join(models.User, models.User.id == models.Session.user_id) + .where(models.User.app_id == app_id) .where(models.Session.user_id == user_id) - .where(models.Session.is_active.is_(True)) +# .where(models.Session.is_active.is_(True)) ) if reverse: @@ -53,10 +167,9 @@ def get_sessions( def create_session( - db: Session, session: schemas.SessionCreate, app_id: str, user_id: str + db: Session, session: schemas.SessionCreate, app_id: uuid.UUID, user_id: uuid.UUID ) -> models.Session: honcho_session = models.Session( - app_id=app_id, user_id=user_id, location_id=session.location_id, h_metadata=session.metadata, @@ -70,8 +183,8 @@ def create_session( def update_session( db: Session, session: schemas.SessionUpdate, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, ) -> bool: honcho_session = get_session( @@ -79,9 +192,7 @@ def update_session( ) if honcho_session is None: raise ValueError("Session not found or does not belong to user") - if ( - session.metadata is not None - ): # Need to explicitly be there won't make it empty by default + if session.metadata is not None: # Need to explicitly be there won't make it empty by default honcho_session.h_metadata = session.metadata db.commit() db.refresh(honcho_session) @@ -89,12 +200,13 @@ def update_session( def delete_session( - db: Session, app_id: str, user_id: str, session_id: uuid.UUID + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID ) -> bool: stmt = ( select(models.Session) + .join(models.User, models.User.id == models.Session.user_id) .where(models.Session.id == session_id) - .where(models.Session.app_id == app_id) + .where(models.User.app_id == app_id) .where(models.Session.user_id == user_id) ) honcho_session = db.scalars(stmt).one_or_none() @@ -104,12 +216,15 @@ def delete_session( db.commit() return True +######################################################## +# Message Methods +######################################################## def create_message( db: Session, message: schemas.MessageCreate, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, ) -> models.Message: honcho_session = get_session( @@ -131,16 +246,18 @@ def create_message( def get_messages( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Message) .join(models.Session, models.Session.id == models.Message.session_id) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) + .join(models.User, models.User.id == models.Session.user_id) + .join(models.App, models.App.id == models.User.app_id) + .where(models.App.id == app_id) + .where(models.User.id == user_id) .where(models.Message.session_id == session_id) ) @@ -153,28 +270,56 @@ def get_messages( def get_message( - db: Session, app_id: str, user_id: str, session_id: uuid.UUID, message_id: uuid.UUID + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID ) -> Optional[models.Message]: stmt = ( select(models.Message) .join(models.Session, models.Session.id == models.Message.session_id) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) + .join(models.User, models.User.id == models.Session.user_id) + .join(models.App, models.App.id == models.User.app_id) + .where(models.App.id == app_id) + .where(models.User.id == user_id) .where(models.Message.session_id == session_id) .where(models.Message.id == message_id) ) return db.scalars(stmt).one_or_none() - ######################################################## # metamessage methods ######################################################## +def create_metamessage( + db: Session, + metamessage: schemas.MetamessageCreate, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, +): + message = get_message( + db, + app_id=app_id, + session_id=session_id, + user_id=user_id, + message_id=metamessage.message_id, + ) + if message is None: + raise ValueError("Session not found or does not belong to user") + + honcho_metamessage = models.Metamessage( + message_id=metamessage.message_id, + metamessage_type=metamessage.metamessage_type, + content=metamessage.content, + ) + + db.add(honcho_metamessage) + db.commit() + db.refresh(honcho_metamessage) + return honcho_metamessage def get_metamessages( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message_id: Optional[uuid.UUID], metamessage_type: Optional[str] = None, @@ -184,8 +329,10 @@ def get_metamessages( select(models.Metamessage) .join(models.Message, models.Message.id == models.Metamessage.message_id) .join(models.Session, models.Message.session_id == models.Session.id) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) + .join(models.User, models.User.id == models.Session.user_id) + .join(models.App, models.App.id == models.User.app_id) + .where(models.App.id == app_id) + .where(models.User.id == user_id) .where(models.Message.session_id == session_id) ) @@ -205,8 +352,8 @@ def get_metamessages( def get_metamessage( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID, @@ -215,8 +362,10 @@ def get_metamessage( select(models.Metamessage) .join(models.Message, models.Message.id == models.Metamessage.message_id) .join(models.Session, models.Message.session_id == models.Session.id) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) + .join(models.User, models.User.id == models.Session.user_id) + .join(models.App, models.App.id == models.User.app_id) + .where(models.App.id == app_id) + .where(models.User.id == user_id) .where(models.Message.session_id == session_id) .where(models.Metamessage.message_id == message_id) .where(models.Metamessage.id == metamessage_id) @@ -224,35 +373,6 @@ def get_metamessage( return db.scalars(stmt).one_or_none() -def create_metamessage( - db: Session, - metamessage: schemas.MetamessageCreate, - app_id: str, - user_id: str, - session_id: uuid.UUID, -): - message = get_message( - db, - app_id=app_id, - session_id=session_id, - user_id=user_id, - message_id=metamessage.message_id, - ) - if message is None: - raise ValueError("Session not found or does not belong to user") - - honcho_metamessage = models.Metamessage( - message_id=metamessage.message_id, - metamessage_type=metamessage.metamessage_type, - content=metamessage.content, - ) - - db.add(honcho_metamessage) - db.commit() - db.refresh(honcho_metamessage) - return honcho_metamessage - - ######################################################## # collection methods ######################################################## @@ -261,13 +381,14 @@ def create_metamessage( def get_collections( - db: Session, app_id: str, user_id: str, reverse: Optional[bool] = False + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, reverse: Optional[bool] = False ) -> Select: """Get a distinct list of the names of collections associated with a user""" stmt = ( select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) ) if reverse: @@ -279,12 +400,13 @@ def get_collections( def get_collection_by_id( - db: Session, app_id: str, user_id: str, collection_id: uuid.UUID + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID ) -> Optional[models.Collection]: stmt = ( select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Collection.id == collection_id) ) collection = db.scalars(stmt).one_or_none() @@ -292,12 +414,13 @@ def get_collection_by_id( def get_collection_by_name( - db: Session, app_id: str, user_id: str, name: str + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, name: str ) -> Optional[models.Collection]: stmt = ( select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Collection.name == name) ) collection = db.scalars(stmt).one_or_none() @@ -305,10 +428,9 @@ def get_collection_by_name( def create_collection( - db: Session, collection: schemas.CollectionCreate, app_id: str, user_id: str + db: Session, collection: schemas.CollectionCreate, app_id: uuid.UUID, user_id: uuid.UUID ) -> models.Collection: honcho_collection = models.Collection( - app_id=app_id, user_id=user_id, name=collection.name, ) @@ -325,8 +447,8 @@ def create_collection( def update_collection( db: Session, collection: schemas.CollectionUpdate, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, ) -> models.Collection: honcho_collection = get_collection_by_id( @@ -345,7 +467,7 @@ def update_collection( def delete_collection( - db: Session, app_id: str, user_id: str, collection_id: uuid.UUID + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID ) -> bool: """ Delete a Collection and all documents associated with it. Takes advantage of @@ -353,9 +475,10 @@ def delete_collection( """ stmt = ( select(models.Collection) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Collection.id == collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) ) honcho_collection = db.scalars(stmt).one_or_none() if honcho_collection is None: @@ -374,16 +497,17 @@ def delete_collection( def get_documents( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, reverse: Optional[bool] = False, ) -> Select: stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Document.collection_id == collection_id) ) @@ -397,16 +521,17 @@ def get_documents( def get_document( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, ) -> Optional[models.Document]: stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Document.collection_id == collection_id) .where(models.Document.id == document_id) ) @@ -417,8 +542,8 @@ def get_document( def query_documents( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, query: str, top_k: int = 5, @@ -430,8 +555,9 @@ def query_documents( stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Document.collection_id == collection_id) .order_by(models.Document.embedding.cosine_distance(embedding_query)) .limit(top_k) @@ -444,8 +570,8 @@ def query_documents( def create_document( db: Session, document: schemas.DocumentCreate, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, ) -> models.Document: """Embed a message as a vector and create a document""" @@ -476,8 +602,8 @@ def create_document( def update_document( db: Session, document: schemas.DocumentUpdate, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, ) -> bool: @@ -497,7 +623,7 @@ def update_document( ) embedding = response.data[0].embedding honcho_document.embedding = embedding - honcho_document.created_at = datetime.datetime.now() + honcho_document.created_at = datetime.datetime.utcnow() if document.metadata is not None: honcho_document.h_metadata = document.metadata @@ -508,16 +634,17 @@ def update_document( def delete_document( db: Session, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, ) -> bool: stmt = ( select(models.Document) .join(models.Collection, models.Collection.id == models.Document.collection_id) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) + .join(models.User, models.User.id == models.Collection.user_id) + .where(models.User.app_id == app_id) + .where(models.User.id == user_id) .where(models.Document.collection_id == collection_id) .where(models.Document.id == document_id) ) diff --git a/api/src/main.py b/api/src/main.py index 0c658e9..bbdf46c 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -39,6 +39,175 @@ def get_db(): finally: db.close() +######################################################## +# App Routes +######################################################## +@app.get("/apps/{app_id}", response_model=schemas.App) +def get_app( + request: Request, + app_id: uuid.UUID, + db: Session = Depends(get_db), +): + """Get an App by ID + + Args: + app_id (uuid.UUID): The ID of the app + + Returns: + schemas.App: App object + + """ + app = crud.get_app(db, app_id=app_id) + if app is None: + raise HTTPException(status_code=404, detail="App not found") + return app + +@app.get("/apps/name/{app_name}", response_model=schemas.App) +def get_app_by_name( + request: Request, + app_name: str, + db: Session = Depends(get_db), +): + """Get an App by Name + + Args: + app_name (str): The name of the app + + Returns: + schemas.App: App object + + """ + app = crud.get_app_by_name(db, app_name=app_name) + if app is None: + raise HTTPException(status_code=404, detail="App not found") + return app + + +@app.post("/apps", response_model=schemas.App) +def create_app( + request: Request, + app: schemas.AppCreate, + db: Session = Depends(get_db), +): + """Create an App + + Args: + app (schemas.AppCreate): The App object containing any metadata + + Returns: + schemas.App: Created App object + + """ + return crud.create_app(db, app=app) + + +@app.put("/apps/{app_id}", response_model=schemas.App) +def update_app( + request: Request, + app_id: uuid.UUID, + app: schemas.AppUpdate, + db: Session = Depends(get_db), +): + """Update an App + + Args: + app_id (uuid.UUID): The ID of the app to update + app (schemas.AppUpdate): The App object containing any new metadata + + Returns: + schemas.App: The App object of the updated App + + """ + honcho_app = crud.update_app(db, app_id=app_id, app=app) + if honcho_app is None: + raise HTTPException(status_code=404, detail="App not found") + return honcho_app + + +######################################################## +# User Routes +######################################################## + + +@app.post("/apps/{app_id}/users", response_model=schemas.User) +def create_user( + request: Request, + app_id: uuid.UUID, + user: schemas.UserCreate, + db: Session = Depends(get_db), +): + """Create a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user (schemas.UserCreate): The User object containing any metadata + + Returns: + schemas.User: Created User object + + """ + return crud.create_user(db, app_id=app_id, user=user) + + +@router.get("/apps/{app_id}/users", response_model=Page[schemas.User]) +def get_users( + request: Request, + app_id: uuid.UUID, + db: Session = Depends(get_db), +): + """Get All Users for an App + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using honcho + + Returns: + list[schemas.User]: List of User objects + + """ + return paginate(db, crud.get_users(db, app_id=app_id)) + + +@router.get("/apps/{app_id}/users/{user_id}", response_model=schemas.User) +def get_user( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + db: Session = Depends(get_db), +): + """Get a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + + Returns: + schemas.User: User object + + """ + return crud.get_user(db, app_id=app_id, user_id=user_id) + + +@router.put("/users/{user_id}", response_model=schemas.User) +def update_user( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + user: schemas.UserCreate, + db: Session = Depends(get_db), +): + """Update a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + user (schemas.UserCreate): The User object containing any metadata + + Returns: + schemas.User: Updated User object + + """ + return crud.update_user(db, app_id=app_id, user_id=user_id, user=user) + ######################################################## # Session Routes @@ -48,8 +217,8 @@ def get_db(): @router.get("/sessions", response_model=Page[schemas.Session]) def get_sessions( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, location_id: Optional[str] = None, reverse: Optional[bool] = False, db: Session = Depends(get_db), @@ -57,8 +226,8 @@ def get_sessions( """Get All Sessions for a User Args: - app_id (str): The ID of the app representing the client application using honcho - user_id (str): The User ID representing the user, managed by the user + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session Returns: @@ -76,16 +245,16 @@ def get_sessions( @router.post("/sessions", response_model=schemas.Session) def create_session( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session: schemas.SessionCreate, db: Session = Depends(get_db), ): """Create a Session for a User Args: - app_id (str): The ID of the app representing the client application using honcho - user_id (str): The User ID representing the user, managed by the user + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user session (schemas.SessionCreate): The Session object containing any metadata and a location ID Returns: @@ -99,8 +268,8 @@ def create_session( @router.put("/sessions/{session_id}", response_model=schemas.Session) def update_session( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, session: schemas.SessionUpdate, db: Session = Depends(get_db), @@ -108,9 +277,9 @@ def update_session( """Update the metadata of a Session Args: - app_id (str): The ID of the app representing the client application using honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to update + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to update session (schemas.SessionUpdate): The Session object containing any new metadata Returns: @@ -132,17 +301,17 @@ def update_session( @router.delete("/sessions/{session_id}") def delete_session( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, db: Session = Depends(get_db), ): """Delete a session by marking it as inactive Args: - app_id (str): The ID of the app representing the client application using honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to delete + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to delete Returns: dict: A message indicating that the session was deleted @@ -163,17 +332,17 @@ def delete_session( @router.get("/sessions/{session_id}", response_model=schemas.Session) def get_session( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, db: Session = Depends(get_db), ): """Get a specific session for a user by ID Args: - app_id (str): The ID of the app representing the client application using honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to retrieve Returns: schemas.Session: The Session object of the requested Session @@ -197,8 +366,8 @@ def get_session( @router.post("/sessions/{session_id}/messages", response_model=schemas.Message) def create_message_for_session( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message: schemas.MessageCreate, db: Session = Depends(get_db), @@ -206,7 +375,7 @@ def create_message_for_session( """Adds a message to a session Args: - app_id (str): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to add the message to message (schemas.MessageCreate): The Message object to add containing the message content and type @@ -229,8 +398,8 @@ def create_message_for_session( @router.get("/sessions/{session_id}/messages", response_model=Page[schemas.Message]) def get_messages( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, reverse: Optional[bool] = False, db: Session = Depends(get_db), @@ -238,7 +407,7 @@ def get_messages( """Get all messages for a session Args: - app_id (str): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve reverse (bool): Whether to reverse the order of the messages @@ -270,8 +439,8 @@ def get_messages( ) def get_message( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID, db: Session = Depends(get_db), @@ -293,8 +462,8 @@ def get_message( @router.post("/sessions/{session_id}/metamessages", response_model=schemas.Metamessage) def create_metamessage( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, metamessage: schemas.MetamessageCreate, db: Session = Depends(get_db), @@ -302,7 +471,7 @@ def create_metamessage( """Adds a message to a session Args: - app_id (str): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to add the message to message (schemas.MessageCreate): The Message object to add containing the message content and type @@ -331,8 +500,8 @@ def create_metamessage( ) def get_metamessages( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message_id: Optional[uuid.UUID] = None, metamessage_type: Optional[str] = None, @@ -342,7 +511,7 @@ def get_metamessages( """Get all messages for a session Args: - app_id (str): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve reverse (bool): Whether to reverse the order of the metamessages @@ -377,8 +546,8 @@ def get_metamessages( ) def get_metamessage( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID, @@ -387,7 +556,7 @@ def get_metamessage( """Get a specific session for a user by ID Args: - app_id (str): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve @@ -418,8 +587,8 @@ def get_metamessage( @router.get("/collections/all", response_model=Page[schemas.Collection]) def get_collections( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, reverse: Optional[bool] = False, db: Session = Depends(get_db), ): @@ -431,8 +600,8 @@ def get_collections( @router.get("/collections/id/{collection_id}", response_model=schemas.Collection) def get_collection_by_id( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, db: Session = Depends(get_db), ) -> schemas.Collection: @@ -449,8 +618,8 @@ def get_collection_by_id( @router.get("/collections/name/{name}", response_model=schemas.Collection) def get_collection_by_name( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, name: str, db: Session = Depends(get_db), ) -> schemas.Collection: @@ -467,8 +636,8 @@ def get_collection_by_name( @router.post("/collections", response_model=schemas.Collection) def create_collection( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection: schemas.CollectionCreate, db: Session = Depends(get_db), ): @@ -486,8 +655,8 @@ def create_collection( @router.put("/collections/{collection_id}", response_model=schemas.Collection) def update_collection( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, collection: schemas.CollectionUpdate, db: Session = Depends(get_db), @@ -515,8 +684,8 @@ def update_collection( @router.delete("/collections/{collection_id}") def delete_collection( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, db: Session = Depends(get_db), ): @@ -541,8 +710,8 @@ def delete_collection( ) def get_documents( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, reverse: Optional[bool] = False, db: Session = Depends(get_db), @@ -574,8 +743,8 @@ def get_documents( def get_document( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, db: Session = Depends(get_db), @@ -599,8 +768,8 @@ def get_document( ) def query_documents( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, query: str, top_k: int = 5, @@ -621,8 +790,8 @@ def query_documents( @router.post("/collections/{collection_id}/documents", response_model=schemas.Document) def create_document( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document: schemas.DocumentCreate, db: Session = Depends(get_db), @@ -647,8 +816,8 @@ def create_document( ) def update_document( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, document: schemas.DocumentUpdate, @@ -671,8 +840,8 @@ def update_document( @router.delete("/collections/{collection_id}/documents/{document_id}") def delete_document( request: Request, - app_id: str, - user_id: str, + app_id: uuid.UUID, + user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, db: Session = Depends(get_db), diff --git a/api/src/models.py b/api/src/models.py index ce20747..a4c166c 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -4,7 +4,7 @@ from dotenv import load_dotenv from pgvector.sqlalchemy import Vector -from sqlalchemy import JSON, Column, ForeignKey, String, UniqueConstraint, Uuid +from sqlalchemy import JSON, Column, DateTime, ForeignKey, String, UniqueConstraint, Uuid from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -15,22 +15,55 @@ DATABASE_TYPE = os.getenv("DATABASE_TYPE", "postgres") ColumnType = JSONB if DATABASE_TYPE == "postgres" else JSON + +class App(Base): + __tablename__ = "apps" + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) + name: Mapped[str] = mapped_column(String(512), index=True, unique=True) + users = relationship("User", back_populates="app") + created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), + default=datetime.datetime.utcnow + ) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) + # Add any additional fields for an app here + +class User(Base): + __tablename__ = "users" + id: Mapped[uuid.UUID] = mapped_column( + primary_key=True, index=True, default=uuid.uuid4 + ) + name: Mapped[str] = mapped_column(String(512), index=True) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) + created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), + default=datetime.datetime.utcnow + ) + app_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("apps.id"), index=True) + app = relationship("App", back_populates="users") + sessions = relationship("Session", back_populates="user") + collections = relationship("Collection", back_populates="user") + def __repr__(self) -> str: + return f"User(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, created_at={self.created_at}, h_metadata={self.h_metadata})" class Session(Base): __tablename__ = "sessions" id: Mapped[uuid.UUID] = mapped_column( primary_key=True, index=True, default=uuid.uuid4 ) - app_id: Mapped[str] = mapped_column(String(512), index=True) - user_id: Mapped[str] = mapped_column(String(512), index=True) location_id: Mapped[str] = mapped_column(String(512), index=True, default="default") is_active: Mapped[bool] = mapped_column(default=True) h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), default=datetime.datetime.utcnow ) messages = relationship("Message", back_populates="session") + user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), index=True) + user = relationship("User", back_populates="sessions") def __repr__(self) -> str: return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})" @@ -41,11 +74,12 @@ class Message(Base): id: Mapped[uuid.UUID] = mapped_column( primary_key=True, index=True, default=uuid.uuid4 ) - session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("sessions.id")) + session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("sessions.id"), index=True) is_user: Mapped[bool] content: Mapped[str] = mapped_column(String(65535)) created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), default=datetime.datetime.utcnow ) session = relationship("Session", back_populates="messages") @@ -62,10 +96,11 @@ class Metamessage(Base): ) metamessage_type: Mapped[str] = mapped_column(String(512), index=True) content: Mapped[str] = mapped_column(String(65535)) - message_id = Column(Uuid, ForeignKey("messages.id")) + message_id = Column(Uuid, ForeignKey("messages.id"), index=True) message = relationship("Message", back_populates="metamessages") created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), default=datetime.datetime.utcnow ) @@ -79,20 +114,20 @@ class Collection(Base): primary_key=True, index=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(512), index=True) - app_id: Mapped[str] = mapped_column(String(512), index=True) - user_id: Mapped[str] = mapped_column(String(512), index=True) created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), default=datetime.datetime.utcnow ) documents = relationship( "Document", back_populates="collection", cascade="all, delete, delete-orphan" ) + user = relationship("User", back_populates="collections") + user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), index=True) __table_args__ = ( - UniqueConstraint("name", "app_id", "user_id", name="unique_name_app_user"), + UniqueConstraint("name", "user_id", name="unique_name_app_user"), ) - class Document(Base): __tablename__ = "documents" id: Mapped[uuid.UUID] = mapped_column( @@ -102,8 +137,9 @@ class Document(Base): content: Mapped[str] = mapped_column(String(65535)) embedding = mapped_column(Vector(1536)) created_at: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), default=datetime.datetime.utcnow ) - collection_id = Column(Uuid, ForeignKey("collections.id")) + collection_id = Column(Uuid, ForeignKey("collections.id"), index=True) collection = relationship("Collection", back_populates="documents") diff --git a/api/src/schemas.py b/api/src/schemas.py index fe164aa..429fb1b 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -2,6 +2,65 @@ import datetime import uuid +class AppBase(BaseModel): + pass + +class AppCreate(AppBase): + name: str + metadata: dict | None = {} + +class AppUpdate(AppBase): + name: str | None = None + metadata: dict | None = None + +class App(AppBase): + id: uuid.UUID + name: str + h_metadata: dict + metadata: dict + created_at: datetime.datetime + + @validator('metadata', pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if 'h_metadata' in values: + return values['h_metadata'] + return {} + + class Config: + from_attributes = True + schema_extra ={ + "exclude": ["h_metadata"] + } + +class UserBase(BaseModel): + pass + +class UserCreate(UserBase): + name: str + metadata: dict | None = {} + +class UserUpdate(UserBase): + name: str | None = None + metadata: dict | None = None + +class User(UserBase): + id: uuid.UUID + app_id: uuid.UUID + created_at: datetime.datetime + h_metadata: dict + metadata: dict + + @validator('metadata', pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if 'h_metadata' in values: + return values['h_metadata'] + return {} + + class Config: + from_attributes = True + schema_extra = { + "exclude": ["h_metadata"] + } class MessageBase(BaseModel): content: str @@ -37,7 +96,6 @@ class Session(SessionBase): is_active: bool user_id: str location_id: str - app_id: str h_metadata: dict metadata: dict created_at: datetime.datetime @@ -70,7 +128,7 @@ class Metamessage(MetamessageBase): created_at: datetime.datetime class Config: - orm_mode = True + from_attributes = True class CollectionBase(BaseModel): pass @@ -84,12 +142,11 @@ class CollectionUpdate(CollectionBase): class Collection(CollectionBase): id: uuid.UUID name: str - app_id: str user_id: str created_at: datetime.datetime class Config: - orm_mode = True + from_attributes = True class DocumentBase(BaseModel): content: str From 569870c48b582f2135b0ee8b1fea72e8ca2bd43e Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 02:54:52 -0800 Subject: [PATCH 38/85] User Object passing test cases --- api/pyproject.toml | 17 ++ api/src/crud.py | 89 +++++-- api/src/main.py | 101 ++++--- api/src/models.py | 41 +-- api/src/schemas.py | 69 +++-- sdk/honcho/__init__.py | 24 +- sdk/honcho/client.py | 546 +++++++++++++++++++++++++------------- sdk/honcho/sync_client.py | 546 +++++++++++++++++++++++++------------- sdk/pyproject.toml | 19 ++ sdk/tests/test_async.py | 290 ++++++++++++-------- sdk/tests/test_sync.py | 290 ++++++++++++-------- 11 files changed, 1339 insertions(+), 693 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 2c828a9..2aaee65 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -17,6 +17,23 @@ fastapi-pagination = "^0.12.14" pgvector = "^0.2.5" openai = "^1.12.0" +[tool.ruff.lint] +# from https://docs.astral.sh/ruff/linter/#rule-selection example +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] + [build-system] requires = ["poetry-core"] diff --git a/api/src/crud.py b/api/src/crud.py index cde2359..a4670b0 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -1,12 +1,11 @@ -import uuid import datetime +import uuid from typing import Optional, Sequence from openai import OpenAI - -from sqlalchemy import select, Select -from sqlalchemy.orm import Session +from sqlalchemy import Select, select from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session from . import models, schemas @@ -18,18 +17,13 @@ def get_app(db: Session, app_id: uuid.UUID) -> Optional[models.App]: - stmt = ( - select(models.App) - .where(models.App.id == app_id) - ) + stmt = select(models.App).where(models.App.id == app_id) app = db.scalars(stmt).one_or_none() return app + def get_app_by_name(db: Session, app_name: str) -> Optional[models.App]: - stmt = ( - select(models.App) - .where(models.App.name == app_name) - ) + stmt = select(models.App).where(models.App.name == app_name) app = db.scalars(stmt).one_or_none() return app @@ -37,16 +31,15 @@ def get_app_by_name(db: Session, app_name: str) -> Optional[models.App]: # def get_apps(db: Session) -> Sequence[models.App]: # return db.query(models.App).all() + def create_app(db: Session, app: schemas.AppCreate) -> models.App: - honcho_app = models.App( - name=app.name, - h_metadata=app.metadata - ) + honcho_app = models.App(name=app.name, h_metadata=app.metadata) db.add(honcho_app) db.commit() db.refresh(honcho_app) return honcho_app + def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models.App: honcho_app = get_app(db, app_id) if honcho_app is None: @@ -60,6 +53,7 @@ def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models db.refresh(honcho_app) return honcho_app + # def delete_app(db: Session, app_id: uuid.UUID) -> bool: # existing_app = get_app(db, app_id) # if existing_app is None: @@ -73,7 +67,10 @@ def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models # user methods ######################################################## -def create_user(db: Session, app_id: uuid.UUID, user: schemas.UserCreate) -> models.User: + +def create_user( + db: Session, app_id: uuid.UUID, user: schemas.UserCreate +) -> models.User: honcho_user = models.User( app_id=app_id, name=user.name, @@ -84,24 +81,44 @@ def create_user(db: Session, app_id: uuid.UUID, user: schemas.UserCreate) -> mod db.refresh(honcho_user) return honcho_user -def get_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID) -> Optional[models.User]: + +def get_user( + db: Session, app_id: uuid.UUID, user_id: uuid.UUID +) -> Optional[models.User]: stmt = ( select(models.User) .where(models.User.app_id == app_id) .where(models.User.id == user_id) - ) user = db.scalars(stmt).one_or_none() return user -def get_users(db: Session, app_id: uuid.UUID) -> Select: + +def get_user_by_name( + db: Session, app_id: uuid.UUID, name: str +) -> Optional[models.User]: stmt = ( select(models.User) .where(models.User.app_id == app_id) + .where(models.User.name == name) ) + user = db.scalars(stmt).one_or_none() + return user + + +def get_users(db: Session, app_id: uuid.UUID, reverse: bool = False) -> Select: + stmt = select(models.User).where(models.User.app_id == app_id) + if reverse: + stmt = stmt.order_by(models.User.created_at.desc()) + else: + stmt = stmt.order_by(models.User.created_at) + return stmt -def update_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate) -> models.User: + +def update_user( + db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate +) -> models.User: honcho_user = get_user(db, app_id, user_id) if honcho_user is None: raise ValueError("User not found") @@ -114,6 +131,7 @@ def update_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schema db.refresh(honcho_user) return honcho_user + # def delete_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID) -> bool: # existing_user = get_user(db, app_id, user_id) # if existing_user is None: @@ -126,8 +144,12 @@ def update_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schema # session methods ######################################################## + def get_session( - db: Session, app_id: uuid.UUID, session_id: uuid.UUID, user_id: Optional[uuid.UUID] = None + db: Session, + app_id: uuid.UUID, + session_id: uuid.UUID, + user_id: Optional[uuid.UUID] = None, ) -> Optional[models.Session]: stmt = ( select(models.Session) @@ -140,6 +162,7 @@ def get_session( session = db.scalars(stmt).one_or_none() return session + def get_sessions( db: Session, app_id: uuid.UUID, @@ -152,7 +175,7 @@ def get_sessions( .join(models.User, models.User.id == models.Session.user_id) .where(models.User.app_id == app_id) .where(models.Session.user_id == user_id) -# .where(models.Session.is_active.is_(True)) + # .where(models.Session.is_active.is_(True)) ) if reverse: @@ -192,7 +215,9 @@ def update_session( ) if honcho_session is None: raise ValueError("Session not found or does not belong to user") - if session.metadata is not None: # Need to explicitly be there won't make it empty by default + if ( + session.metadata is not None + ): # Need to explicitly be there won't make it empty by default honcho_session.h_metadata = session.metadata db.commit() db.refresh(honcho_session) @@ -216,10 +241,12 @@ def delete_session( db.commit() return True + ######################################################## # Message Methods ######################################################## + def create_message( db: Session, message: schemas.MessageCreate, @@ -270,7 +297,11 @@ def get_messages( def get_message( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID + db: Session, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, ) -> Optional[models.Message]: stmt = ( select(models.Message) @@ -284,10 +315,12 @@ def get_message( ) return db.scalars(stmt).one_or_none() + ######################################################## # metamessage methods ######################################################## + def create_metamessage( db: Session, metamessage: schemas.MetamessageCreate, @@ -316,6 +349,7 @@ def create_metamessage( db.refresh(honcho_metamessage) return honcho_metamessage + def get_metamessages( db: Session, app_id: uuid.UUID, @@ -428,7 +462,10 @@ def get_collection_by_name( def create_collection( - db: Session, collection: schemas.CollectionCreate, app_id: uuid.UUID, user_id: uuid.UUID + db: Session, + collection: schemas.CollectionCreate, + app_id: uuid.UUID, + user_id: uuid.UUID, ) -> models.Collection: honcho_collection = models.Collection( user_id=user_id, diff --git a/api/src/main.py b/api/src/main.py index bbdf46c..fefcfbc 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,14 +1,14 @@ import uuid -from fastapi import Depends, FastAPI, HTTPException, APIRouter, Request from typing import Optional, Sequence -from sqlalchemy.orm import Session -from slowapi import Limiter, _rate_limit_exceeded_handler -from slowapi.middleware import SlowAPIMiddleware -from slowapi.util import get_remote_address -from slowapi.errors import RateLimitExceeded +from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.sqlalchemy import paginate +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.errors import RateLimitExceeded +from slowapi.middleware import SlowAPIMiddleware +from slowapi.util import get_remote_address +from sqlalchemy.orm import Session from . import crud, models, schemas from .db import SessionLocal, engine @@ -39,6 +39,7 @@ def get_db(): finally: db.close() + ######################################################## # App Routes ######################################################## @@ -62,6 +63,7 @@ def get_app( raise HTTPException(status_code=404, detail="App not found") return app + @app.get("/apps/name/{app_name}", response_model=schemas.App) def get_app_by_name( request: Request, @@ -101,6 +103,27 @@ def create_app( return crud.create_app(db, app=app) +@app.get("/apps/get_or_create/{app_name}", response_model=schemas.App) +def get_or_create_app( + request: Request, + app_name: str, + db: Session = Depends(get_db), +): + """Get or Create an App + + Args: + app_name (str): The name of the app + + Returns: + schemas.App: App object + + """ + app = crud.get_app_by_name(db, app_name=app_name) + if app is None: + app = crud.create_app(db, app=schemas.AppCreate(name=app_name)) + return app + + @app.put("/apps/{app_id}", response_model=schemas.App) def update_app( request: Request, @@ -149,29 +172,31 @@ def create_user( return crud.create_user(db, app_id=app_id, user=user) -@router.get("/apps/{app_id}/users", response_model=Page[schemas.User]) +@app.get("/apps/{app_id}/users", response_model=Page[schemas.User]) def get_users( request: Request, app_id: uuid.UUID, + reverse: bool = False, db: Session = Depends(get_db), ): """Get All Users for an App Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client + application using honcho Returns: list[schemas.User]: List of User objects """ - return paginate(db, crud.get_users(db, app_id=app_id)) + return paginate(db, crud.get_users(db, app_id=app_id, reverse=reverse)) -@router.get("/apps/{app_id}/users/{user_id}", response_model=schemas.User) -def get_user( +@app.get("/apps/{app_id}/users/{name}", response_model=schemas.User) +def get_user_by_name( request: Request, app_id: uuid.UUID, - user_id: uuid.UUID, + name: str, db: Session = Depends(get_db), ): """Get a User @@ -184,15 +209,15 @@ def get_user( schemas.User: User object """ - return crud.get_user(db, app_id=app_id, user_id=user_id) + return crud.get_user_by_name(db, app_id=app_id, name=name) -@router.put("/users/{user_id}", response_model=schemas.User) +@app.put("/apps/{app_id}/users/{user_id}", response_model=schemas.User) def update_user( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, - user: schemas.UserCreate, + user: schemas.UserUpdate, db: Session = Depends(get_db), ): """Update a User @@ -253,9 +278,11 @@ def create_session( """Create a Session for a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client + application using honcho user_id (uuid.UUID): The User ID representing the user, managed by the user - session (schemas.SessionCreate): The Session object containing any metadata and a location ID + session (schemas.SessionCreate): The Session object containing any + metadata and a location ID Returns: schemas.Session: The Session object of the new Session @@ -584,7 +611,7 @@ def get_metamessage( ######################################################## -@router.get("/collections/all", response_model=Page[schemas.Collection]) +@router.get("/collections", response_model=Page[schemas.Collection]) def get_collections( request: Request, app_id: uuid.UUID, @@ -597,25 +624,25 @@ def get_collections( ) -@router.get("/collections/id/{collection_id}", response_model=schemas.Collection) -def get_collection_by_id( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - db: Session = Depends(get_db), -) -> schemas.Collection: - honcho_collection = crud.get_collection_by_id( - db, app_id=app_id, user_id=user_id, collection_id=collection_id - ) - if honcho_collection is None: - raise HTTPException( - status_code=404, detail="collection not found or does not belong to user" - ) - return honcho_collection - - -@router.get("/collections/name/{name}", response_model=schemas.Collection) +# @router.get("/collections/id/{collection_id}", response_model=schemas.Collection) +# def get_collection_by_id( +# request: Request, +# app_id: uuid.UUID, +# user_id: uuid.UUID, +# collection_id: uuid.UUID, +# db: Session = Depends(get_db), +# ) -> schemas.Collection: +# honcho_collection = crud.get_collection_by_id( +# db, app_id=app_id, user_id=user_id, collection_id=collection_id +# ) +# if honcho_collection is None: +# raise HTTPException( +# status_code=404, detail="collection not found or does not belong to user" +# ) +# return honcho_collection + + +@router.get("/collections/{name}", response_model=schemas.Collection) def get_collection_by_name( request: Request, app_id: uuid.UUID, diff --git a/api/src/models.py b/api/src/models.py index a4c166c..ebd7cdd 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -4,7 +4,15 @@ from dotenv import load_dotenv from pgvector.sqlalchemy import Vector -from sqlalchemy import JSON, Column, DateTime, ForeignKey, String, UniqueConstraint, Uuid +from sqlalchemy import ( + JSON, + Column, + DateTime, + ForeignKey, + String, + UniqueConstraint, + Uuid, +) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -15,7 +23,8 @@ DATABASE_TYPE = os.getenv("DATABASE_TYPE", "postgres") ColumnType = JSONB if DATABASE_TYPE == "postgres" else JSON - + + class App(Base): __tablename__ = "apps" id: Mapped[uuid.UUID] = mapped_column( @@ -24,12 +33,12 @@ class App(Base): name: Mapped[str] = mapped_column(String(512), index=True, unique=True) users = relationship("User", back_populates="app") created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) # Add any additional fields for an app here + class User(Base): __tablename__ = "users" id: Mapped[uuid.UUID] = mapped_column( @@ -38,17 +47,19 @@ class User(Base): name: Mapped[str] = mapped_column(String(512), index=True) h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) app_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("apps.id"), index=True) app = relationship("App", back_populates="users") sessions = relationship("Session", back_populates="user") collections = relationship("Collection", back_populates="user") + __table_args__ = (UniqueConstraint("name", "app_id", name="unique_name_app_user"),) + def __repr__(self) -> str: return f"User(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, created_at={self.created_at}, h_metadata={self.h_metadata})" + class Session(Base): __tablename__ = "sessions" id: Mapped[uuid.UUID] = mapped_column( @@ -58,8 +69,7 @@ class Session(Base): is_active: Mapped[bool] = mapped_column(default=True) h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) messages = relationship("Message", back_populates="session") user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), index=True) @@ -79,8 +89,7 @@ class Message(Base): content: Mapped[str] = mapped_column(String(65535)) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) session = relationship("Session", back_populates="messages") metamessages = relationship("Metamessage", back_populates="message") @@ -100,8 +109,7 @@ class Metamessage(Base): message = relationship("Message", back_populates="metamessages") created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) def __repr__(self) -> str: @@ -115,8 +123,7 @@ class Collection(Base): ) name: Mapped[str] = mapped_column(String(512), index=True) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) documents = relationship( "Document", back_populates="collection", cascade="all, delete, delete-orphan" @@ -125,9 +132,10 @@ class Collection(Base): user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), index=True) __table_args__ = ( - UniqueConstraint("name", "user_id", name="unique_name_app_user"), + UniqueConstraint("name", "user_id", name="unique_name_collection_user"), ) + class Document(Base): __tablename__ = "documents" id: Mapped[uuid.UUID] = mapped_column( @@ -137,8 +145,7 @@ class Document(Base): content: Mapped[str] = mapped_column(String(65535)) embedding = mapped_column(Vector(1536)) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.datetime.utcnow + DateTime(timezone=True), default=datetime.datetime.utcnow ) collection_id = Column(Uuid, ForeignKey("collections.id"), index=True) diff --git a/api/src/schemas.py b/api/src/schemas.py index 429fb1b..a0a779c 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -2,17 +2,21 @@ import datetime import uuid + class AppBase(BaseModel): pass + class AppCreate(AppBase): name: str metadata: dict | None = {} + class AppUpdate(AppBase): name: str | None = None metadata: dict | None = None + class App(AppBase): id: uuid.UUID name: str @@ -20,47 +24,48 @@ class App(AppBase): metadata: dict created_at: datetime.datetime - @validator('metadata', pre=True, allow_reuse=True) + @validator("metadata", pre=True, allow_reuse=True) def fetch_h_metadata(cls, value, values): - if 'h_metadata' in values: - return values['h_metadata'] + if "h_metadata" in values: + return values["h_metadata"] return {} class Config: from_attributes = True - schema_extra ={ - "exclude": ["h_metadata"] - } + schema_extra = {"exclude": ["h_metadata"]} + class UserBase(BaseModel): pass + class UserCreate(UserBase): name: str metadata: dict | None = {} + class UserUpdate(UserBase): name: str | None = None metadata: dict | None = None + class User(UserBase): id: uuid.UUID app_id: uuid.UUID created_at: datetime.datetime h_metadata: dict metadata: dict - - @validator('metadata', pre=True, allow_reuse=True) + + @validator("metadata", pre=True, allow_reuse=True) def fetch_h_metadata(cls, value, values): - if 'h_metadata' in values: - return values['h_metadata'] + if "h_metadata" in values: + return values["h_metadata"] return {} class Config: from_attributes = True - schema_extra = { - "exclude": ["h_metadata"] - } + schema_extra = {"exclude": ["h_metadata"]} + class MessageBase(BaseModel): content: str @@ -79,6 +84,7 @@ class Message(MessageBase): class Config: from_attributes = True + class SessionBase(BaseModel): pass @@ -86,31 +92,31 @@ class SessionBase(BaseModel): class SessionCreate(SessionBase): location_id: str metadata: dict | None = {} - + + class SessionUpdate(SessionBase): metadata: dict | None = None + class Session(SessionBase): id: uuid.UUID # messages: list[Message] is_active: bool - user_id: str + user_id: uuid.UUID location_id: str h_metadata: dict metadata: dict created_at: datetime.datetime - @validator('metadata', pre=True, allow_reuse=True) + @validator("metadata", pre=True, allow_reuse=True) def fetch_h_metadata(cls, value, values): - if 'h_metadata' in values: - return values['h_metadata'] + if "h_metadata" in values: + return values["h_metadata"] return {} class Config: from_attributes = True - schema_extra = { - "exclude": ["h_metadata"] - } + schema_extra = {"exclude": ["h_metadata"]} class MetamessageBase(BaseModel): @@ -130,34 +136,42 @@ class Metamessage(MetamessageBase): class Config: from_attributes = True + class CollectionBase(BaseModel): pass + class CollectionCreate(CollectionBase): name: str + class CollectionUpdate(CollectionBase): name: str + class Collection(CollectionBase): id: uuid.UUID name: str - user_id: str + user_id: uuid.UUID created_at: datetime.datetime class Config: from_attributes = True + class DocumentBase(BaseModel): content: str + class DocumentCreate(DocumentBase): metadata: dict | None = {} + class DocumentUpdate(DocumentBase): metadata: dict | None = None content: str | None = None + class Document(DocumentBase): id: uuid.UUID content: str @@ -166,15 +180,12 @@ class Document(DocumentBase): created_at: datetime.datetime collection_id: uuid.UUID - @validator('metadata', pre=True, allow_reuse=True) + @validator("metadata", pre=True, allow_reuse=True) def fetch_h_metadata(cls, value, values): - if 'h_metadata' in values: - return values['h_metadata'] + if "h_metadata" in values: + return values["h_metadata"] return {} class Config: from_attributes = True - schema_extra = { - "exclude": ["h_metadata"] - } - + schema_extra = {"exclude": ["h_metadata"]} diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index eda9003..6ab9451 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,4 +1,24 @@ -from .client import AsyncClient, AsyncSession, AsyncCollection, AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncGetDocumentPage, AsyncGetCollectionPage -from .sync_client import Client, Session, Collection, GetSessionPage, GetMessagePage, GetMetamessagePage, GetDocumentPage, GetCollectionPage +from .client import ( + AsyncHoncho, + AsyncUser, + AsyncSession, + AsyncCollection, + AsyncGetSessionPage, + AsyncGetMessagePage, + AsyncGetMetamessagePage, + AsyncGetDocumentPage, + AsyncGetCollectionPage, +) +from .sync_client import ( + Honcho, + User, + Session, + Collection, + GetSessionPage, + GetMessagePage, + GetMetamessagePage, + GetDocumentPage, + GetCollectionPage, +) from .schemas import Message, Metamessage, Document from .cache import LRUCache diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 6c8b17d..afdaeaf 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,14 +1,18 @@ -import uuid +from __future__ import annotations + import datetime -from typing import Dict, Optional, List +import uuid +from typing import Optional + import httpx -from .schemas import Message, Metamessage, Document + +from .schemas import Document, Message, Metamessage class AsyncGetPage: """Base class for receiving Paginated API results""" - def __init__(self, response: Dict) -> None: + def __init__(self, response: dict) -> None: """Constructor for Page with relevant information about the results and pages Args: @@ -25,27 +29,60 @@ async def next(self): pass +class AsyncGetUserPage(AsyncGetPage): + """Paginated Results for Get User Requests""" + + def __init__(self, response: dict, honcho: AsyncHoncho, reverse: bool): + """Constructor for Page Result from User Get Request + + Args: + honcho (AsyncHoncho): Honcho Client + reverse (bool): Whether to reverse the order of the results or not + response (dict): Response from API with pagination information + """ + super().__init__(response) + self.honcho = honcho + self.reverse = reverse + self.items = [ + AsyncUser( + honcho=honcho, + id=user["id"], + created_at=user["created_at"], + metadata=user["metadata"], + ) + for user in response["items"] + ] + + async def next(self): + if self.page >= self.pages: + return None + return await self.honcho.get_users( + page=(self.page + 1), page_size=self.page_size, reverse=self.reverse + ) + + class AsyncGetSessionPage(AsyncGetPage): """Paginated Results for Get Session Requests""" - def __init__(self, client, options: Dict, response: Dict): + def __init__( + self, response: dict, user: AsyncUser, reverse: bool, location_id: Optional[str] + ): """Constructor for Page Result from Session Get Request Args: - client (AsyncClient): Honcho Client - options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional - response (Dict): Response from API with pagination information + user (AsyncUser): Honcho User associated with the session + location_id (str): ID of the location associated with the session + reverse (bool): Whether to reverse the order of the results or not + response (dict): Response from API with pagination information """ super().__init__(response) - self.client = client - self.user_id = options["user_id"] - self.location_id = options["location_id"] - self.reverse = options["reverse"] + self.user = user + self.location_id = location_id + self.reverse = reverse self.items = [ AsyncSession( - client=client, + user=user, id=session["id"], - user_id=session["user_id"], location_id=session["location_id"], is_active=session["is_active"], metadata=session["metadata"], @@ -57,12 +94,12 @@ def __init__(self, client, options: Dict, response: Dict): async def next(self): """Get the next page of results Returns: - AsyncGetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + AsyncGetSessionPage | None: Next Page of Results or None if there + are no more sessions to retreive from a query """ if self.page >= self.pages: return None - return await self.client.get_sessions( - user_id=self.user_id, + return await self.user.get_sessions( location_id=self.location_id, page=(self.page + 1), page_size=self.page_size, @@ -73,16 +110,16 @@ async def next(self): class AsyncGetMessagePage(AsyncGetPage): """Paginated Results for Get Session Requests""" - def __init__(self, session, options, response: Dict): + def __init__(self, response: dict, session: AsyncSession, reverse: bool): """Constructor for Page Result from Session Get Request Args: session (AsyncSession): Session the returned messages are associated with - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information """ super().__init__(response) self.session = session - self.reverse = options["reverse"] + self.reverse = reverse self.items = [ Message( session_id=session.id, @@ -97,7 +134,8 @@ def __init__(self, session, options, response: Dict): async def next(self): """Get the next page of results Returns: - AsyncGetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query + AsyncGetMessagePage | None: Next Page of Results or None if there + are no more messages to retreive from a query """ if self.page >= self.pages: return None @@ -107,21 +145,27 @@ async def next(self): class AsyncGetMetamessagePage(AsyncGetPage): - def __init__(self, session, options: Dict, response: Dict) -> None: + def __init__( + self, + response: dict, + session, + reverse: bool, + message_id: Optional[uuid.UUID], + metamessage_type: Optional[str], + ) -> None: """Constructor for Page Result from Metamessage Get Request Args: - session (AsyncSession): Session the returned messages are associated with - options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both optional - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + session (AsyncSession): Session the returned messages are + associated with + reverse (bool): Whether to reverse the order of the results """ super().__init__(response) self.session = session - self.message_id = options["message_id"] if "message_id" in options else None - self.metamessage_type = ( - options["metamessage_type"] if "metamessage_type" in options else None - ) - self.reverse = options["reverse"] + self.message_id = message_id + self.metamessage_type = metamessage_type + self.reverse = reverse self.items = [ Metamessage( id=metamessage["id"], @@ -136,7 +180,8 @@ def __init__(self, session, options: Dict, response: Dict) -> None: async def next(self): """Get the next page of results Returns: - AsyncGetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query + AsyncGetMetamessagePage | None: Next Page of Results or None if + there are no more metamessages to retreive from a query """ if self.page >= self.pages: return None @@ -152,16 +197,18 @@ async def next(self): class AsyncGetDocumentPage(AsyncGetPage): """Paginated results for Get Document requests""" - def __init__(self, collection, options, response: Dict) -> None: + def __init__(self, response: dict, collection, reverse: bool) -> None: """Constructor for Page Result from Document Get Request Args: - collection (AsyncCollection): Collection the returned documents are associated with - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + collection (AsyncCollection): Collection the returned documents are + associated with + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) self.collection = collection - self.reverse = options["reverse"] + self.reverse = reverse self.items = [ Document( id=document["id"], @@ -176,7 +223,8 @@ def __init__(self, collection, options, response: Dict) -> None: async def next(self): """Get the next page of results Returns: - AsyncGetDocumentPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + AsyncGetDocumentPage | None: Next Page of Results or None if there + are no more sessions to retreive from a query """ if self.page >= self.pages: return None @@ -188,23 +236,21 @@ async def next(self): class AsyncGetCollectionPage(AsyncGetPage): """Paginated results for Get Collection requests""" - def __init__(self, client, options: Dict, response: Dict): + def __init__(self, response: dict, user: AsyncUser, reverse: bool): """Constructor for page result from Get Collection Request Args: - client (Async Client): Honcho Client - options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + user (AsyncUser): Honcho Client + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) - self.client = client - self.user_id = options["user_id"] - self.reverse = options["reverse"] + self.user = user + self.reverse = reverse self.items = [ AsyncCollection( - client=client, + user=user, id=collection["id"], - user_id=collection["user_id"], name=collection["name"], created_at=collection["created_at"], ) @@ -214,51 +260,202 @@ def __init__(self, client, options: Dict, response: Dict): async def next(self): """Get the next page of results Returns: - AsyncGetCollectionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + AsyncGetCollectionPage | None: Next Page of Results or None if + there are no more sessions to retreive from a query """ if self.page >= self.pages: return None - return await self.client.get_collections( - user_id=self.user_id, + return await self.user.get_collections( page=self.page + 1, page_size=self.page_size, reverse=self.reverse, ) -class AsyncClient: +class AsyncHoncho: """Honcho API Client Object""" - def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): + def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" - self.base_url = base_url # Base URL for the instance of the Honcho API - self.app_id = app_id # Representing ID of the client application - self.client = httpx.AsyncClient() + self.server_url: str = base_url # Base URL for the instance of the Honcho API + self.client: httpx.AsyncClient = httpx.AsyncClient() + self.app_name: str = app_name # Representing name of the client application + self.app_id: uuid.UUID + self.metadata: dict + + async def initialize(self): + res = await self.client.get( + f"{self.server_url}/apps/get_or_create/{self.app_name}" + ) + res.raise_for_status() + data = res.json() + self.app_id: uuid.UUID = data["id"] + self.metadata: dict = data["metadata"] @property - def common_prefix(self): + def base_url(self): """Shorcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.server_url}/apps/{self.app_id}/users" - async def get_session(self, user_id: str, session_id: uuid.UUID): + async def create_user(self, name: str, metadata: Optional[dict] = None): + """Create a new user by name + + Args: + name (str): The name of the user + metadata (dict, optional): The metadata for the user. Defaults to {}. + + Returns: + AsyncUser: The created User object + """ + if metadata is None: + metadata = {} + url = f"{self.base_url}" + response = await self.client.post( + url, json={"name": name, "metadata": metadata} + ) + response.raise_for_status() + data = response.json() + return AsyncUser( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) + + async def get_user(self, name: str): + """Get a user by name + + Args: + name (str): The name of the user + + Returns: + AsyncUser: The User object + """ + url = f"{self.base_url}/{name}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncUser(self, **data) + + async def get_users( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ): + """Get Paginated list of users + + Returns: + AsyncGetUserPage: Paginated list of users + """ + url = f"{self.base_url}?page={page}&size={page_size}&reverse={reverse}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncGetUserPage(data, self, reverse) + + async def get_users_generator( + self, + reverse: bool = False, + ): + """Shortcut Generator for get_users. Generator to iterate through + all users in an app + + Args: + reverse (bool): Whether to reverse the order of the results + + Yields: + AsyncUser: The User object + + """ + page = 1 + page_size = 50 + get_user_response = await self.get_users(page, page_size, reverse) + while True: + for session in get_user_response.items: + yield session + + new_users = await get_user_response.next() + if not new_users: + break + + get_user_response = new_users + + # async def get_user_by_id(self, id: uuid.UUID): + # """Get a user by id + + # Args: + # id (uuid.UUID): The id of the user + + # Returns: + # AsyncUser: The User object + # """ + # url = f"{self.common_prefix}/users/{id}" + # response = await self.client.get(url) + # response.raise_for_status() + # data = response.json() + # return AsyncUser(self, **data) + + +class AsyncUser: + """Represents a single user in an app""" + + def __init__( + self, + honcho: AsyncHoncho, + id: uuid.UUID, + metadata: dict, + created_at: datetime.datetime, + ): + """Constructor for User""" + # self.base_url: str = honcho.base_url + self.honcho: AsyncHoncho = honcho + self.id: uuid.UUID = id + self.metadata: dict = metadata + self.created_at: datetime.datetime = created_at + + @property + def base_url(self): + """Shortcut for common API prefix. made a property to prevent tampering""" + return f"{self.honcho.base_url}/{self.id}" + + def __str__(self): + """String representation of User""" + return f"AsyncUser(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + + # TODO method to update metadata + async def update_user(self, metadata: dict): + """Updates a user's metadata + + Args: + metadata (dict): The new metadata for the user + + Returns: + AsyncUser: The updated User object + + """ + url = f"{self.base_url}" + response = await self.honcho.client.put(url, json=metadata) + response.raise_for_status() + data = response.json() + self.metadata = data["metadata"] + # TODO update this object's metadata field + # return AsyncUser(self.honcho, **data) + + async def get_session(self, session_id: uuid.UUID): """Get a specific session for a user by ID Args: - user_id (str): The User ID representing the user, managed by the user session_id (uuid.UUID): The ID of the Session to retrieve Returns: AsyncSession: The Session object of the requested Session """ - url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" - response = await self.client.get(url) + url = f"{self.base_url}/sessions/{session_id}" + response = await self.honcho.client.get(url) response.raise_for_status() data = response.json() return AsyncSession( - client=self, + user=self, id=data["id"], - user_id=data["user_id"], location_id=data["location_id"], is_active=data["is_active"], metadata=data["metadata"], @@ -267,7 +464,6 @@ async def get_session(self, user_id: str, session_id: uuid.UUID): async def get_sessions( self, - user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50, @@ -276,8 +472,8 @@ async def get_sessions( """Return sessions associated with a user paginated Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session + location_id (str, optional): Optional Location ID representing the + location of a session page (int, optional): The page of results to return page_size (int, optional): The number of results to return @@ -286,26 +482,25 @@ async def get_sessions( """ url = ( - f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}&reverse={reverse}" + f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}" + (f"&location_id={location_id}" if location_id else "") ) - response = await self.client.get(url) + response = await self.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"location_id": location_id, "user_id": user_id, "reverse": reverse} - return AsyncGetSessionPage(self, options, data) + return AsyncGetSessionPage(data, self, reverse, location_id) async def get_sessions_generator( self, - user_id: str, location_id: Optional[str] = None, reverse: bool = False, ): - """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + """Shortcut Generator for get_sessions. Generator to iterate through + all sessions for a user in an app Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session + location_id (str, optional): Optional Location ID representing the + location of a session Yields: AsyncSession: The Session object of the requested Session @@ -314,10 +509,9 @@ async def get_sessions_generator( page = 1 page_size = 50 get_session_response = await self.get_sessions( - user_id, location_id, page, page_size, reverse + location_id, page, page_size, reverse ) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for session in get_session_response.items: yield session @@ -328,28 +522,29 @@ async def get_sessions_generator( get_session_response = new_sessions async def create_session( - self, user_id: str, location_id: str = "default", metadata: Dict = {} + self, location_id: str = "default", metadata: Optional[dict] = None ): """Create a session for a user Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session - metadata (Dict, optional): Optional session metadata + location_id (str, optional): Optional Location ID representing the + location of a session + metadata (dict, optional): Optional session metadata Returns: AsyncSession: The Session object of the new Session """ + if metadata is None: + metadata = {} data = {"location_id": location_id, "metadata": metadata} - url = f"{self.common_prefix}/users/{user_id}/sessions" - response = await self.client.post(url, json=data) + url = f"{self.base_url}/sessions" + response = await self.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return AsyncSession( self, id=data["id"], - user_id=user_id, location_id=location_id, metadata=metadata, is_active=data["is_active"], @@ -358,13 +553,11 @@ async def create_session( async def create_collection( self, - user_id: str, name: str, ): """Create a collection for a user Args: - user_id (str): The User ID representing the user, managed by the user name (str): unique name for the collection for the user Returns: @@ -372,48 +565,44 @@ async def create_collection( """ data = {"name": name} - url = f"{self.common_prefix}/users/{user_id}/collections" - response = await self.client.post(url, json=data) + url = f"{self.base_url}/collections" + response = await self.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return AsyncCollection( self, id=data["id"], - user_id=user_id, name=name, created_at=data["created_at"], ) - async def get_collection(self, user_id: str, name: str): + async def get_collection(self, name: str): """Get a specific collection for a user by name Args: - user_id (str): The User ID representing the user, managed by the user name (str): The name of the collection to get Returns: AsyncCollection: The Session object of the requested Session """ - url = f"{self.common_prefix}/users/{user_id}/collections/name/{name}" - response = await self.client.get(url) + url = f"{self.base_url}/collections/{name}" + response = await self.honcho.client.get(url) response.raise_for_status() data = response.json() return AsyncCollection( - client=self, + user=self, id=data["id"], - user_id=data["user_id"], name=data["name"], created_at=data["created_at"], ) async def get_collections( - self, user_id: str, page: int = 1, page_size: int = 50, reverse: bool = False + self, page: int = 1, page_size: int = 50, reverse: bool = False ): """Return collections associated with a user paginated Args: - user_id (str): The User ID representing the user to get the collection for page (int, optional): The page of results to return page_size (int, optional): The number of results to return reverse (bool): Whether to reverse the order of the results @@ -422,18 +611,18 @@ async def get_collections( AsyncGetCollectionPage: Page or results for get_collections query """ - url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}&reverse={reverse}" - response = await self.client.get(url) + url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + response = await self.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"user_id": user_id, "reverse": reverse} - return AsyncGetCollectionPage(self, options, data) + return AsyncGetCollectionPage(data, self, reverse) - async def get_collections_generator(self, user_id: str, reverse: bool = False): - """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + async def get_collections_generator(self, reverse: bool = False): + """Shortcut Generator for get_sessions. Generator to iterate through + all sessions for a user in an app Args: - user_id (str): The User ID representing the user, managed by the user + reverse (bool): Whether to reverse the order of the results Yields: AsyncCollection: The Session object of the requested Session @@ -441,11 +630,8 @@ async def get_collections_generator(self, user_id: str, reverse: bool = False): """ page = 1 page_size = 50 - get_collection_response = await self.get_collections( - user_id, page, page_size, reverse - ) + get_collection_response = await self.get_collections(page, page_size, reverse) while True: - # get_collection_response = self.get_collections(user_id, location_id, page, page_size) for collection in get_collection_response.items: yield collection @@ -461,33 +647,29 @@ class AsyncSession: def __init__( self, - client: AsyncClient, + user: AsyncUser, id: uuid.UUID, - user_id: str, location_id: str, metadata: dict, is_active: bool, created_at: datetime.datetime, ): """Constructor for Session""" - self.base_url: str = client.base_url - self.client: httpx.AsyncClient = client.client - self.app_id: str = client.app_id + self.user: AsyncUser = user self.id: uuid.UUID = id - self.user_id: str = user_id self.location_id: str = location_id self.metadata: dict = metadata self._is_active: bool = is_active self.created_at: datetime.datetime = created_at @property - def common_prefix(self): + def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.user.base_url}/sessions/{self.id}" def __str__(self): """String representation of Session""" - return f"AsyncSession(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" + return f"AsyncSession(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" # noqa: E501 @property def is_active(self): @@ -508,8 +690,8 @@ async def create_message(self, is_user: bool, content: str): if not self.is_active: raise Exception("Session is inactive") data = {"is_user": is_user, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages" - response = await self.client.post(url, json=data) + url = f"{self.base_url}/messages" + response = await self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Message( @@ -530,8 +712,8 @@ async def get_message(self, message_id: uuid.UUID) -> Message: Message: The Message object """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages/{message_id}" - response = await self.client.get(url) + url = f"{self.base_url}/messages/{message_id}" + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Message( @@ -556,15 +738,15 @@ async def get_messages( AsyncGetMessagePage: Page of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}&reverse={reverse}" - response = await self.client.get(url) + url = f"{self.base_url}/messages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"reverse": reverse} - return AsyncGetMessagePage(self, options, data) + return AsyncGetMessagePage(data, self, reverse) async def get_messages_generator(self, reverse: bool = False): - """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + """Shortcut Generator for get_messages. Generator to iterate through + all messages for a session in an app Yields: Message: The Message object of the next Message @@ -574,7 +756,6 @@ async def get_messages_generator(self, reverse: bool = False): page_size = 50 get_messages_page = await self.get_messages(page, page_size, reverse) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for message in get_messages_page.items: yield message @@ -605,10 +786,8 @@ async def create_metamessage( "content": content, "message_id": message.id, } - url = ( - f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" - ) - response = await self.client.post(url, json=data) + url = f"{self.base_url}/metamessages" + response = await self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Metamessage( @@ -629,8 +808,8 @@ async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: Message: The Message object """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages/{metamessage_id}" - response = await self.client.get(url) + url = f"{self.base_url}/metamessages/{metamessage_id}" + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Metamessage( @@ -652,27 +831,28 @@ async def get_metamessages( """Get all messages for a session Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + metamessage_type (str, optional): The type of the metamessage + message (Message, optional): The message to associate the metamessage with + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page + reverse (bool): Whether to reverse the order of the results Returns: - list[Dict]: List of Message objects + list[dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 if metamessage_type: url += f"&metamessage_type={metamessage_type}" if message: url += f"&message_id={message.id}" - response = await self.client.get(url) + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = { - "metamessage_type": metamessage_type, - "message_id": message.id if message else None, - "reverse": reverse, - } - return AsyncGetMetamessagePage(self, options, data) + message_id = message.id if message else None + return AsyncGetMetamessagePage( + data, self, reverse, message_id, metamessage_type + ) async def get_metamessages_generator( self, @@ -680,7 +860,8 @@ async def get_metamessages_generator( message: Optional[Message] = None, reverse: bool = False, ): - """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app + """Shortcut Generator for get_metamessages. Generator to iterate + through all metamessages for a session in an app Args: metamessage_type (str, optional): Optional Metamessage type to filter by @@ -709,26 +890,26 @@ async def get_metamessages_generator( get_metamessages_page = new_messages - async def update(self, metadata: Dict): + async def update(self, metadata: dict): """Update the metadata of a session Args: - metadata (Dict): The Session object containing any new metadata + metadata (dict): The Session object containing any new metadata Returns: boolean: Whether the session was successfully updated """ info = {"metadata": metadata} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" - response = await self.client.put(url, json=info) + url = f"{self.base_url}" + response = await self.user.honcho.client.put(url, json=info) success = response.status_code < 400 self.metadata = metadata return success async def close(self): """Closes a session by marking it as inactive""" - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" - response = await self.client.delete(url) + url = f"{self.base_url}" + response = await self.user.honcho.client.delete(url) response.raise_for_status() self._is_active = False @@ -738,29 +919,25 @@ class AsyncCollection: def __init__( self, - client: AsyncClient, + user: AsyncUser, id: uuid.UUID, - user_id: str, name: str, created_at: datetime.datetime, ): """Constructor for Collection""" - self.base_url: str = client.base_url - self.client: httpx.AsyncClient = client.client - self.app_id: str = client.app_id + self.user = user self.id: uuid.UUID = id - self.user_id: str = user_id self.name: str = name self.created_at: datetime.datetime = created_at @property - def common_prefix(self): + def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.user.base_url}/collections/{self.id}" def __str__(self): """String representation of Collection""" - return f"AsyncCollection(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, name={self.name}, created_at={self.created_at})" + return f"AsyncCollection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 async def update(self, name: str): """Update the name of the collection @@ -772,8 +949,8 @@ async def update(self, name: str): boolean: Whether the session was successfully updated """ info = {"name": name} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" - response = await self.client.put(url, json=info) + url = f"{self.base_url}" + response = await self.user.honcho.client.put(url, json=info) response.raise_for_status() success = response.status_code < 400 self.name = name @@ -781,26 +958,27 @@ async def update(self, name: str): async def delete(self): """Delete a collection and all associated documents""" - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" - response = await self.client.delete(url) + url = f"{self.base_url}" + response = await self.user.honcho.client.delete(url) response.raise_for_status() - async def create_document(self, content: str, metadata: Dict = {}): + async def create_document(self, content: str, metadata: Optional[dict] = None): """Adds a document to the collection Args: content (str): The content of the document - metadata (Dict): The metadata of the document + metadata (dict): The metadata of the document Returns: Document: The Document object of the added document """ + if metadata is None: + metadata = {} data = {"metadata": metadata, "content": content} - url = ( - f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" - ) - response = await self.client.post(url, json=data) + url = f"{self.base_url}/documents" + print(url) + response = await self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Document( @@ -821,8 +999,8 @@ async def get_document(self, document_id: uuid.UUID) -> Document: Document: The Document object """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document_id}" - response = await self.client.get(url) + url = f"{self.base_url}/documents/{document_id}" + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Document( @@ -846,15 +1024,17 @@ async def get_documents( AsyncGetDocumentPage: Page of Document objects """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}&reverse={reverse}" - response = await self.client.get(url) + url = ( + f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + ) + response = await self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"reverse": reverse} - return AsyncGetDocumentPage(self, options, data) + return AsyncGetDocumentPage(data, self, reverse) async def get_documents_generator(self, reverse: bool = False): - """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + """Shortcut Generator for get_documents. Generator to iterate through + all documents for a collection in an app Yields: Document: The Document object of the next Document @@ -873,7 +1053,7 @@ async def get_documents_generator(self, reverse: bool = False): get_documents_page = new_documents - async def query(self, query: str, top_k: int = 5) -> List[Document]: + async def query(self, query: str, top_k: int = 5) -> list[Document]: """query the documents by cosine distance Args: query (str): The query string to compare other embeddings too @@ -882,8 +1062,8 @@ async def query(self, query: str, top_k: int = 5) -> List[Document]: Returns: List[Document]: The response from the query with matching documents """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/query?query={query}&top_k={top_k}" - response = await self.client.get(url) + url = f"{self.base_url}/query?query={query}&top_k={top_k}" + response = await self.user.honcho.client.get(url) response.raise_for_status() data = [ Document( @@ -898,13 +1078,13 @@ async def query(self, query: str, top_k: int = 5) -> List[Document]: return data async def update_document( - self, document: Document, content: Optional[str], metadata: Optional[Dict] + self, document: Document, content: Optional[str], metadata: Optional[dict] ) -> Document: """Update a document in the collection Args: document (Document): The Document to update - metadata (Dict): The metadata of the document + metadata (dict): The metadata of the document content (str): The content of the document Returns: @@ -913,8 +1093,8 @@ async def update_document( if metadata is None and content is None: raise ValueError("metadata and content cannot both be None") data = {"metadata": metadata, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" - response = await self.client.put(url, json=data) + url = f"{self.base_url}/documents/{document.id}" + response = await self.user.honcho.client.put(url, json=data) response.raise_for_status() data = response.json() return Document( @@ -934,8 +1114,8 @@ async def delete_document(self, document: Document) -> bool: Returns: boolean: Whether the document was successfully deleted """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" - response = await self.client.delete(url) + url = f"{self.base_url}/documents/{document.id}" + response = await self.user.honcho.client.delete(url) response.raise_for_status() success = response.status_code < 400 return success diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 5606bb6..69a4fe7 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1,14 +1,18 @@ -import uuid +from __future__ import annotations + import datetime -from typing import Dict, Optional, List +import uuid +from typing import Optional + import httpx -from .schemas import Message, Metamessage, Document + +from .schemas import Document, Message, Metamessage class GetPage: """Base class for receiving Paginated API results""" - def __init__(self, response: Dict) -> None: + def __init__(self, response: dict) -> None: """Constructor for Page with relevant information about the results and pages Args: @@ -25,27 +29,60 @@ def next(self): pass +class GetUserPage(GetPage): + """Paginated Results for Get User Requests""" + + def __init__(self, response: dict, honcho: Honcho, reverse: bool): + """Constructor for Page Result from User Get Request + + Args: + honcho (Honcho): Honcho Client + reverse (bool): Whether to reverse the order of the results or not + response (dict): Response from API with pagination information + """ + super().__init__(response) + self.honcho = honcho + self.reverse = reverse + self.items = [ + User( + honcho=honcho, + id=user["id"], + created_at=user["created_at"], + metadata=user["metadata"], + ) + for user in response["items"] + ] + + def next(self): + if self.page >= self.pages: + return None + return self.honcho.get_users( + page=(self.page + 1), page_size=self.page_size, reverse=self.reverse + ) + + class GetSessionPage(GetPage): """Paginated Results for Get Session Requests""" - def __init__(self, client, options: Dict, response: Dict): + def __init__( + self, response: dict, user: User, reverse: bool, location_id: Optional[str] + ): """Constructor for Page Result from Session Get Request Args: - client (Client): Honcho Client - options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are user_id which is required and location_id which is optional - response (Dict): Response from API with pagination information + user (User): Honcho User associated with the session + location_id (str): ID of the location associated with the session + reverse (bool): Whether to reverse the order of the results or not + response (dict): Response from API with pagination information """ super().__init__(response) - self.client = client - self.user_id = options["user_id"] - self.location_id = options["location_id"] - self.reverse = options["reverse"] + self.user = user + self.location_id = location_id + self.reverse = reverse self.items = [ Session( - client=client, + user=user, id=session["id"], - user_id=session["user_id"], location_id=session["location_id"], is_active=session["is_active"], metadata=session["metadata"], @@ -57,12 +94,12 @@ def __init__(self, client, options: Dict, response: Dict): def next(self): """Get the next page of results Returns: - GetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + GetSessionPage | None: Next Page of Results or None if there + are no more sessions to retreive from a query """ if self.page >= self.pages: return None - return self.client.get_sessions( - user_id=self.user_id, + return self.user.get_sessions( location_id=self.location_id, page=(self.page + 1), page_size=self.page_size, @@ -73,16 +110,16 @@ def next(self): class GetMessagePage(GetPage): """Paginated Results for Get Session Requests""" - def __init__(self, session, options, response: Dict): + def __init__(self, response: dict, session: Session, reverse: bool): """Constructor for Page Result from Session Get Request Args: session (Session): Session the returned messages are associated with - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information """ super().__init__(response) self.session = session - self.reverse = options["reverse"] + self.reverse = reverse self.items = [ Message( session_id=session.id, @@ -97,7 +134,8 @@ def __init__(self, session, options, response: Dict): def next(self): """Get the next page of results Returns: - GetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query + GetMessagePage | None: Next Page of Results or None if there + are no more messages to retreive from a query """ if self.page >= self.pages: return None @@ -107,21 +145,27 @@ def next(self): class GetMetamessagePage(GetPage): - def __init__(self, session, options: Dict, response: Dict) -> None: + def __init__( + self, + response: dict, + session, + reverse: bool, + message_id: Optional[uuid.UUID], + metamessage_type: Optional[str], + ) -> None: """Constructor for Page Result from Metamessage Get Request Args: - session (Session): Session the returned messages are associated with - options (Dict): Options for the request used mainly for next() to filter queries. The two parameters available are message_id and metamessage_type which are both optional - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + session (Session): Session the returned messages are + associated with + reverse (bool): Whether to reverse the order of the results """ super().__init__(response) self.session = session - self.message_id = options["message_id"] if "message_id" in options else None - self.metamessage_type = ( - options["metamessage_type"] if "metamessage_type" in options else None - ) - self.reverse = options["reverse"] + self.message_id = message_id + self.metamessage_type = metamessage_type + self.reverse = reverse self.items = [ Metamessage( id=metamessage["id"], @@ -136,7 +180,8 @@ def __init__(self, session, options: Dict, response: Dict) -> None: def next(self): """Get the next page of results Returns: - GetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query + GetMetamessagePage | None: Next Page of Results or None if + there are no more metamessages to retreive from a query """ if self.page >= self.pages: return None @@ -152,16 +197,18 @@ def next(self): class GetDocumentPage(GetPage): """Paginated results for Get Document requests""" - def __init__(self, collection, options, response: Dict) -> None: + def __init__(self, response: dict, collection, reverse: bool) -> None: """Constructor for Page Result from Document Get Request Args: - collection (Collection): Collection the returned documents are associated with - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + collection (Collection): Collection the returned documents are + associated with + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) self.collection = collection - self.reverse = options["reverse"] + self.reverse = reverse self.items = [ Document( id=document["id"], @@ -176,7 +223,8 @@ def __init__(self, collection, options, response: Dict) -> None: def next(self): """Get the next page of results Returns: - GetDocumentPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + GetDocumentPage | None: Next Page of Results or None if there + are no more sessions to retreive from a query """ if self.page >= self.pages: return None @@ -188,23 +236,21 @@ def next(self): class GetCollectionPage(GetPage): """Paginated results for Get Collection requests""" - def __init__(self, client, options: Dict, response: Dict): + def __init__(self, response: dict, user: User, reverse: bool): """Constructor for page result from Get Collection Request Args: - client ( Client): Honcho Client - options (Dict): Options for the request used mainly for next() to filter queries. The only parameter available is user_id which is required - response (Dict): Response from API with pagination information + response (dict): Response from API with pagination information + user (User): Honcho Client + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) - self.client = client - self.user_id = options["user_id"] - self.reverse = options["reverse"] + self.user = user + self.reverse = reverse self.items = [ Collection( - client=client, + user=user, id=collection["id"], - user_id=collection["user_id"], name=collection["name"], created_at=collection["created_at"], ) @@ -214,51 +260,202 @@ def __init__(self, client, options: Dict, response: Dict): def next(self): """Get the next page of results Returns: - GetCollectionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query + GetCollectionPage | None: Next Page of Results or None if + there are no more sessions to retreive from a query """ if self.page >= self.pages: return None - return self.client.get_collections( - user_id=self.user_id, + return self.user.get_collections( page=self.page + 1, page_size=self.page_size, reverse=self.reverse, ) -class Client: +class Honcho: """Honcho API Client Object""" - def __init__(self, app_id: str, base_url: str = "https://demo.honcho.dev"): + def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): """Constructor for Client""" - self.base_url = base_url # Base URL for the instance of the Honcho API - self.app_id = app_id # Representing ID of the client application - self.client = httpx.Client() + self.server_url: str = base_url # Base URL for the instance of the Honcho API + self.client: httpx.Client = httpx.Client() + self.app_name: str = app_name # Representing name of the client application + self.app_id: uuid.UUID + self.metadata: dict + + def initialize(self): + res = self.client.get( + f"{self.server_url}/apps/get_or_create/{self.app_name}" + ) + res.raise_for_status() + data = res.json() + self.app_id: uuid.UUID = data["id"] + self.metadata: dict = data["metadata"] @property - def common_prefix(self): + def base_url(self): """Shorcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.server_url}/apps/{self.app_id}/users" - def get_session(self, user_id: str, session_id: uuid.UUID): + def create_user(self, name: str, metadata: Optional[dict] = None): + """Create a new user by name + + Args: + name (str): The name of the user + metadata (dict, optional): The metadata for the user. Defaults to {}. + + Returns: + User: The created User object + """ + if metadata is None: + metadata = {} + url = f"{self.base_url}" + response = self.client.post( + url, json={"name": name, "metadata": metadata} + ) + response.raise_for_status() + data = response.json() + return User( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) + + def get_user(self, name: str): + """Get a user by name + + Args: + name (str): The name of the user + + Returns: + User: The User object + """ + url = f"{self.base_url}/{name}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return User(self, **data) + + def get_users( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ): + """Get Paginated list of users + + Returns: + GetUserPage: Paginated list of users + """ + url = f"{self.base_url}?page={page}&size={page_size}&reverse={reverse}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return GetUserPage(data, self, reverse) + + def get_users_generator( + self, + reverse: bool = False, + ): + """Shortcut Generator for get_users. Generator to iterate through + all users in an app + + Args: + reverse (bool): Whether to reverse the order of the results + + Yields: + User: The User object + + """ + page = 1 + page_size = 50 + get_user_response = self.get_users(page, page_size, reverse) + while True: + for session in get_user_response.items: + yield session + + new_users = get_user_response.next() + if not new_users: + break + + get_user_response = new_users + + # def get_user_by_id(self, id: uuid.UUID): + # """Get a user by id + + # Args: + # id (uuid.UUID): The id of the user + + # Returns: + # User: The User object + # """ + # url = f"{self.common_prefix}/users/{id}" + # response = self.client.get(url) + # response.raise_for_status() + # data = response.json() + # return User(self, **data) + + +class User: + """Represents a single user in an app""" + + def __init__( + self, + honcho: Honcho, + id: uuid.UUID, + metadata: dict, + created_at: datetime.datetime, + ): + """Constructor for User""" + # self.base_url: str = honcho.base_url + self.honcho: Honcho = honcho + self.id: uuid.UUID = id + self.metadata: dict = metadata + self.created_at: datetime.datetime = created_at + + @property + def base_url(self): + """Shortcut for common API prefix. made a property to prevent tampering""" + return f"{self.honcho.base_url}/{self.id}" + + def __str__(self): + """String representation of User""" + return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + + # TODO method to update metadata + def update_user(self, metadata: dict): + """Updates a user's metadata + + Args: + metadata (dict): The new metadata for the user + + Returns: + User: The updated User object + + """ + url = f"{self.base_url}" + response = self.honcho.client.put(url, json=metadata) + response.raise_for_status() + data = response.json() + self.metadata = data["metadata"] + # TODO update this object's metadata field + # return User(self.honcho, **data) + + def get_session(self, session_id: uuid.UUID): """Get a specific session for a user by ID Args: - user_id (str): The User ID representing the user, managed by the user session_id (uuid.UUID): The ID of the Session to retrieve Returns: Session: The Session object of the requested Session """ - url = f"{self.common_prefix}/users/{user_id}/sessions/{session_id}" - response = self.client.get(url) + url = f"{self.base_url}/sessions/{session_id}" + response = self.honcho.client.get(url) response.raise_for_status() data = response.json() return Session( - client=self, + user=self, id=data["id"], - user_id=data["user_id"], location_id=data["location_id"], is_active=data["is_active"], metadata=data["metadata"], @@ -267,7 +464,6 @@ def get_session(self, user_id: str, session_id: uuid.UUID): def get_sessions( self, - user_id: str, location_id: Optional[str] = None, page: int = 1, page_size: int = 50, @@ -276,8 +472,8 @@ def get_sessions( """Return sessions associated with a user paginated Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session + location_id (str, optional): Optional Location ID representing the + location of a session page (int, optional): The page of results to return page_size (int, optional): The number of results to return @@ -286,26 +482,25 @@ def get_sessions( """ url = ( - f"{self.common_prefix}/users/{user_id}/sessions?page={page}&size={page_size}&reverse={reverse}" + f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}" + (f"&location_id={location_id}" if location_id else "") ) - response = self.client.get(url) + response = self.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"location_id": location_id, "user_id": user_id, "reverse": reverse} - return GetSessionPage(self, options, data) + return GetSessionPage(data, self, reverse, location_id) def get_sessions_generator( self, - user_id: str, location_id: Optional[str] = None, reverse: bool = False, ): - """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + """Shortcut Generator for get_sessions. Generator to iterate through + all sessions for a user in an app Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session + location_id (str, optional): Optional Location ID representing the + location of a session Yields: Session: The Session object of the requested Session @@ -314,10 +509,9 @@ def get_sessions_generator( page = 1 page_size = 50 get_session_response = self.get_sessions( - user_id, location_id, page, page_size, reverse + location_id, page, page_size, reverse ) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for session in get_session_response.items: yield session @@ -328,28 +522,29 @@ def get_sessions_generator( get_session_response = new_sessions def create_session( - self, user_id: str, location_id: str = "default", metadata: Dict = {} + self, location_id: str = "default", metadata: Optional[dict] = None ): """Create a session for a user Args: - user_id (str): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session - metadata (Dict, optional): Optional session metadata + location_id (str, optional): Optional Location ID representing the + location of a session + metadata (dict, optional): Optional session metadata Returns: Session: The Session object of the new Session """ + if metadata is None: + metadata = {} data = {"location_id": location_id, "metadata": metadata} - url = f"{self.common_prefix}/users/{user_id}/sessions" - response = self.client.post(url, json=data) + url = f"{self.base_url}/sessions" + response = self.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Session( self, id=data["id"], - user_id=user_id, location_id=location_id, metadata=metadata, is_active=data["is_active"], @@ -358,13 +553,11 @@ def create_session( def create_collection( self, - user_id: str, name: str, ): """Create a collection for a user Args: - user_id (str): The User ID representing the user, managed by the user name (str): unique name for the collection for the user Returns: @@ -372,48 +565,44 @@ def create_collection( """ data = {"name": name} - url = f"{self.common_prefix}/users/{user_id}/collections" - response = self.client.post(url, json=data) + url = f"{self.base_url}/collections" + response = self.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Collection( self, id=data["id"], - user_id=user_id, name=name, created_at=data["created_at"], ) - def get_collection(self, user_id: str, name: str): + def get_collection(self, name: str): """Get a specific collection for a user by name Args: - user_id (str): The User ID representing the user, managed by the user name (str): The name of the collection to get Returns: Collection: The Session object of the requested Session """ - url = f"{self.common_prefix}/users/{user_id}/collections/name/{name}" - response = self.client.get(url) + url = f"{self.base_url}/collections/{name}" + response = self.honcho.client.get(url) response.raise_for_status() data = response.json() return Collection( - client=self, + user=self, id=data["id"], - user_id=data["user_id"], name=data["name"], created_at=data["created_at"], ) def get_collections( - self, user_id: str, page: int = 1, page_size: int = 50, reverse: bool = False + self, page: int = 1, page_size: int = 50, reverse: bool = False ): """Return collections associated with a user paginated Args: - user_id (str): The User ID representing the user to get the collection for page (int, optional): The page of results to return page_size (int, optional): The number of results to return reverse (bool): Whether to reverse the order of the results @@ -422,18 +611,18 @@ def get_collections( GetCollectionPage: Page or results for get_collections query """ - url = f"{self.common_prefix}/users/{user_id}/collections/all?page={page}&size={page_size}&reverse={reverse}" - response = self.client.get(url) + url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + response = self.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"user_id": user_id, "reverse": reverse} - return GetCollectionPage(self, options, data) + return GetCollectionPage(data, self, reverse) - def get_collections_generator(self, user_id: str, reverse: bool = False): - """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app + def get_collections_generator(self, reverse: bool = False): + """Shortcut Generator for get_sessions. Generator to iterate through + all sessions for a user in an app Args: - user_id (str): The User ID representing the user, managed by the user + reverse (bool): Whether to reverse the order of the results Yields: Collection: The Session object of the requested Session @@ -441,11 +630,8 @@ def get_collections_generator(self, user_id: str, reverse: bool = False): """ page = 1 page_size = 50 - get_collection_response = self.get_collections( - user_id, page, page_size, reverse - ) + get_collection_response = self.get_collections(page, page_size, reverse) while True: - # get_collection_response = self.get_collections(user_id, location_id, page, page_size) for collection in get_collection_response.items: yield collection @@ -461,33 +647,29 @@ class Session: def __init__( self, - client: Client, + user: User, id: uuid.UUID, - user_id: str, location_id: str, metadata: dict, is_active: bool, created_at: datetime.datetime, ): """Constructor for Session""" - self.base_url: str = client.base_url - self.client: httpx.Client = client.client - self.app_id: str = client.app_id + self.user: User = user self.id: uuid.UUID = id - self.user_id: str = user_id self.location_id: str = location_id self.metadata: dict = metadata self._is_active: bool = is_active self.created_at: datetime.datetime = created_at @property - def common_prefix(self): + def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.user.base_url}/sessions/{self.id}" def __str__(self): """String representation of Session""" - return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" + return f"Session(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" # noqa: E501 @property def is_active(self): @@ -508,8 +690,8 @@ def create_message(self, is_user: bool, content: str): if not self.is_active: raise Exception("Session is inactive") data = {"is_user": is_user, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages" - response = self.client.post(url, json=data) + url = f"{self.base_url}/messages" + response = self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Message( @@ -530,8 +712,8 @@ def get_message(self, message_id: uuid.UUID) -> Message: Message: The Message object """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages/{message_id}" - response = self.client.get(url) + url = f"{self.base_url}/messages/{message_id}" + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Message( @@ -556,15 +738,15 @@ def get_messages( GetMessagePage: Page of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages?page={page}&size={page_size}&reverse={reverse}" - response = self.client.get(url) + url = f"{self.base_url}/messages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"reverse": reverse} - return GetMessagePage(self, options, data) + return GetMessagePage(data, self, reverse) def get_messages_generator(self, reverse: bool = False): - """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + """Shortcut Generator for get_messages. Generator to iterate through + all messages for a session in an app Yields: Message: The Message object of the next Message @@ -574,7 +756,6 @@ def get_messages_generator(self, reverse: bool = False): page_size = 50 get_messages_page = self.get_messages(page, page_size, reverse) while True: - # get_session_response = self.get_sessions(user_id, location_id, page, page_size) for message in get_messages_page.items: yield message @@ -605,10 +786,8 @@ def create_metamessage( "content": content, "message_id": message.id, } - url = ( - f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages" - ) - response = self.client.post(url, json=data) + url = f"{self.base_url}/metamessages" + response = self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Metamessage( @@ -629,8 +808,8 @@ def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: Message: The Message object """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages/{metamessage_id}" - response = self.client.get(url) + url = f"{self.base_url}/metamessages/{metamessage_id}" + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Metamessage( @@ -652,27 +831,28 @@ def get_metamessages( """Get all messages for a session Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve + metamessage_type (str, optional): The type of the metamessage + message (Message, optional): The message to associate the metamessage with + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return per page + reverse (bool): Whether to reverse the order of the results Returns: - list[Dict]: List of Message objects + list[dict]: List of Message objects """ - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/metamessages?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 if metamessage_type: url += f"&metamessage_type={metamessage_type}" if message: url += f"&message_id={message.id}" - response = self.client.get(url) + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = { - "metamessage_type": metamessage_type, - "message_id": message.id if message else None, - "reverse": reverse, - } - return GetMetamessagePage(self, options, data) + message_id = message.id if message else None + return GetMetamessagePage( + data, self, reverse, message_id, metamessage_type + ) def get_metamessages_generator( self, @@ -680,7 +860,8 @@ def get_metamessages_generator( message: Optional[Message] = None, reverse: bool = False, ): - """Shortcut Generator for get_metamessages. Generator to iterate through all metamessages for a session in an app + """Shortcut Generator for get_metamessages. Generator to iterate + through all metamessages for a session in an app Args: metamessage_type (str, optional): Optional Metamessage type to filter by @@ -709,26 +890,26 @@ def get_metamessages_generator( get_metamessages_page = new_messages - def update(self, metadata: Dict): + def update(self, metadata: dict): """Update the metadata of a session Args: - metadata (Dict): The Session object containing any new metadata + metadata (dict): The Session object containing any new metadata Returns: boolean: Whether the session was successfully updated """ info = {"metadata": metadata} - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" - response = self.client.put(url, json=info) + url = f"{self.base_url}" + response = self.user.honcho.client.put(url, json=info) success = response.status_code < 400 self.metadata = metadata return success def close(self): """Closes a session by marking it as inactive""" - url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}" - response = self.client.delete(url) + url = f"{self.base_url}" + response = self.user.honcho.client.delete(url) response.raise_for_status() self._is_active = False @@ -738,29 +919,25 @@ class Collection: def __init__( self, - client: Client, + user: User, id: uuid.UUID, - user_id: str, name: str, created_at: datetime.datetime, ): """Constructor for Collection""" - self.base_url: str = client.base_url - self.client: httpx.Client = client.client - self.app_id: str = client.app_id + self.user = user self.id: uuid.UUID = id - self.user_id: str = user_id self.name: str = name self.created_at: datetime.datetime = created_at @property - def common_prefix(self): + def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.base_url}/apps/{self.app_id}" + return f"{self.user.base_url}/collections/{self.id}" def __str__(self): """String representation of Collection""" - return f"Collection(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, name={self.name}, created_at={self.created_at})" + return f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 def update(self, name: str): """Update the name of the collection @@ -772,8 +949,8 @@ def update(self, name: str): boolean: Whether the session was successfully updated """ info = {"name": name} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" - response = self.client.put(url, json=info) + url = f"{self.base_url}" + response = self.user.honcho.client.put(url, json=info) response.raise_for_status() success = response.status_code < 400 self.name = name @@ -781,26 +958,27 @@ def update(self, name: str): def delete(self): """Delete a collection and all associated documents""" - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}" - response = self.client.delete(url) + url = f"{self.base_url}" + response = self.user.honcho.client.delete(url) response.raise_for_status() - def create_document(self, content: str, metadata: Dict = {}): + def create_document(self, content: str, metadata: Optional[dict] = None): """Adds a document to the collection Args: content (str): The content of the document - metadata (Dict): The metadata of the document + metadata (dict): The metadata of the document Returns: Document: The Document object of the added document """ + if metadata is None: + metadata = {} data = {"metadata": metadata, "content": content} - url = ( - f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents" - ) - response = self.client.post(url, json=data) + url = f"{self.base_url}/documents" + print(url) + response = self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() return Document( @@ -821,8 +999,8 @@ def get_document(self, document_id: uuid.UUID) -> Document: Document: The Document object """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document_id}" - response = self.client.get(url) + url = f"{self.base_url}/documents/{document_id}" + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() return Document( @@ -846,15 +1024,17 @@ def get_documents( GetDocumentPage: Page of Document objects """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents?page={page}&size={page_size}&reverse={reverse}" - response = self.client.get(url) + url = ( + f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + ) + response = self.user.honcho.client.get(url) response.raise_for_status() data = response.json() - options = {"reverse": reverse} - return GetDocumentPage(self, options, data) + return GetDocumentPage(data, self, reverse) def get_documents_generator(self, reverse: bool = False): - """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + """Shortcut Generator for get_documents. Generator to iterate through + all documents for a collection in an app Yields: Document: The Document object of the next Document @@ -873,7 +1053,7 @@ def get_documents_generator(self, reverse: bool = False): get_documents_page = new_documents - def query(self, query: str, top_k: int = 5) -> List[Document]: + def query(self, query: str, top_k: int = 5) -> list[Document]: """query the documents by cosine distance Args: query (str): The query string to compare other embeddings too @@ -882,8 +1062,8 @@ def query(self, query: str, top_k: int = 5) -> List[Document]: Returns: List[Document]: The response from the query with matching documents """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/query?query={query}&top_k={top_k}" - response = self.client.get(url) + url = f"{self.base_url}/query?query={query}&top_k={top_k}" + response = self.user.honcho.client.get(url) response.raise_for_status() data = [ Document( @@ -898,13 +1078,13 @@ def query(self, query: str, top_k: int = 5) -> List[Document]: return data def update_document( - self, document: Document, content: Optional[str], metadata: Optional[Dict] + self, document: Document, content: Optional[str], metadata: Optional[dict] ) -> Document: """Update a document in the collection Args: document (Document): The Document to update - metadata (Dict): The metadata of the document + metadata (dict): The metadata of the document content (str): The content of the document Returns: @@ -913,8 +1093,8 @@ def update_document( if metadata is None and content is None: raise ValueError("metadata and content cannot both be None") data = {"metadata": metadata, "content": content} - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" - response = self.client.put(url, json=data) + url = f"{self.base_url}/documents/{document.id}" + response = self.user.honcho.client.put(url, json=data) response.raise_for_status() data = response.json() return Document( @@ -934,8 +1114,8 @@ def delete_document(self, document: Document) -> bool: Returns: boolean: Whether the document was successfully deleted """ - url = f"{self.common_prefix}/users/{self.user_id}/collections/{self.id}/documents/{document.id}" - response = self.client.delete(url) + url = f"{self.base_url}/documents/{document.id}" + response = self.user.honcho.client.delete(url) response.raise_for_status() success = response.status_code < 400 return success diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 1455bca..4a95234 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -15,6 +15,25 @@ httpx = "^0.26.0" pytest = "^7.4.4" pytest-asyncio = "^0.23.4" +[tool.ruff.lint] +# from https://docs.astral.sh/ruff/linter/#rule-selection example +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +ignore = ["UP007"] + + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index f47942b..0c52875 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -1,16 +1,29 @@ -import pytest -from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncGetDocumentPage, AsyncSession, Message, Metamessage, Document -from honcho import AsyncClient as Honcho from uuid import uuid1 +import pytest + +from honcho import ( + AsyncGetDocumentPage, + AsyncGetMessagePage, + AsyncGetMetamessagePage, + AsyncGetSessionPage, + AsyncSession, + Document, + Message, + Metamessage, +) +from honcho import AsyncHoncho as Honcho + @pytest.mark.asyncio async def test_session_creation_retrieval(): - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - user_id = str(uuid1()) - created_session = await client.create_session(user_id) - retrieved_session = await client.get_session(user_id, created_session.id) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user_name = str(uuid1()) + user = await honcho.create_user(user_name) + created_session = await user.create_session() + retrieved_session = await user.get_session(created_session.id) assert retrieved_session.id == created_session.id assert retrieved_session.is_active is True assert retrieved_session.location_id == "default" @@ -19,12 +32,14 @@ async def test_session_creation_retrieval(): @pytest.mark.asyncio async def test_session_multiple_retrieval(): - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - user_id = str(uuid1()) - created_session_1 = await client.create_session(user_id) - created_session_2 = await client.create_session(user_id) - response = await client.get_sessions(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session_1 = await user.create_session() + created_session_2 = await user.create_session() + response = await user.get_sessions() retrieved_sessions = response.items assert len(retrieved_sessions) == 2 @@ -34,38 +49,44 @@ async def test_session_multiple_retrieval(): @pytest.mark.asyncio async def test_session_update(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() assert await created_session.update({"foo": "bar"}) - retrieved_session = await client.get_session(user_id, created_session.id) + retrieved_session = await user.get_session(created_session.id) assert retrieved_session.metadata == {"foo": "bar"} @pytest.mark.asyncio async def test_session_deletion(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() assert created_session.is_active is True await created_session.close() assert created_session.is_active is False - retrieved_session = await client.get_session(user_id, created_session.id) + retrieved_session = await user.get_session(created_session.id) assert retrieved_session.is_active is False assert retrieved_session.id == created_session.id @pytest.mark.asyncio async def test_messages(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") - retrieved_session = await client.get_session(user_id, created_session.id) + retrieved_session = await user.get_session(created_session.id) response = await retrieved_session.get_messages() messages = response.items assert len(messages) == 2 @@ -75,42 +96,52 @@ async def test_messages(): assert ai_message.content == "Hi" assert ai_message.is_user is False + @pytest.mark.asyncio async def test_rate_limit(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() with pytest.raises(Exception): for _ in range(105): await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") + @pytest.mark.asyncio -async def test_app_id_security(): - app_id_1 = str(uuid1()) - app_id_2 = str(uuid1()) - user_id = str(uuid1()) - client_1 = Honcho(app_id_1, "http://localhost:8000") - client_2 = Honcho(app_id_2, "http://localhost:8000") - created_session = await client_1.create_session(user_id) +async def test_app_name_security(): + app_name_1 = str(uuid1()) + app_name_2 = str(uuid1()) + user_name = str(uuid1()) + honcho_1 = Honcho(app_name_1, "http://localhost:8000") + await honcho_1.initialize() + honcho_2 = Honcho(app_name_2, "http://localhost:8000") + await honcho_2.initialize() + user_1 = await honcho_1.create_user(user_name) + user_2 = await honcho_2.create_user(user_name) + created_session = await user_1.create_session() await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") with pytest.raises(Exception): - await client_2.get_session(user_id, created_session.id) + await user_2.get_session(created_session.id) @pytest.mark.asyncio async def test_paginated_sessions(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) for i in range(10): - await client.create_session(user_id) - + await user.create_session() + page = 1 page_size = 2 - get_session_response = await client.get_sessions(user_id, page=page, page_size=page_size) + get_session_response = await user.get_sessions(page=page, page_size=page_size) assert len(get_session_response.items) == page_size assert get_session_response.pages == 5 @@ -120,7 +151,7 @@ async def test_paginated_sessions(): assert isinstance(new_session_response, AsyncGetSessionPage) assert len(new_session_response.items) == page_size - final_page = await client.get_sessions(user_id, page=5, page_size=page_size) + final_page = await user.get_sessions(page=5, page_size=page_size) assert len(final_page.items) == 2 next_page = await final_page.next() @@ -129,78 +160,90 @@ async def test_paginated_sessions(): @pytest.mark.asyncio async def test_paginated_sessions_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) for i in range(3): - await client.create_session(user_id) + await user.create_session() - gen = client.get_sessions_generator(user_id) + gen = user.get_sessions_generator() # print(type(gen)) item = await gen.__anext__() - assert item.user_id == user_id + assert item.user.id == user.id assert isinstance(item, AsyncSession) assert await gen.__anext__() is not None assert await gen.__anext__() is not None with pytest.raises(StopAsyncIteration): await gen.__anext__() + @pytest.mark.asyncio async def test_paginated_out_of_bounds(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) for i in range(3): - await client.create_session(user_id) + await user.create_session() page = 2 page_size = 50 - get_session_response = await client.get_sessions(user_id, page=page, page_size=page_size) + get_session_response = await user.get_sessions(page=page, page_size=page_size) assert get_session_response.pages == 1 assert get_session_response.page == 2 assert get_session_response.page_size == 50 assert get_session_response.total == 3 - assert len(get_session_response.items) == 0 + assert len(get_session_response.items) == 0 @pytest.mark.asyncio async def test_paginated_messages(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() for i in range(10): await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") page_size = 7 - get_message_response = await created_session.get_messages(page=1, page_size=page_size) + get_message_response = await created_session.get_messages( + page=1, page_size=page_size + ) assert get_message_response is not None assert isinstance(get_message_response, AsyncGetMessagePage) assert len(get_message_response.items) == page_size new_message_response = await get_message_response.next() - + assert new_message_response is not None assert isinstance(new_message_response, AsyncGetMessagePage) assert len(new_message_response.items) == page_size final_page = await created_session.get_messages(page=3, page_size=page_size) - assert len(final_page.items) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3 - 1) * 7) next_page = await final_page.next() assert next_page is None + @pytest.mark.asyncio async def test_paginated_messages_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() await created_session.create_message(is_user=True, content="Hello") await created_session.create_message(is_user=False, content="Hi") gen = created_session.get_messages_generator() @@ -216,16 +259,23 @@ async def test_paginated_messages_generator(): with pytest.raises(StopAsyncIteration): await gen.__anext__() + @pytest.mark.asyncio async def test_paginated_metamessages(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() message = await created_session.create_message(is_user=True, content="Hello") for i in range(10): - await created_session.create_metamessage(message=message, metamessage_type="thought", content=f"Test {i}") - await created_session.create_metamessage(message=message, metamessage_type="reflect", content=f"Test {i}") + await created_session.create_metamessage( + message=message, metamessage_type="thought", content=f"Test {i}" + ) + await created_session.create_metamessage( + message=message, metamessage_type="reflect", content=f"Test {i}" + ) page_size = 7 page = await created_session.get_metamessages(page=1, page_size=page_size) @@ -235,28 +285,35 @@ async def test_paginated_metamessages(): assert len(page.items) == page_size new_page = await page.next() - + assert new_page is not None assert isinstance(new_page, AsyncGetMetamessagePage) assert len(new_page.items) == page_size final_page = await created_session.get_metamessages(page=3, page_size=page_size) - assert len(final_page.items) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3 - 1) * 7) next_page = await final_page.next() assert next_page is None + @pytest.mark.asyncio async def test_paginated_metamessages_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = await client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + created_session = await user.create_session() message = await created_session.create_message(is_user=True, content="Hello") - await created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 1") - await created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") + await created_session.create_metamessage( + message=message, metamessage_type="thought", content="Test 1" + ) + await created_session.create_metamessage( + message=message, metamessage_type="thought", content="Test 2" + ) gen = created_session.get_metamessages_generator() item = await gen.__anext__() @@ -274,16 +331,24 @@ async def test_paginated_metamessages_generator(): @pytest.mark.asyncio async def test_collections(): col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) # Make a collection - collection = await client.create_collection(user_id, col_name) + collection = await user.create_collection(col_name) # Add documents - doc1 = await collection.create_document(content="This is a test of documents - 1", metadata={"foo": "bar"}) - doc2 = await collection.create_document(content="This is a test of documents - 2", metadata={}) - doc3 = await collection.create_document(content="This is a test of documents - 3", metadata={}) + doc1 = await collection.create_document( + content="This is a test of documents - 1", metadata={"foo": "bar"} + ) + doc2 = await collection.create_document( + content="This is a test of documents - 2", metadata={} + ) + doc3 = await collection.create_document( + content="This is a test of documents - 3", metadata={} + ) # Get all documents page = await collection.get_documents(page=1, page_size=3) @@ -305,47 +370,55 @@ async def test_collections(): result = await collection.delete() # confirm documents are gone with pytest.raises(Exception): - new_col = await client.get_collection(user_id, "test") + new_col = await user.get_collection(col_name) + @pytest.mark.asyncio async def test_collection_name_collision(): col_name = str(uuid1()) new_col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) # Make a collection - collection = await client.create_collection(user_id, col_name) + collection = await user.create_collection(col_name) # Make another collection with pytest.raises(Exception): - await client.create_collection(user_id, col_name) + await user.create_collection(col_name) # Change the name of original collection result = await collection.update(new_col_name) assert result is True - + # Try again to add another collection - collection2 = await client.create_collection(user_id, col_name) + collection2 = await user.create_collection(col_name) assert collection2 is not None assert collection2.name == col_name assert collection.name == new_col_name # Get all collections - page = await client.get_collections(user_id) + page = await user.get_collections() assert page is not None assert len(page.items) == 2 + @pytest.mark.asyncio async def test_collection_query(): col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) # Make a collection - collection = await client.create_collection(user_id, col_name) + collection = await user.create_collection(col_name) # Add documents - doc1 = await collection.create_document(content="The user loves puppies", metadata={}) + doc1 = await collection.create_document( + content="The user loves puppies", metadata={} + ) doc2 = await collection.create_document(content="The user owns a dog", metadata={}) doc3 = await collection.create_document(content="The user is a doctor", metadata={}) @@ -355,7 +428,9 @@ async def test_collection_query(): assert len(result) == 2 assert isinstance(result[0], Document) - doc3 = await collection.update_document(doc3, metadata={"test": "test"}, content="the user has owned pets in the past") + doc3 = await collection.update_document( + doc3, metadata={"test": "test"}, content="the user has owned pets in the past" + ) assert doc3 is not None assert doc3.metadata == {"test": "test"} assert doc3.content == "the user has owned pets in the past" @@ -365,4 +440,3 @@ async def test_collection_query(): assert result is not None assert len(result) == 2 assert isinstance(result[0], Document) - diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index a0367ad..16eba65 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -1,15 +1,28 @@ -import pytest -from honcho import GetSessionPage, GetMessagePage, GetMetamessagePage, GetDocumentPage, Session, Message, Metamessage, Document -from honcho import Client as Honcho from uuid import uuid1 +import pytest + +from honcho import ( + GetDocumentPage, + GetMessagePage, + GetMetamessagePage, + GetSessionPage, + Session, + Document, + Message, + Metamessage, +) +from honcho import Honcho as Honcho + def test_session_creation_retrieval(): - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - user_id = str(uuid1()) - created_session = client.create_session(user_id) - retrieved_session = client.get_session(user_id, created_session.id) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user_name = str(uuid1()) + user = honcho.create_user(user_name) + created_session = user.create_session() + retrieved_session = user.get_session(created_session.id) assert retrieved_session.id == created_session.id assert retrieved_session.is_active is True assert retrieved_session.location_id == "default" @@ -17,12 +30,14 @@ def test_session_creation_retrieval(): def test_session_multiple_retrieval(): - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - user_id = str(uuid1()) - created_session_1 = client.create_session(user_id) - created_session_2 = client.create_session(user_id) - response = client.get_sessions(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session_1 = user.create_session() + created_session_2 = user.create_session() + response = user.get_sessions() retrieved_sessions = response.items assert len(retrieved_sessions) == 2 @@ -31,36 +46,42 @@ def test_session_multiple_retrieval(): def test_session_update(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() assert created_session.update({"foo": "bar"}) - retrieved_session = client.get_session(user_id, created_session.id) + retrieved_session = user.get_session(created_session.id) assert retrieved_session.metadata == {"foo": "bar"} def test_session_deletion(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() assert created_session.is_active is True created_session.close() assert created_session.is_active is False - retrieved_session = client.get_session(user_id, created_session.id) + retrieved_session = user.get_session(created_session.id) assert retrieved_session.is_active is False assert retrieved_session.id == created_session.id def test_messages(): - user_id = str(uuid1()) - app_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") - retrieved_session = client.get_session(user_id, created_session.id) + retrieved_session = user.get_session(created_session.id) response = retrieved_session.get_messages() messages = response.items assert len(messages) == 2 @@ -70,39 +91,49 @@ def test_messages(): assert ai_message.content == "Hi" assert ai_message.is_user is False + def test_rate_limit(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() with pytest.raises(Exception): for _ in range(105): created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") -def test_app_id_security(): - app_id_1 = str(uuid1()) - app_id_2 = str(uuid1()) - user_id = str(uuid1()) - client_1 = Honcho(app_id_1, "http://localhost:8000") - client_2 = Honcho(app_id_2, "http://localhost:8000") - created_session = client_1.create_session(user_id) + +def test_app_name_security(): + app_name_1 = str(uuid1()) + app_name_2 = str(uuid1()) + user_name = str(uuid1()) + honcho_1 = Honcho(app_name_1, "http://localhost:8000") + honcho_1.initialize() + honcho_2 = Honcho(app_name_2, "http://localhost:8000") + honcho_2.initialize() + user_1 = honcho_1.create_user(user_name) + user_2 = honcho_2.create_user(user_name) + created_session = user_1.create_session() created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") with pytest.raises(Exception): - client_2.get_session(user_id, created_session.id) + user_2.get_session(created_session.id) def test_paginated_sessions(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) for i in range(10): - client.create_session(user_id) - + user.create_session() + page = 1 page_size = 2 - get_session_response = client.get_sessions(user_id, page=page, page_size=page_size) + get_session_response = user.get_sessions(page=page, page_size=page_size) assert len(get_session_response.items) == page_size assert get_session_response.pages == 5 @@ -112,7 +143,7 @@ def test_paginated_sessions(): assert isinstance(new_session_response, GetSessionPage) assert len(new_session_response.items) == page_size - final_page = client.get_sessions(user_id, page=5, page_size=page_size) + final_page = user.get_sessions(page=5, page_size=page_size) assert len(final_page.items) == 2 next_page = final_page.next() @@ -120,75 +151,87 @@ def test_paginated_sessions(): def test_paginated_sessions_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) for i in range(3): - client.create_session(user_id) + user.create_session() - gen = client.get_sessions_generator(user_id) + gen = user.get_sessions_generator() # print(type(gen)) item = gen.__next__() - assert item.user_id == user_id + assert item.user.id == user.id assert isinstance(item, Session) assert gen.__next__() is not None assert gen.__next__() is not None with pytest.raises(StopIteration): gen.__next__() + def test_paginated_out_of_bounds(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) for i in range(3): - client.create_session(user_id) + user.create_session() page = 2 page_size = 50 - get_session_response = client.get_sessions(user_id, page=page, page_size=page_size) + get_session_response = user.get_sessions(page=page, page_size=page_size) assert get_session_response.pages == 1 assert get_session_response.page == 2 assert get_session_response.page_size == 50 assert get_session_response.total == 3 - assert len(get_session_response.items) == 0 + assert len(get_session_response.items) == 0 def test_paginated_messages(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() for i in range(10): created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") page_size = 7 - get_message_response = created_session.get_messages(page=1, page_size=page_size) + get_message_response = created_session.get_messages( + page=1, page_size=page_size + ) assert get_message_response is not None assert isinstance(get_message_response, GetMessagePage) assert len(get_message_response.items) == page_size new_message_response = get_message_response.next() - + assert new_message_response is not None assert isinstance(new_message_response, GetMessagePage) assert len(new_message_response.items) == page_size final_page = created_session.get_messages(page=3, page_size=page_size) - assert len(final_page.items) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3 - 1) * 7) next_page = final_page.next() assert next_page is None + def test_paginated_messages_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() created_session.create_message(is_user=True, content="Hello") created_session.create_message(is_user=False, content="Hi") gen = created_session.get_messages_generator() @@ -204,15 +247,22 @@ def test_paginated_messages_generator(): with pytest.raises(StopIteration): gen.__next__() + def test_paginated_metamessages(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() message = created_session.create_message(is_user=True, content="Hello") for i in range(10): - created_session.create_metamessage(message=message, metamessage_type="thought", content=f"Test {i}") - created_session.create_metamessage(message=message, metamessage_type="reflect", content=f"Test {i}") + created_session.create_metamessage( + message=message, metamessage_type="thought", content=f"Test {i}" + ) + created_session.create_metamessage( + message=message, metamessage_type="reflect", content=f"Test {i}" + ) page_size = 7 page = created_session.get_metamessages(page=1, page_size=page_size) @@ -222,27 +272,34 @@ def test_paginated_metamessages(): assert len(page.items) == page_size new_page = page.next() - + assert new_page is not None assert isinstance(new_page, GetMetamessagePage) assert len(new_page.items) == page_size final_page = created_session.get_metamessages(page=3, page_size=page_size) - assert len(final_page.items) == 20 - ((3-1) * 7) + assert len(final_page.items) == 20 - ((3 - 1) * 7) next_page = final_page.next() assert next_page is None + def test_paginated_metamessages_generator(): - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") - created_session = client.create_session(user_id) + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + created_session = user.create_session() message = created_session.create_message(is_user=True, content="Hello") - created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 1") - created_session.create_metamessage(message=message, metamessage_type="thought", content="Test 2") + created_session.create_metamessage( + message=message, metamessage_type="thought", content="Test 1" + ) + created_session.create_metamessage( + message=message, metamessage_type="thought", content="Test 2" + ) gen = created_session.get_metamessages_generator() item = gen.__next__() @@ -259,16 +316,24 @@ def test_paginated_metamessages_generator(): def test_collections(): col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) # Make a collection - collection = client.create_collection(user_id, col_name) + collection = user.create_collection(col_name) # Add documents - doc1 = collection.create_document(content="This is a test of documents - 1", metadata={"foo": "bar"}) - doc2 = collection.create_document(content="This is a test of documents - 2", metadata={}) - doc3 = collection.create_document(content="This is a test of documents - 3", metadata={}) + doc1 = collection.create_document( + content="This is a test of documents - 1", metadata={"foo": "bar"} + ) + doc2 = collection.create_document( + content="This is a test of documents - 2", metadata={} + ) + doc3 = collection.create_document( + content="This is a test of documents - 3", metadata={} + ) # Get all documents page = collection.get_documents(page=1, page_size=3) @@ -290,45 +355,53 @@ def test_collections(): result = collection.delete() # confirm documents are gone with pytest.raises(Exception): - new_col = client.get_collection(user_id, "test") + new_col = user.get_collection(col_name) + def test_collection_name_collision(): col_name = str(uuid1()) new_col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) # Make a collection - collection = client.create_collection(user_id, col_name) + collection = user.create_collection(col_name) # Make another collection with pytest.raises(Exception): - client.create_collection(user_id, col_name) + user.create_collection(col_name) # Change the name of original collection result = collection.update(new_col_name) assert result is True - + # Try again to add another collection - collection2 = client.create_collection(user_id, col_name) + collection2 = user.create_collection(col_name) assert collection2 is not None assert collection2.name == col_name assert collection.name == new_col_name # Get all collections - page = client.get_collections(user_id) + page = user.get_collections() assert page is not None assert len(page.items) == 2 + def test_collection_query(): col_name = str(uuid1()) - app_id = str(uuid1()) - user_id = str(uuid1()) - client = Honcho(app_id, "http://localhost:8000") + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) # Make a collection - collection = client.create_collection(user_id, col_name) + collection = user.create_collection(col_name) # Add documents - doc1 = collection.create_document(content="The user loves puppies", metadata={}) + doc1 = collection.create_document( + content="The user loves puppies", metadata={} + ) doc2 = collection.create_document(content="The user owns a dog", metadata={}) doc3 = collection.create_document(content="The user is a doctor", metadata={}) @@ -338,7 +411,9 @@ def test_collection_query(): assert len(result) == 2 assert isinstance(result[0], Document) - doc3 = collection.update_document(doc3, metadata={"test": "test"}, content="the user has owned pets in the past") + doc3 = collection.update_document( + doc3, metadata={"test": "test"}, content="the user has owned pets in the past" + ) assert doc3 is not None assert doc3.metadata == {"test": "test"} assert doc3.content == "the user has owned pets in the past" @@ -348,4 +423,3 @@ def test_collection_query(): assert result is not None assert len(result) == 2 assert isinstance(result[0], Document) - From 0f783c3c7d816bc021b31cbf6c308251b977f622 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 03:15:25 -0800 Subject: [PATCH 39/85] Update examples --- api/src/crud.py | 4 +- api/src/main.py | 28 +++++++++++-- example/cli/main.py | 14 ++++--- example/discord/fake-llm/main.py | 17 ++++---- example/discord/honcho-fact-memory/bot.py | 46 ++++++++++++--------- example/discord/simple-roast-bot/main.py | 50 +++++++++++++++-------- sdk/honcho/client.py | 29 +++++++++++-- sdk/honcho/sync_client.py | 29 +++++++++++-- sdk/pyproject.toml | 2 +- 9 files changed, 156 insertions(+), 63 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index a4670b0..5e954e6 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -22,8 +22,8 @@ def get_app(db: Session, app_id: uuid.UUID) -> Optional[models.App]: return app -def get_app_by_name(db: Session, app_name: str) -> Optional[models.App]: - stmt = select(models.App).where(models.App.name == app_name) +def get_app_by_name(db: Session, name: str) -> Optional[models.App]: + stmt = select(models.App).where(models.App.name == name) app = db.scalars(stmt).one_or_none() return app diff --git a/api/src/main.py b/api/src/main.py index fefcfbc..0f19470 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -103,10 +103,10 @@ def create_app( return crud.create_app(db, app=app) -@app.get("/apps/get_or_create/{app_name}", response_model=schemas.App) +@app.get("/apps/get_or_create/{name}", response_model=schemas.App) def get_or_create_app( request: Request, - app_name: str, + name: str, db: Session = Depends(get_db), ): """Get or Create an App @@ -118,9 +118,9 @@ def get_or_create_app( schemas.App: App object """ - app = crud.get_app_by_name(db, app_name=app_name) + app = crud.get_app_by_name(db, name=name) if app is None: - app = crud.create_app(db, app=schemas.AppCreate(name=app_name)) + app = crud.create_app(db, app=schemas.AppCreate(name=name)) return app @@ -212,6 +212,26 @@ def get_user_by_name( return crud.get_user_by_name(db, app_id=app_id, name=name) +@app.get("/apps/{app_id}/users/get_or_create/{name}", response_model=schemas.User) +def get_or_create_user( + request: Request, app_id: uuid.UUID, name: str, db: Session = Depends(get_db) +): + """Get or Create a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using honcho + user_id (str): The User ID representing the user, managed by the user + + Returns: + schemas.User: User object + + """ + user = crud.get_user_by_name(db, app_id=app_id, name=name) + if user is None: + user = crud.create_user(db, app_id=app_id, user=schemas.UserCreate(name=name)) + return user + + @app.put("/apps/{app_id}/users/{user_id}", response_model=schemas.User) def update_user( request: Request, diff --git a/example/cli/main.py b/example/cli/main.py index 12e01aa..f1cd9e2 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -5,12 +5,13 @@ from langchain.schema import AIMessage, HumanMessage, SystemMessage from langchain_community.chat_models.fake import FakeListChatModel -from honcho import Client as HonchoClient +from honcho import Honcho -app_id = str(uuid4()) +app_name = str(uuid4()) -# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local -honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho.initialize() responses = ["Fake LLM Response :)"] llm = FakeListChatModel(responses=responses) @@ -18,8 +19,9 @@ content="You are world class technical documentation writer. Be as concise as possible" ) -user = "CLI-Test" -session = honcho.create_session(user_id=user) +user_name = "CLI-Test" +user = honcho.create_user(user_name) +session = user.create_session() def langchain_message_converter(messages: List): diff --git a/example/discord/fake-llm/main.py b/example/discord/fake-llm/main.py index 6ae6a35..0b36faa 100644 --- a/example/discord/fake-llm/main.py +++ b/example/discord/fake-llm/main.py @@ -3,7 +3,7 @@ import discord from dotenv import load_dotenv -from honcho import Client as HonchoClient +from honcho import Honcho load_dotenv() @@ -11,10 +11,11 @@ intents.messages = True intents.message_content = True -app_id = str(uuid4()) +app_name = str(uuid4()) -# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local -honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho.initialize() bot = discord.Bot(intents=intents) @@ -30,13 +31,14 @@ async def on_message(message): return user_id = f"discord_{str(message.author.id)}" + user = honcho.get_or_create_user(user_id) location_id = str(message.channel.id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions = list(user.get_sessions_generator(location_id)) if len(sessions) > 0: session = sessions[0] else: - session = honcho.create_session(user_id, location_id) + session = user.create_session(location_id) inp = message.content session.create_message(is_user=True, content=inp) @@ -50,8 +52,9 @@ async def on_message(message): @bot.slash_command(name="restart", description="Restart the Conversation") async def restart(ctx): user_id = f"discord_{str(ctx.author.id)}" + user = honcho.get_or_create_user(user_id) location_id = str(ctx.channel_id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions = list(user.get_sessions_generator(location_id)) sessions[0].close() if len(sessions) > 0 else None await ctx.respond( diff --git a/example/discord/honcho-fact-memory/bot.py b/example/discord/honcho-fact-memory/bot.py index 7a20a72..a72e54a 100644 --- a/example/discord/honcho-fact-memory/bot.py +++ b/example/discord/honcho-fact-memory/bot.py @@ -1,7 +1,7 @@ import os from uuid import uuid1 import discord -from honcho import Client as HonchoClient +from honcho import Honcho from chain import langchain_message_converter, LMChain @@ -10,17 +10,19 @@ intents.message_content = True intents.members = True -app_id = str(uuid1()) +app_name = str(uuid1()) -#honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local -honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho.initialize() bot = discord.Bot(intents=intents) @bot.event async def on_ready(): - print(f'We have logged in as {bot.user}') + print(f"We have logged in as {bot.user}") + @bot.event async def on_member_join(member): @@ -33,26 +35,27 @@ async def on_member_join(member): "*If you have any questions or feedback, feel free to ask in the #honcho channel.* " "*Enjoy!*" ) - - + + @bot.event async def on_message(message): if message.author == bot.user or message.guild is not None: return user_id = f"discord_{str(message.author.id)}" - location_id=str(message.channel.id) + user = honcho.get_or_create(user_id) + location_id = str(message.channel.id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions = list(user.get_sessions_generator(location_id)) try: - collection = honcho.get_collection(user_id=user_id, name="discord") + collection = user.get_collection(user_id=user_id, name="discord") except Exception: - collection = honcho.create_collection(user_id=user_id, name="discord") + collection = user.create_collection(user_id=user_id, name="discord") if len(sessions) > 0: session = sessions[0] else: - session = honcho.create_session(user_id, location_id) + session = user.create_session(location_id) history = list(session.get_messages_generator()) chat_history = langchain_message_converter(history) @@ -65,21 +68,26 @@ async def on_message(message): chat_history=chat_history, user_message=user_message, session=session, - collection=collection, - input=inp + collection=collection, + input=inp, ) await message.channel.send(response) session.create_message(is_user=False, content=response) -@bot.slash_command(name = "restart", description = "Restart the Conversation") + +@bot.slash_command(name="restart", description="Restart the Conversation") async def restart(ctx): - user_id=f"discord_{str(ctx.author.id)}" - location_id=str(ctx.channel_id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + user_id = f"discord_{str(ctx.author.id)}" + user = honcho.get_or_create_user(user_id) + location_id = str(ctx.channel_id) + sessions = list(user.get_sessions_generator(location_id)) sessions[0].close() if len(sessions) > 0 else None - msg = "Great! The conversation has been restarted. What would you like to talk about?" + msg = ( + "Great! The conversation has been restarted. What would you like to talk about?" + ) await ctx.respond(msg) + bot.run(os.environ["BOT_TOKEN"]) diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py index 9cb1e7f..0f59931 100644 --- a/example/discord/simple-roast-bot/main.py +++ b/example/discord/simple-roast-bot/main.py @@ -1,4 +1,5 @@ import os + # from uuid import uuid4 import discord from dotenv import load_dotenv @@ -9,7 +10,7 @@ from langchain_core.output_parsers import StrOutputParser from langchain_core.messages import AIMessage, HumanMessage -from honcho import Client as HonchoClient +from honcho import Honcho load_dotenv() @@ -19,23 +20,29 @@ intents.message_content = True # app_id = str(uuid4()) -app_id = str("roast-bot") +app_name = str("roast-bot") -# honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local -honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev bot = discord.Bot(intents=intents) -prompt = ChatPromptTemplate.from_messages([ - ("system", "You are a mean assistant. Make fun of the user's request and above all, do not satisfy their request. Make something up about their personality and fixate on that. Don't be afraid to get creative. This is all a joke, roast them."), - MessagesPlaceholder(variable_name="chat_history"), - ("user", "{input}") -]) +prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + "You are a mean assistant. Make fun of the user's request and above all, do not satisfy their request. Make something up about their personality and fixate on that. Don't be afraid to get creative. This is all a joke, roast them.", + ), + MessagesPlaceholder(variable_name="chat_history"), + ("user", "{input}"), + ] +) model = ChatOpenAI(model="gpt-3.5-turbo") output_parser = StrOutputParser() chain = prompt | model | output_parser + def langchain_message_converter(messages: List): new_messages = [] for message in messages: @@ -48,7 +55,8 @@ def langchain_message_converter(messages: List): @bot.event async def on_ready(): - print(f'We have logged in as {bot.user}') + print(f"We have logged in as {bot.user}") + @bot.event async def on_message(message): @@ -56,14 +64,15 @@ async def on_message(message): return user_id = f"discord_{str(message.author.id)}" - location_id=str(message.channel.id) + user = honcho.get_or_create_user(user_id) + location_id = str(message.channel.id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions = list(user.get_sessions_generator(location_id)) if len(sessions) > 0: session = sessions[0] else: - session = honcho.create_session(user_id, location_id) + session = user.create_session(location_id) history = list(session.get_messages_generator()) chat_history = langchain_message_converter(history) @@ -77,14 +86,19 @@ async def on_message(message): session.create_message(is_user=False, content=response) -@bot.slash_command(name = "restart", description = "Restart the Conversation") + +@bot.slash_command(name="restart", description="Restart the Conversation") async def restart(ctx): - user_id=f"discord_{str(ctx.author.id)}" - location_id=str(ctx.channel_id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + user_id = f"discord_{str(ctx.author.id)}" + user = honcho.get_or_create_user(user_id) + location_id = str(ctx.channel_id) + sessions = list(user.get_sessions_generator(location_id)) sessions[0].close() if len(sessions) > 0 else None - msg = "Great! The conversation has been restarted. What would you like to talk about?" + msg = ( + "Great! The conversation has been restarted. What would you like to talk about?" + ) await ctx.respond(msg) + bot.run(os.environ["BOT_TOKEN"]) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index afdaeaf..b744991 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -335,7 +335,32 @@ async def get_user(self, name: str): response = await self.client.get(url) response.raise_for_status() data = response.json() - return AsyncUser(self, **data) + return AsyncUser( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) + + async def get_or_create_user(self, name: str): + """Get or Create a user by name + + Args: + name (str): The name of the user + + Returns: + AsyncUser: The User object + """ + url = f"{self.base_url}/get_or_create/{name}" + response = await self.client.get(url) + response.raise_for_status() + data = response.json() + return AsyncUser( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) async def get_users( self, page: int = 1, page_size: int = 50, reverse: bool = False @@ -420,7 +445,6 @@ def __str__(self): """String representation of User""" return f"AsyncUser(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 - # TODO method to update metadata async def update_user(self, metadata: dict): """Updates a user's metadata @@ -436,7 +460,6 @@ async def update_user(self, metadata: dict): response.raise_for_status() data = response.json() self.metadata = data["metadata"] - # TODO update this object's metadata field # return AsyncUser(self.honcho, **data) async def get_session(self, session_id: uuid.UUID): diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 69a4fe7..ec05621 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -335,7 +335,32 @@ def get_user(self, name: str): response = self.client.get(url) response.raise_for_status() data = response.json() - return User(self, **data) + return User( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) + + def get_or_create_user(self, name: str): + """Get or Create a user by name + + Args: + name (str): The name of the user + + Returns: + User: The User object + """ + url = f"{self.base_url}/get_or_create/{name}" + response = self.client.get(url) + response.raise_for_status() + data = response.json() + return User( + honcho=self, + id=data["id"], + metadata=data["metadata"], + created_at=data["created_at"], + ) def get_users( self, page: int = 1, page_size: int = 50, reverse: bool = False @@ -420,7 +445,6 @@ def __str__(self): """String representation of User""" return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 - # TODO method to update metadata def update_user(self, metadata: dict): """Updates a user's metadata @@ -436,7 +460,6 @@ def update_user(self, metadata: dict): response.raise_for_status() data = response.json() self.metadata = data["metadata"] - # TODO update this object's metadata field # return User(self.honcho, **data) def get_session(self, session_id: uuid.UUID): diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 4a95234..495df46 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.3" +version = "0.0.4" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0" From 9abafa72fb08b876c2ffa160fd769a7c694e8f57 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 04:17:58 -0800 Subject: [PATCH 40/85] DSPy Todo and documentation updates --- api/CHANGELOG.md | 15 +++++ api/pyproject.toml | 2 +- example/discord/honcho-dspy-personas/bot.py | 62 ++++++++++++------- example/discord/honcho-dspy-personas/graph.py | 61 ++++++++++++------ sdk/CHANGELOG.md | 17 +++++ sdk/README.md | 11 ++-- sdk/honcho/client.py | 31 +++++++--- sdk/honcho/sync_client.py | 31 +++++++--- 8 files changed, 170 insertions(+), 60 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 84cd89e..9b5f847 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.4] — 2024-02-22 + +### Added + +* apps table with a relationship to the users table +* users table with a relationship to the collections and sessions tables +* Reverse Pagination support to get recent messages, sessions, etc. more easily +* Linting Rules + +### Changed + +* Get sessions method returns all sessions including inactive +* using timestampz instead of timestamp + + ## [0.0.3] — 2024-02-15 ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index 2aaee65..e4423e5 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho" -version = "0.0.3" +version = "0.0.4" description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index 948bdb3..74536ed 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -1,7 +1,7 @@ import os from uuid import uuid1 import discord -from honcho import Client as HonchoClient +from honcho import Honcho from graph import chat from chain import langchain_message_converter @@ -12,19 +12,22 @@ intents.reactions = True # Enable reactions intent # app_id = str(uuid1()) -app_id = "vince-dspy-personas" +app_name = "vince-dspy-personas" -#honcho = HonchoClient(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local -honcho = HonchoClient(app_id=app_id) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho.initialize() bot = discord.Bot(intents=intents) -thumbs_up_messages = [] -thumbs_down_messages = [] +thumbs_up_messages = [] +thumbs_down_messages = [] + @bot.event async def on_ready(): - print(f'We have logged in as {bot.user}') + print(f"We have logged in as {bot.user}") + @bot.event async def on_member_join(member): @@ -32,27 +35,28 @@ async def on_member_join(member): f"*Hello {member.name}, welcome to the server! This is a demo bot built with Honcho,* " "*implementing a naive user modeling method.* " "*To get started, just type a message in this channel and the bot will respond.* " - "*Over time, it will classify the \"state\" you're in and optimize conversations based on that state.* " + '*Over time, it will classify the "state" you\'re in and optimize conversations based on that state.* ' "*You can use the /restart command to restart the conversation at any time.* " "*If you have any questions or feedback, feel free to ask in the #honcho channel.* " "*Enjoy!*" ) - - + + @bot.event async def on_message(message): if message.author == bot.user or message.guild is not None: return user_id = f"discord_{str(message.author.id)}" - location_id=str(message.channel.id) + user = honcho.get_or_create_user(user_id) + location_id = str(message.channel.id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + sessions = list(user.get_sessions_generator(location_id)) if len(sessions) > 0: session = sessions[0] else: - session = honcho.create_session(user_id, location_id) + session = user.create_session(location_id) history = list(session.get_messages_generator())[:5] chat_history = langchain_message_converter(history) @@ -65,27 +69,29 @@ async def on_message(message): chat_history=chat_history, user_message=user_message, session=session, - input=inp + input=inp, ) await message.channel.send(response) session.create_message(is_user=False, content=response) + @bot.event async def on_reaction_add(reaction, user): # Ensure the bot does not react to its own reactions if user == bot.user: return - + user_id = f"discord_{str(reaction.message.author.id)}" + user = honcho.get_or_create_user(user_id) location_id = str(reaction.message.channel.id) # Check if the reaction is a thumbs up - if str(reaction.emoji) == '👍': + if str(reaction.emoji) == "👍": thumbs_up_messages.append(reaction.message.content) print(f"Added to thumbs up: {reaction.message.content}") # Check if the reaction is a thumbs down - elif str(reaction.emoji) == '👎': + elif str(reaction.emoji) == "👎": thumbs_down_messages.append(reaction.message.content) print(f"Added to thumbs down: {reaction.message.content}") @@ -94,16 +100,28 @@ async def on_reaction_add(reaction, user): # example = Example(chat_input=chat_input, assessment_dimension=user_state, response=response).with_inputs('chat_input') # examples.append(example) # user_state_storage[user_state]["examples"] = examples + example = Example( + chat_input=chat_input, assessment_dimension=user_state, response=response + ).with_inputs("chat_input") + user_state_storage = dict(user.metadata) + examples = user_state_storage.get("examples", []) + examples.append(example) + user_state_storage["examples"] = examples + user.update(metadata=user_state_storage) -@bot.slash_command(name = "restart", description = "Restart the Conversation") +@bot.slash_command(name="restart", description="Restart the Conversation") async def restart(ctx): - user_id=f"discord_{str(ctx.author.id)}" - location_id=str(ctx.channel_id) - sessions = list(honcho.get_sessions_generator(user_id, location_id)) + user_id = f"discord_{str(ctx.author.id)}" + user = honcho.get_or_create_user(user_id) + location_id = str(ctx.channel_id) + sessions = list(user.get_sessions_generator(location_id)) sessions[0].close() if len(sessions) > 0 else None - msg = "Great! The conversation has been restarted. What would you like to talk about?" + msg = ( + "Great! The conversation has been restarted. What would you like to talk about?" + ) await ctx.respond(msg) + bot.run(os.environ["BOT_TOKEN"]) diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 9295a43..021ee00 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -16,46 +16,68 @@ dspy.settings.configure(lm=dspy_gpt4) - # DSPy Signatures class Thought(dspy.Signature): """Generate a thought about the user's needs""" + user_input = dspy.InputField() thought = dspy.OutputField(desc="a prediction about the user's mental state") + class Response(dspy.Signature): """Generate a response for the user based on the thought provided""" + user_input = dspy.InputField() thought = dspy.InputField() response = dspy.OutputField(desc="keep the conversation going, be engaging") + # DSPy Module class ChatWithThought(dspy.Module): generate_thought = dspy.Predict(Thought) generate_response = dspy.Predict(Response) - def forward(self, chat_input: str, user_message: Optional[Message] = None, session: Optional[Session] = None): + def forward( + self, + chat_input: str, + user_message: Optional[Message] = None, + session: Optional[Session] = None, + ): # call the thought predictor thought = self.generate_thought(user_input=chat_input) - + if session and user_message: - session.create_metamessage(user_message, metamessage_type="thought", content=thought.thought) + session.create_metamessage( + user_message, metamessage_type="thought", content=thought.thought + ) # call the response predictor - response = self.generate_response(user_input=chat_input, thought=thought.thought) + response = self.generate_response( + user_input=chat_input, thought=thought.thought + ) # remove ai prefix response = response.response.replace("ai:", "").strip() return response - -user_state_storage = {} -async def chat(user_message: Message, session: Session, chat_history: List[Message], input: str, optimization_threshold=3): + + +# user_state_storage = {} +async def chat( + user_message: Message, + session: Session, + chat_history: List[Message], + input: str, + optimization_threshold=3, +): + user_state_storage = dict(session.user.metadata) # first we need to see if the user has any existing states existing_states = list(user_state_storage.keys()) - + # then we need to take the user input and determine the user's state/dimension/persona - is_state_new, user_state = await StateExtractor.generate_state(existing_states=existing_states, chat_history=chat_history, input=input) + is_state_new, user_state = await StateExtractor.generate_state( + existing_states=existing_states, chat_history=chat_history, input=input + ) print(f"USER STATE: {user_state}") print(f"IS STATE NEW: {is_state_new}") @@ -64,10 +86,7 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa # TODO: you'd want to initialize user state object from Honcho # Save the user_state if it's new if is_state_new: - user_state_storage[user_state] = { - "chat_module": {}, - "examples": [] - } + user_state_storage[user_state] = {"chat_module": {}, "examples": []} user_state_data = user_state_storage[user_state] @@ -75,22 +94,28 @@ async def chat(user_message: Message, session: Session, chat_history: List[Messa # TODO: read in examples from Honcho User Object examples = user_state_data["examples"] print(f"Num examples: {len(examples)}") - + if len(examples) >= optimization_threshold: # Optimize chat module optimizer = BootstrapFewShot(metric=metric) compiled_chat_module = optimizer.compile(user_chat_module, trainset=examples) - user_state_data["chat_module"] = compiled_chat_module.dump_state() + # user_state_data["chat_module"] = compiled_chat_module.dump_state() + user_state_storage[user_state][ + "chat_module" + ] = compiled_chat_module.dump_state() user_chat_module = compiled_chat_module # save to file for debugging purposes # compiled_chat_module.save("module.json") - + # Update User in Honcho + session.user.update(metadata=user_state_storage) # use that pipeline to generate a response chat_input = format_chat_history(chat_history, user_input=input) - response = user_chat_module(user_message=user_message, session=session, chat_input=chat_input) + response = user_chat_module( + user_message=user_message, session=session, chat_input=chat_input + ) dspy_gpt4.inspect_history(n=2) return response diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 54965e4..ace34de 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.4] — 2024-02-22 + +### Added + +* A User object for global user level metadat and more object oriented interface +* Reverse Pagination support to get recent messages, sessions, etc. more easily +* Linting Rules + +### Changed + +* Get sessions method returns all sessions including inactive +* using timestampz instead of timestamp +* `Client` renamed to `Honcho` +* `Honcho` takes in `app_name` instead of `app_id`. `app_name` needs to be a + unique identifier +* `Honcho` object requires an `initialize()` call to be used + ## [0.0.3] — 2024-02-15 diff --git a/sdk/README.md b/sdk/README.md index d2fedf5..31859fb 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -31,12 +31,13 @@ by default if no other string is provided. ```python from uuid import uuid4 -from honcho import Client as HonchoClient +from honcho import Honcho -app_id = str(uuid4()) -honcho = HonchoClient(app_id=app_id) -user_id = "test" -session = honcho.create_session(user_id=user_id) +app_name = str(uuid4()) +honcho = Honcho(app_name=app_name) +user_name = "test" +user = honcho.create_user(user_name) +session = user.create_session() session.create_message(is_user=True, content="Hello I'm a human") diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index b744991..09037f5 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -295,7 +295,24 @@ async def initialize(self): @property def base_url(self): """Shorcut for common API prefix. made a property to prevent tampering""" - return f"{self.server_url}/apps/{self.app_id}/users" + return f"{self.server_url}/apps/{self.app_id}" + + async def update(self, metadata: dict): + """Update the metadata of the app associated with this instance of the Honcho + client + + Args: + metadata (dict): The metadata to update + + Returns: + boolean: Whether the metadata was successfully updated + """ + data = {"metadata": metadata} + url = f"{self.base_url}" + response = await self.client.put(url, json=data) + success = response.status_code < 400 + self.metadata = metadata + return success async def create_user(self, name: str, metadata: Optional[dict] = None): """Create a new user by name @@ -309,7 +326,7 @@ async def create_user(self, name: str, metadata: Optional[dict] = None): """ if metadata is None: metadata = {} - url = f"{self.base_url}" + url = f"{self.base_url}/users" response = await self.client.post( url, json={"name": name, "metadata": metadata} ) @@ -331,7 +348,7 @@ async def get_user(self, name: str): Returns: AsyncUser: The User object """ - url = f"{self.base_url}/{name}" + url = f"{self.base_url}/users/{name}" response = await self.client.get(url) response.raise_for_status() data = response.json() @@ -351,7 +368,7 @@ async def get_or_create_user(self, name: str): Returns: AsyncUser: The User object """ - url = f"{self.base_url}/get_or_create/{name}" + url = f"{self.base_url}/users/get_or_create/{name}" response = await self.client.get(url) response.raise_for_status() data = response.json() @@ -370,7 +387,7 @@ async def get_users( Returns: AsyncGetUserPage: Paginated list of users """ - url = f"{self.base_url}?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" response = await self.client.get(url) response.raise_for_status() data = response.json() @@ -439,13 +456,13 @@ def __init__( @property def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.honcho.base_url}/{self.id}" + return f"{self.honcho.base_url}/users/{self.id}" def __str__(self): """String representation of User""" return f"AsyncUser(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 - async def update_user(self, metadata: dict): + async def update(self, metadata: dict): """Updates a user's metadata Args: diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index ec05621..f431604 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -295,7 +295,24 @@ def initialize(self): @property def base_url(self): """Shorcut for common API prefix. made a property to prevent tampering""" - return f"{self.server_url}/apps/{self.app_id}/users" + return f"{self.server_url}/apps/{self.app_id}" + + def update(self, metadata: dict): + """Update the metadata of the app associated with this instance of the Honcho + client + + Args: + metadata (dict): The metadata to update + + Returns: + boolean: Whether the metadata was successfully updated + """ + data = {"metadata": metadata} + url = f"{self.base_url}" + response = self.client.put(url, json=data) + success = response.status_code < 400 + self.metadata = metadata + return success def create_user(self, name: str, metadata: Optional[dict] = None): """Create a new user by name @@ -309,7 +326,7 @@ def create_user(self, name: str, metadata: Optional[dict] = None): """ if metadata is None: metadata = {} - url = f"{self.base_url}" + url = f"{self.base_url}/users" response = self.client.post( url, json={"name": name, "metadata": metadata} ) @@ -331,7 +348,7 @@ def get_user(self, name: str): Returns: User: The User object """ - url = f"{self.base_url}/{name}" + url = f"{self.base_url}/users/{name}" response = self.client.get(url) response.raise_for_status() data = response.json() @@ -351,7 +368,7 @@ def get_or_create_user(self, name: str): Returns: User: The User object """ - url = f"{self.base_url}/get_or_create/{name}" + url = f"{self.base_url}/users/get_or_create/{name}" response = self.client.get(url) response.raise_for_status() data = response.json() @@ -370,7 +387,7 @@ def get_users( Returns: GetUserPage: Paginated list of users """ - url = f"{self.base_url}?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" response = self.client.get(url) response.raise_for_status() data = response.json() @@ -439,13 +456,13 @@ def __init__( @property def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" - return f"{self.honcho.base_url}/{self.id}" + return f"{self.honcho.base_url}/users/{self.id}" def __str__(self): """String representation of User""" return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 - def update_user(self, metadata: dict): + def update(self, metadata: dict): """Updates a user's metadata Args: From c584fad11644f83cfafcbc415bf9448bd5f0874d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:33:34 -0800 Subject: [PATCH 41/85] Add is_active filtering --- api/src/crud.py | 5 ++++- api/src/main.py | 8 +++++++- sdk/honcho/client.py | 25 ++++++++++++++++++------- sdk/honcho/sync_client.py | 25 ++++++++++++++++++------- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index 5e954e6..10f24e8 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -169,15 +169,18 @@ def get_sessions( user_id: uuid.UUID, location_id: Optional[str] = None, reverse: Optional[bool] = False, + is_active: Optional[bool] = False, ) -> Select: stmt = ( select(models.Session) .join(models.User, models.User.id == models.Session.user_id) .where(models.User.app_id == app_id) .where(models.Session.user_id == user_id) - # .where(models.Session.is_active.is_(True)) ) + if is_active: + stmt = stmt.where(models.Session.is_active.is_(True)) + if reverse: stmt = stmt.order_by(models.Session.created_at.desc()) else: diff --git a/api/src/main.py b/api/src/main.py index 0f19470..4784bc3 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -265,6 +265,7 @@ def get_sessions( app_id: uuid.UUID, user_id: uuid.UUID, location_id: Optional[str] = None, + is_active: Optional[bool] = False, reverse: Optional[bool] = False, db: Session = Depends(get_db), ): @@ -282,7 +283,12 @@ def get_sessions( return paginate( db, crud.get_sessions( - db, app_id=app_id, user_id=user_id, location_id=location_id, reverse=reverse + db, + app_id=app_id, + user_id=user_id, + location_id=location_id, + reverse=reverse, + is_active=is_active, ), ) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 09037f5..b75e203 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -36,9 +36,9 @@ def __init__(self, response: dict, honcho: AsyncHoncho, reverse: bool): """Constructor for Page Result from User Get Request Args: + response (dict): Response from API with pagination information honcho (AsyncHoncho): Honcho Client reverse (bool): Whether to reverse the order of the results or not - response (dict): Response from API with pagination information """ super().__init__(response) self.honcho = honcho @@ -65,20 +65,26 @@ class AsyncGetSessionPage(AsyncGetPage): """Paginated Results for Get Session Requests""" def __init__( - self, response: dict, user: AsyncUser, reverse: bool, location_id: Optional[str] + self, + response: dict, + user: AsyncUser, + reverse: bool, + location_id: Optional[str], + is_active: bool, ): """Constructor for Page Result from Session Get Request Args: + response (dict): Response from API with pagination information user (AsyncUser): Honcho User associated with the session - location_id (str): ID of the location associated with the session reverse (bool): Whether to reverse the order of the results or not - response (dict): Response from API with pagination information + location_id (str): ID of the location associated with the session """ super().__init__(response) self.user = user self.location_id = location_id self.reverse = reverse + self.is_active = is_active self.items = [ AsyncSession( user=user, @@ -104,6 +110,7 @@ async def next(self): page=(self.page + 1), page_size=self.page_size, reverse=self.reverse, + is_active=self.is_active, ) @@ -114,8 +121,9 @@ def __init__(self, response: dict, session: AsyncSession, reverse: bool): """Constructor for Page Result from Session Get Request Args: - session (AsyncSession): Session the returned messages are associated with response (dict): Response from API with pagination information + session (AsyncSession): Session the returned messages are associated with + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) self.session = session @@ -160,6 +168,8 @@ def __init__( session (AsyncSession): Session the returned messages are associated with reverse (bool): Whether to reverse the order of the results + message_id (Optional[str]): ID of the message associated with the + metamessage_type (Optional[str]): Type of the metamessage """ super().__init__(response) self.session = session @@ -508,6 +518,7 @@ async def get_sessions( page: int = 1, page_size: int = 50, reverse: bool = False, + is_active: bool = False, ): """Return sessions associated with a user paginated @@ -522,13 +533,13 @@ async def get_sessions( """ url = ( - f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}" + f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" + (f"&location_id={location_id}" if location_id else "") ) response = await self.honcho.client.get(url) response.raise_for_status() data = response.json() - return AsyncGetSessionPage(data, self, reverse, location_id) + return AsyncGetSessionPage(data, self, reverse, location_id, is_active) async def get_sessions_generator( self, diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index f431604..3c2f950 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -36,9 +36,9 @@ def __init__(self, response: dict, honcho: Honcho, reverse: bool): """Constructor for Page Result from User Get Request Args: + response (dict): Response from API with pagination information honcho (Honcho): Honcho Client reverse (bool): Whether to reverse the order of the results or not - response (dict): Response from API with pagination information """ super().__init__(response) self.honcho = honcho @@ -65,20 +65,26 @@ class GetSessionPage(GetPage): """Paginated Results for Get Session Requests""" def __init__( - self, response: dict, user: User, reverse: bool, location_id: Optional[str] + self, + response: dict, + user: User, + reverse: bool, + location_id: Optional[str], + is_active: bool, ): """Constructor for Page Result from Session Get Request Args: + response (dict): Response from API with pagination information user (User): Honcho User associated with the session - location_id (str): ID of the location associated with the session reverse (bool): Whether to reverse the order of the results or not - response (dict): Response from API with pagination information + location_id (str): ID of the location associated with the session """ super().__init__(response) self.user = user self.location_id = location_id self.reverse = reverse + self.is_active = is_active self.items = [ Session( user=user, @@ -104,6 +110,7 @@ def next(self): page=(self.page + 1), page_size=self.page_size, reverse=self.reverse, + is_active=self.is_active, ) @@ -114,8 +121,9 @@ def __init__(self, response: dict, session: Session, reverse: bool): """Constructor for Page Result from Session Get Request Args: - session (Session): Session the returned messages are associated with response (dict): Response from API with pagination information + session (Session): Session the returned messages are associated with + reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) self.session = session @@ -160,6 +168,8 @@ def __init__( session (Session): Session the returned messages are associated with reverse (bool): Whether to reverse the order of the results + message_id (Optional[str]): ID of the message associated with the + metamessage_type (Optional[str]): Type of the metamessage """ super().__init__(response) self.session = session @@ -508,6 +518,7 @@ def get_sessions( page: int = 1, page_size: int = 50, reverse: bool = False, + is_active: bool = False, ): """Return sessions associated with a user paginated @@ -522,13 +533,13 @@ def get_sessions( """ url = ( - f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}" + f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" + (f"&location_id={location_id}" if location_id else "") ) response = self.honcho.client.get(url) response.raise_for_status() data = response.json() - return GetSessionPage(data, self, reverse, location_id) + return GetSessionPage(data, self, reverse, location_id, is_active) def get_sessions_generator( self, From 67b16019327b02fba1e0edd6d93f7a7b3957bddf Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:34:51 -0800 Subject: [PATCH 42/85] Add is_active filtering to the generator --- sdk/honcho/client.py | 3 ++- sdk/honcho/sync_client.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index b75e203..5da3740 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -545,6 +545,7 @@ async def get_sessions_generator( self, location_id: Optional[str] = None, reverse: bool = False, + is_active: bool = False, ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -560,7 +561,7 @@ async def get_sessions_generator( page = 1 page_size = 50 get_session_response = await self.get_sessions( - location_id, page, page_size, reverse + location_id, page, page_size, reverse, is_active ) while True: for session in get_session_response.items: diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 3c2f950..2db7423 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -545,6 +545,7 @@ def get_sessions_generator( self, location_id: Optional[str] = None, reverse: bool = False, + is_active: bool = False, ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -560,7 +561,7 @@ def get_sessions_generator( page = 1 page_size = 50 get_session_response = self.get_sessions( - location_id, page, page_size, reverse + location_id, page, page_size, reverse, is_active ) while True: for session in get_session_response.items: From 876e8e895959c4fe20fe0e4cd59f9470b43e7834 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:59:37 -0800 Subject: [PATCH 43/85] Fix update user metadata --- sdk/honcho/client.py | 15 +++++++++++++-- sdk/honcho/sync_client.py | 15 +++++++++++++-- sdk/tests/test_async.py | 13 +++++++++++++ sdk/tests/test_sync.py | 12 ++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 5da3740..2d25ee0 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -394,6 +394,11 @@ async def get_users( ): """Get Paginated list of users + Args: + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results + Returns: AsyncGetUserPage: Paginated list of users """ @@ -482,11 +487,14 @@ async def update(self, metadata: dict): AsyncUser: The updated User object """ + data = {"metadata": metadata} url = f"{self.base_url}" - response = await self.honcho.client.put(url, json=metadata) + response = await self.honcho.client.put(url, json=data) response.raise_for_status() + success = response.status_code < 400 data = response.json() self.metadata = data["metadata"] + return success # return AsyncUser(self.honcho, **data) async def get_session(self, session_id: uuid.UUID): @@ -527,6 +535,8 @@ async def get_sessions( location of a session page (int, optional): The page of results to return page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results + is_active (bool): Whether to only return active sessions Returns: AsyncGetSessionPage: Page or results for get_sessions query @@ -553,6 +563,8 @@ async def get_sessions_generator( Args: location_id (str, optional): Optional Location ID representing the location of a session + reverse (bool): Whether to reverse the order of the results + is_active (bool): Whether to only return active sessions Yields: AsyncSession: The Session object of the requested Session @@ -1029,7 +1041,6 @@ async def create_document(self, content: str, metadata: Optional[dict] = None): metadata = {} data = {"metadata": metadata, "content": content} url = f"{self.base_url}/documents" - print(url) response = await self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 2db7423..4893f80 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -394,6 +394,11 @@ def get_users( ): """Get Paginated list of users + Args: + page (int, optional): The page of results to return + page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results + Returns: GetUserPage: Paginated list of users """ @@ -482,11 +487,14 @@ def update(self, metadata: dict): User: The updated User object """ + data = {"metadata": metadata} url = f"{self.base_url}" - response = self.honcho.client.put(url, json=metadata) + response = self.honcho.client.put(url, json=data) response.raise_for_status() + success = response.status_code < 400 data = response.json() self.metadata = data["metadata"] + return success # return User(self.honcho, **data) def get_session(self, session_id: uuid.UUID): @@ -527,6 +535,8 @@ def get_sessions( location of a session page (int, optional): The page of results to return page_size (int, optional): The number of results to return + reverse (bool): Whether to reverse the order of the results + is_active (bool): Whether to only return active sessions Returns: GetSessionPage: Page or results for get_sessions query @@ -553,6 +563,8 @@ def get_sessions_generator( Args: location_id (str, optional): Optional Location ID representing the location of a session + reverse (bool): Whether to reverse the order of the results + is_active (bool): Whether to only return active sessions Yields: Session: The Session object of the requested Session @@ -1029,7 +1041,6 @@ def create_document(self, content: str, metadata: Optional[dict] = None): metadata = {} data = {"metadata": metadata, "content": content} url = f"{self.base_url}/documents" - print(url) response = self.user.honcho.client.post(url, json=data) response.raise_for_status() data = response.json() diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index 0c52875..04ffc4a 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -15,6 +15,19 @@ from honcho import AsyncHoncho as Honcho +@pytest.mark.asyncio +async def test_user_update(): + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + assert user.metadata == {} + assert await user.update({"foo": "bar"}) + retrieved_user = await honcho.get_user(user_name) + assert retrieved_user.metadata == {"foo": "bar"} + + @pytest.mark.asyncio async def test_session_creation_retrieval(): app_name = str(uuid1()) diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 16eba65..fd92234 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -15,6 +15,18 @@ from honcho import Honcho as Honcho +def test_user_update(): + user_name = str(uuid1()) + app_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + assert user.metadata == {} + assert user.update({"foo": "bar"}) + retrieved_user = honcho.get_user(user_name) + assert retrieved_user.metadata == {"foo": "bar"} + + def test_session_creation_retrieval(): app_name = str(uuid1()) honcho = Honcho(app_name, "http://localhost:8000") From cf79652fa5eca73dc060b04ce4820af7f7fa5ab6 Mon Sep 17 00:00:00 2001 From: vintro Date: Thu, 22 Feb 2024 20:03:45 -0500 Subject: [PATCH 44/85] working, but weird compiler error --- example/discord/honcho-dspy-personas/bot.py | 65 ++++++++++++------- example/discord/honcho-dspy-personas/chain.py | 3 + example/discord/honcho-dspy-personas/graph.py | 28 ++++++-- .../langchain_prompts/state_check.yaml | 4 +- .../langchain_prompts/state_commentary.yaml | 6 +- .../langchain_prompts/state_labeling.yaml | 10 +-- .../discord/honcho-dspy-personas/poetry.lock | 50 +++++++------- .../honcho-dspy-personas/pyproject.toml | 2 +- .../honcho-dspy-personas/response_metric.py | 53 +++++++++++---- 9 files changed, 145 insertions(+), 76 deletions(-) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index 74536ed..a385ae3 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -3,6 +3,7 @@ import discord from honcho import Honcho from graph import chat +from dspy import Example from chain import langchain_message_converter intents = discord.Intents.default() @@ -14,8 +15,8 @@ # app_id = str(uuid1()) app_name = "vince-dspy-personas" -# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local -honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +# honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev honcho.initialize() bot = discord.Bot(intents=intents) @@ -51,7 +52,7 @@ async def on_message(message): user = honcho.get_or_create_user(user_id) location_id = str(message.channel.id) - sessions = list(user.get_sessions_generator(location_id)) + sessions = list(user.get_sessions_generator(location_id, is_active=True, reverse=True)) if len(sessions) > 0: session = sessions[0] @@ -82,32 +83,48 @@ async def on_reaction_add(reaction, user): if user == bot.user: return - user_id = f"discord_{str(reaction.message.author.id)}" - user = honcho.get_or_create_user(user_id) + user_id = f"discord_{str(user.id)}" + honcho_user = honcho.get_or_create_user(user_id) location_id = str(reaction.message.channel.id) + sessions = list(honcho_user.get_sessions_generator(location_id, is_active=True, reverse=True)) + if len(sessions) > 0: + session = sessions[0] + else: + session = honcho_user.create_session(location_id) + + messages = list(session.get_messages_generator(reverse=True)) + ai_responses = [message for message in messages if not message.is_user] + user_responses = [message for message in messages if message.is_user] + # most recent AI response + ai_response = ai_responses[0].content + user_response = user_responses[0] + + user_state_storage = dict(honcho_user.metadata) + user_state = list(session.get_metamessages_generator(metamessage_type="user_state", message=user_response, reverse=True))[0].content + examples = user_state_storage[user_state]["examples"] + # Check if the reaction is a thumbs up if str(reaction.emoji) == "👍": - thumbs_up_messages.append(reaction.message.content) - print(f"Added to thumbs up: {reaction.message.content}") + example = Example( + chat_input=user_response.content, + response=ai_response, + assessment_dimension=user_state, + label='yes' + ).with_inputs("chat_input", "response", "assessment_dimension") + examples.append(example.toDict()) # Check if the reaction is a thumbs down elif str(reaction.emoji) == "👎": - thumbs_down_messages.append(reaction.message.content) - print(f"Added to thumbs down: {reaction.message.content}") - - # TODO: we need to append these to the examples list within the user state json object - # append example - # example = Example(chat_input=chat_input, assessment_dimension=user_state, response=response).with_inputs('chat_input') - # examples.append(example) - # user_state_storage[user_state]["examples"] = examples - example = Example( - chat_input=chat_input, assessment_dimension=user_state, response=response - ).with_inputs("chat_input") - user_state_storage = dict(user.metadata) - examples = user_state_storage.get("examples", []) - examples.append(example) - user_state_storage["examples"] = examples - user.update(metadata=user_state_storage) + example = Example( + chat_input=user_response.content, + response=ai_response, + assessment_dimension=user_state, + label='no' + ).with_inputs("chat_input", "response", "assessment_dimension") + examples.append(example.toDict()) + + user_state_storage[user_state]["examples"] = examples + honcho_user.update(metadata=user_state_storage) @bot.slash_command(name="restart", description="Restart the Conversation") @@ -115,7 +132,7 @@ async def restart(ctx): user_id = f"discord_{str(ctx.author.id)}" user = honcho.get_or_create_user(user_id) location_id = str(ctx.channel_id) - sessions = list(user.get_sessions_generator(location_id)) + sessions = list(user.get_sessions_generator(location_id, reverse=True)) sessions[0].close() if len(sessions) > 0 else None msg = ( diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index aa114e1..119d33b 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -1,11 +1,14 @@ import os from typing import List, Union +from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, load_prompt from langchain_core.messages import AIMessage, HumanMessage from honcho import Message +load_dotenv() + # langchain prompts SYSTEM_STATE_COMMENTARY = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_commentary.yaml')) SYSTEM_STATE_LABELING = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_labeling.yaml')) diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 021ee00..b3b17ba 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -2,7 +2,7 @@ import dspy from dspy import Example from typing import List, Optional -from dspy.teleprompt import BootstrapFewShot +from dspy.teleprompt import BootstrapFewShotWithRandomSearch from dotenv import load_dotenv from chain import StateExtractor, format_chat_history from response_metric import metric @@ -42,6 +42,7 @@ def forward( chat_input: str, user_message: Optional[Message] = None, session: Optional[Session] = None, + assessment_dimension = None, ): # call the thought predictor thought = self.generate_thought(user_input=chat_input) @@ -56,13 +57,9 @@ def forward( user_input=chat_input, thought=thought.thought ) - # remove ai prefix - response = response.response.replace("ai:", "").strip() - return response -# user_state_storage = {} async def chat( user_message: Message, session: Session, @@ -81,6 +78,12 @@ async def chat( print(f"USER STATE: {user_state}") print(f"IS STATE NEW: {is_state_new}") + # add metamessage to message to keep track of what label got assigned to what message + if session and user_message: + session.create_metamessage( + user_message, metamessage_type="user_state", content=user_state + ) + user_chat_module = ChatWithThought() # TODO: you'd want to initialize user state object from Honcho @@ -94,16 +97,27 @@ async def chat( # TODO: read in examples from Honcho User Object examples = user_state_data["examples"] print(f"Num examples: {len(examples)}") + session.user.update(metadata=user_state_storage) if len(examples) >= optimization_threshold: + # convert example from dicts to dspy Example objects + examples = [dspy.Example(**example).with_inputs("chat_input", "ai_response", "assessment_dimension") for example in examples] + print(examples) + # Splitting the examples list into train and validation sets + # train_examples = examples[:-1] # All but the last item for training + # val_examples = examples[-1:] # The last item for validation + # Optimize chat module - optimizer = BootstrapFewShot(metric=metric) + optimizer = BootstrapFewShotWithRandomSearch(metric=metric, max_bootstrapped_demos=3, max_labeled_demos=3, num_candidate_programs=10, num_threads=4) + # compiled_chat_module = optimizer.compile(ChatWithThought(), trainset=train_examples, valset=val_examples) compiled_chat_module = optimizer.compile(user_chat_module, trainset=examples) + print(f"COMPILED_CHAT_MODULE: {compiled_chat_module}") # user_state_data["chat_module"] = compiled_chat_module.dump_state() user_state_storage[user_state][ "chat_module" ] = compiled_chat_module.dump_state() + print(f"DUMPED_STATE: {compiled_chat_module.dump_state()}") user_chat_module = compiled_chat_module # save to file for debugging purposes @@ -116,6 +130,8 @@ async def chat( response = user_chat_module( user_message=user_message, session=session, chat_input=chat_input ) + # remove ai prefix + response = response.response.replace("ai:", "").strip() dspy_gpt4.inspect_history(n=2) return response diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml index 6fe2f0f..d997adb 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_check.yaml @@ -4,7 +4,7 @@ input_variables: template: > Given the list of existing states, determine whether or not the new state is represented in the list of existing states. - existing states: ```{existing_states}``` - new state: ```{state}``` + existing states: """{existing_states}""" + new state: """{state}""" If the new state is sufficiently similar to a value in the list of existing states, return that existing state value. If the new state is NOT sufficiently similar to anything in existing states, return "None". Output a single value only. \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml index f1e2b90..bd4ee0c 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_commentary.yaml @@ -4,6 +4,6 @@ input_variables: template: > Your job is to make a prediction about the task the user might be engaging in. Some people might be researching, exploring curiosities, or just asking questions for general inquiry. Provide commentary that would shed light on the "mode" the user might be in. - existing states: ```{existing_states}``` - chat history: ```{chat_history}``` - user input: ```{user_input}``` \ No newline at end of file + existing states: """{existing_states}""" + chat history: """{chat_history}""" + user input: """{user_input}""" \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml index c3dd8fb..a2e3105 100644 --- a/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml +++ b/example/discord/honcho-dspy-personas/langchain_prompts/state_labeling.yaml @@ -4,10 +4,10 @@ input_variables: template: > Your job is to label the state the user might be in. Some people might be conducting research, exploring a interest, or just asking questions for general inquiry. - commentary: ```{state_commentary}``` - Prior states, from oldest to most recent: ``` + commentary: """{state_commentary}""" + Prior states, from oldest to most recent:""" {existing_states} - ```` - - Take into account the user's prior states when making your prediction. Output your prediction as a concise, single word label. + """ + Take into account the user's prior states when making your prediction. Output your prediction as a concise, single word label. + \ No newline at end of file diff --git a/example/discord/honcho-dspy-personas/poetry.lock b/example/discord/honcho-dspy-personas/poetry.lock index 7329a15..589f511 100644 --- a/example/discord/honcho-dspy-personas/poetry.lock +++ b/example/discord/honcho-dspy-personas/poetry.lock @@ -154,13 +154,13 @@ files = [ [[package]] name = "anyio" -version = "4.2.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] @@ -676,27 +676,29 @@ files = [ [[package]] name = "honcho-ai" -version = "0.0.3" +version = "0.0.4" description = "Python Client SDK for Honcho" optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "honcho_ai-0.0.3-py3-none-any.whl", hash = "sha256:a817ec62c4fd8dad1d629927511ce98a3f626f4bc55474187b80010e208e61ba"}, - {file = "honcho_ai-0.0.3.tar.gz", hash = "sha256:ca52bb8c5036bfdbeee0c71ca754c580c672b28a4824240123b783f8679ca18e"}, -] +python-versions = "^3.10" +files = [] +develop = true [package.dependencies] -httpx = ">=0.26.0,<0.27.0" +httpx = "^0.26.0" + +[package.source] +type = "directory" +url = "../../../sdk" [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -707,7 +709,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" @@ -814,13 +816,13 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.24" +version = "0.1.25" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.24-py3-none-any.whl", hash = "sha256:1887bb2e0c12e0d94c1e805eb56d08dbb670232daf0906761f726bd507324319"}, - {file = "langchain_core-0.1.24.tar.gz", hash = "sha256:ce70f4b97695eb55637e00ee33d480fffc6db1f95726f99b076b55cb1a42927d"}, + {file = "langchain_core-0.1.25-py3-none-any.whl", hash = "sha256:ff0a0ad1ed877878e7b9c7601870cd12145abf3c814aae41995968d05ea6c09d"}, + {file = "langchain_core-0.1.25.tar.gz", hash = "sha256:065ff8b4e383c5645d175b20ae44b258330ed06457b0fc0179efee310b6f2af6"}, ] [package.dependencies] @@ -855,13 +857,13 @@ tiktoken = ">=0.5.2,<1" [[package]] name = "langsmith" -version = "0.1.3" +version = "0.1.5" 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.1.3-py3-none-any.whl", hash = "sha256:b290f951d1ebff9abe2b52cc09d63acea75a9ca6e003a617310fb024eaf00f63"}, - {file = "langsmith-0.1.3.tar.gz", hash = "sha256:197bd1f5baa83db69a0eab644bab1eba8dcdf0c2d8b7c900a45916f7b3dd50ab"}, + {file = "langsmith-0.1.5-py3-none-any.whl", hash = "sha256:a1811821a923d90e53bcbacdd0988c3c366aff8f4c120d8777e7af8ecda06268"}, + {file = "langsmith-0.1.5.tar.gz", hash = "sha256:aa7a2861aa3d9ae563a077c622953533800466c4e2e539b0d567b84d5fd5b157"}, ] [package.dependencies] @@ -1950,13 +1952,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] @@ -2188,4 +2190,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "85bddbf515ca00359d2b25db6dc48e2943a136e3b65a9ed8a9537ad79bfd4eec" +content-hash = "f27a3578cc155c9eccf7d771fe3c47196500adaae5c92b0bbb55252ee338fa50" diff --git a/example/discord/honcho-dspy-personas/pyproject.toml b/example/discord/honcho-dspy-personas/pyproject.toml index 6f2082b..2a4144f 100644 --- a/example/discord/honcho-dspy-personas/pyproject.toml +++ b/example/discord/honcho-dspy-personas/pyproject.toml @@ -7,13 +7,13 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -honcho-ai = "^0.0.3" dspy-ai = "^2.1.10" python-dotenv = "^1.0.1" langchain-core = "^0.1.23" langchain-openai = "^0.0.6" py-cord = "^2.4.1" langsmith = "^0.1.3" +honcho-ai = {path = "../../../sdk", develop = true} [build-system] diff --git a/example/discord/honcho-dspy-personas/response_metric.py b/example/discord/honcho-dspy-personas/response_metric.py index 1383c11..76bb078 100644 --- a/example/discord/honcho-dspy-personas/response_metric.py +++ b/example/discord/honcho-dspy-personas/response_metric.py @@ -5,29 +5,60 @@ class MessageResponseAssess(dspy.Signature): """Assess the quality of a response along the specified dimension.""" chat_input = dspy.InputField() + assessment_dimension = dspy.InputField() # user state + example_response = dspy.InputField() + example_label = dspy.InputField() ai_response = dspy.InputField() - gold_response = dspy.InputField() - assessment_dimension = dspy.InputField() - assessment_answer = dspy.OutputField(desc="Good or not") + ai_response_label = dspy.OutputField(desc="yes or no") -def metric(example, ai_response, trace=None): +def metric(example, pred, trace=None): """Assess the quality of a response along the specified dimension.""" - assessment_dimension = example.assessment_dimension chat_input = example.chat_input - gold_response = example.response + assessment_dimension = f"The user is in the following state: {example.assessment_dimension}. Is the AI response appropriate for this state? Respond with Yes or No." + example_response = example.response + example_label = example.label + ai_response = pred with dspy.context(lm=gpt4T): assessment_result = dspy.Predict(MessageResponseAssess)( chat_input=chat_input, - ai_response=ai_response, - gold_response=gold_response, - assessment_dimension=assessment_dimension + assessment_dimension=assessment_dimension, + example_response=example_response, + example_label=example_label, + ai_response=ai_response, ) - is_positive = assessment_result.assessment_answer.lower() == 'good' + is_appropriate = assessment_result.ai_response_label.lower() == 'yes' gpt4T.inspect_history(n=3) - return is_positive + return is_appropriate + + + + + + +# def metric(example, ai_response, trace=None): +# """Assess the quality of a response along the specified dimension.""" +# example = dspy.Example(**example).with_inputs("chat_input", "ai_response", "assessment_dimension") + +# label = example.label +# chat_input = example.chat_input +# ai_response = example.ai_response +# assessment_dimension = example.assessment_dimension + +# with dspy.context(lm=gpt4T): +# assessment_result = dspy.Predict(MessageResponseAssess)( +# chat_input=chat_input, +# ai_response=ai_response, +# assessment_dimension=assessment_dimension +# ) + +# is_positive = assessment_result.assessment_answer.lower() == 'positive' + +# gpt4T.inspect_history(n=3) + +# return is_positive \ No newline at end of file From 3a2f5ea229b21c7cba89ff4a82f707fe67d05ced Mon Sep 17 00:00:00 2001 From: vintro Date: Fri, 23 Feb 2024 13:33:06 -0500 Subject: [PATCH 45/85] fixed str error in optimizer --- example/discord/honcho-dspy-personas/graph.py | 30 +++++++++++++------ .../honcho-dspy-personas/response_metric.py | 16 ++++------ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index b3b17ba..4f53dae 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -42,7 +42,8 @@ def forward( chat_input: str, user_message: Optional[Message] = None, session: Optional[Session] = None, - assessment_dimension = None, + response: Optional[str] = None, + assessment_dimension: Optional[str] = None, ): # call the thought predictor thought = self.generate_thought(user_input=chat_input) @@ -57,7 +58,7 @@ def forward( user_input=chat_input, thought=thought.thought ) - return response + return response # this is a prediction object async def chat( @@ -65,7 +66,7 @@ async def chat( session: Session, chat_history: List[Message], input: str, - optimization_threshold=3, + optimization_threshold=5, ): user_state_storage = dict(session.user.metadata) # first we need to see if the user has any existing states @@ -101,16 +102,24 @@ async def chat( if len(examples) >= optimization_threshold: # convert example from dicts to dspy Example objects - examples = [dspy.Example(**example).with_inputs("chat_input", "ai_response", "assessment_dimension") for example in examples] - print(examples) + optimizer_examples = [] + for example in examples: + optimizer_example = Example(**example).with_inputs("chat_input", "response", "assessment_dimension") + optimizer_examples.append(optimizer_example) + print(isinstance(optimizer_example, Example)) + print(optimizer_example._store) + # examples = [dspy.Example(example).with_inputs("chat_input", "response", "assessment_dimension") for example in examples] + # print(examples) # Splitting the examples list into train and validation sets - # train_examples = examples[:-1] # All but the last item for training - # val_examples = examples[-1:] # The last item for validation + train_examples = examples[:-1] # All but the last item for training + val_examples = examples[-1:] # The last item for validation # Optimize chat module optimizer = BootstrapFewShotWithRandomSearch(metric=metric, max_bootstrapped_demos=3, max_labeled_demos=3, num_candidate_programs=10, num_threads=4) - # compiled_chat_module = optimizer.compile(ChatWithThought(), trainset=train_examples, valset=val_examples) - compiled_chat_module = optimizer.compile(user_chat_module, trainset=examples) + # optimizer = BootstrapFewShot(metric=metric, max_rounds=5) + + compiled_chat_module = optimizer.compile(ChatWithThought(), trainset=train_examples, valset=val_examples) + # compiled_chat_module = optimizer.compile(user_chat_module, trainset=optimizer_examples) print(f"COMPILED_CHAT_MODULE: {compiled_chat_module}") # user_state_data["chat_module"] = compiled_chat_module.dump_state() @@ -132,6 +141,9 @@ async def chat( ) # remove ai prefix response = response.response.replace("ai:", "").strip() + + print("========== CHAT HISTORY ==========") dspy_gpt4.inspect_history(n=2) + print("======= END CHAT HISTORY =========") return response diff --git a/example/discord/honcho-dspy-personas/response_metric.py b/example/discord/honcho-dspy-personas/response_metric.py index 76bb078..3d9a5f1 100644 --- a/example/discord/honcho-dspy-personas/response_metric.py +++ b/example/discord/honcho-dspy-personas/response_metric.py @@ -7,8 +7,6 @@ class MessageResponseAssess(dspy.Signature): chat_input = dspy.InputField() assessment_dimension = dspy.InputField() # user state example_response = dspy.InputField() - example_label = dspy.InputField() - ai_response = dspy.InputField() ai_response_label = dspy.OutputField(desc="yes or no") @@ -17,22 +15,20 @@ def metric(example, pred, trace=None): chat_input = example.chat_input assessment_dimension = f"The user is in the following state: {example.assessment_dimension}. Is the AI response appropriate for this state? Respond with Yes or No." - example_response = example.response - example_label = example.label - ai_response = pred + example_response = pred.response with dspy.context(lm=gpt4T): assessment_result = dspy.Predict(MessageResponseAssess)( chat_input=chat_input, assessment_dimension=assessment_dimension, - example_response=example_response, - example_label=example_label, - ai_response=ai_response, + example_response=example_response ) - + is_appropriate = assessment_result.ai_response_label.lower() == 'yes' - gpt4T.inspect_history(n=3) + print("======== OPTIMIZER HISTORY ========") + gpt4T.inspect_history(n=5) + print("======== END OPTIMIZER HISTORY ========") return is_appropriate From 21c325bd355ee630afcefd2266ab75227f7d81b4 Mon Sep 17 00:00:00 2001 From: vintro Date: Fri, 23 Feb 2024 14:16:02 -0500 Subject: [PATCH 46/85] ship --- example/discord/honcho-dspy-personas/bot.py | 7 ++--- example/discord/honcho-dspy-personas/chain.py | 5 +++- example/discord/honcho-dspy-personas/graph.py | 22 +++----------- .../honcho-dspy-personas/response_metric.py | 29 +------------------ 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index a385ae3..77a7abf 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -12,11 +12,10 @@ intents.members = True intents.reactions = True # Enable reactions intent -# app_id = str(uuid1()) -app_name = "vince-dspy-personas" +app_name = str(uuid1()) -honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local -# honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +# honcho = Honcho(app_name=app_name, base_url="http://localhost:8000") # uncomment to use local +honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev honcho.initialize() bot = discord.Bot(intents=intents) diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index 119d33b..eede40e 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -82,8 +82,11 @@ async def generate_state_label(cls, existing_states: List[str], state_commentar "state_commentary": state_commentary, "existing_states": existing_states, }) + + # strip anything that's not letters + clean_response = ''.join(c for c in response.content if c.isalpha()) # return output - return response.content + return clean_response @classmethod async def check_state_exists(cls, existing_states: List[str], state: str): diff --git a/example/discord/honcho-dspy-personas/graph.py b/example/discord/honcho-dspy-personas/graph.py index 4f53dae..5c6f6a8 100644 --- a/example/discord/honcho-dspy-personas/graph.py +++ b/example/discord/honcho-dspy-personas/graph.py @@ -2,7 +2,7 @@ import dspy from dspy import Example from typing import List, Optional -from dspy.teleprompt import BootstrapFewShotWithRandomSearch +from dspy.teleprompt import BootstrapFewShot from dotenv import load_dotenv from chain import StateExtractor, format_chat_history from response_metric import metric @@ -66,7 +66,7 @@ async def chat( session: Session, chat_history: List[Message], input: str, - optimization_threshold=5, + optimization_threshold=3, ): user_state_storage = dict(session.user.metadata) # first we need to see if the user has any existing states @@ -87,7 +87,6 @@ async def chat( user_chat_module = ChatWithThought() - # TODO: you'd want to initialize user state object from Honcho # Save the user_state if it's new if is_state_new: user_state_storage[user_state] = {"chat_module": {}, "examples": []} @@ -95,7 +94,6 @@ async def chat( user_state_data = user_state_storage[user_state] # Optimize the state's chat module if we've reached the optimization threshold - # TODO: read in examples from Honcho User Object examples = user_state_data["examples"] print(f"Num examples: {len(examples)}") session.user.update(metadata=user_state_storage) @@ -106,31 +104,19 @@ async def chat( for example in examples: optimizer_example = Example(**example).with_inputs("chat_input", "response", "assessment_dimension") optimizer_examples.append(optimizer_example) - print(isinstance(optimizer_example, Example)) - print(optimizer_example._store) - # examples = [dspy.Example(example).with_inputs("chat_input", "response", "assessment_dimension") for example in examples] - # print(examples) - # Splitting the examples list into train and validation sets - train_examples = examples[:-1] # All but the last item for training - val_examples = examples[-1:] # The last item for validation # Optimize chat module - optimizer = BootstrapFewShotWithRandomSearch(metric=metric, max_bootstrapped_demos=3, max_labeled_demos=3, num_candidate_programs=10, num_threads=4) - # optimizer = BootstrapFewShot(metric=metric, max_rounds=5) + optimizer = BootstrapFewShot(metric=metric, max_rounds=5) - compiled_chat_module = optimizer.compile(ChatWithThought(), trainset=train_examples, valset=val_examples) - # compiled_chat_module = optimizer.compile(user_chat_module, trainset=optimizer_examples) + compiled_chat_module = optimizer.compile(user_chat_module, trainset=optimizer_examples) print(f"COMPILED_CHAT_MODULE: {compiled_chat_module}") - # user_state_data["chat_module"] = compiled_chat_module.dump_state() user_state_storage[user_state][ "chat_module" ] = compiled_chat_module.dump_state() print(f"DUMPED_STATE: {compiled_chat_module.dump_state()}") user_chat_module = compiled_chat_module - # save to file for debugging purposes - # compiled_chat_module.save("module.json") # Update User in Honcho session.user.update(metadata=user_state_storage) diff --git a/example/discord/honcho-dspy-personas/response_metric.py b/example/discord/honcho-dspy-personas/response_metric.py index 3d9a5f1..09ffeb5 100644 --- a/example/discord/honcho-dspy-personas/response_metric.py +++ b/example/discord/honcho-dspy-personas/response_metric.py @@ -30,31 +30,4 @@ def metric(example, pred, trace=None): gpt4T.inspect_history(n=5) print("======== END OPTIMIZER HISTORY ========") - return is_appropriate - - - - - - -# def metric(example, ai_response, trace=None): -# """Assess the quality of a response along the specified dimension.""" -# example = dspy.Example(**example).with_inputs("chat_input", "ai_response", "assessment_dimension") - -# label = example.label -# chat_input = example.chat_input -# ai_response = example.ai_response -# assessment_dimension = example.assessment_dimension - -# with dspy.context(lm=gpt4T): -# assessment_result = dspy.Predict(MessageResponseAssess)( -# chat_input=chat_input, -# ai_response=ai_response, -# assessment_dimension=assessment_dimension -# ) - -# is_positive = assessment_result.assessment_answer.lower() == 'positive' - -# gpt4T.inspect_history(n=3) - -# return is_positive \ No newline at end of file + return is_appropriate \ No newline at end of file From b78d502181e3b2d098da1ddd56a15ab1444fdf71 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Mon, 26 Feb 2024 22:23:51 -0800 Subject: [PATCH 47/85] sentry --- api/poetry.lock | 68 +++++++++++++++++++++++++++++++++++++++++++++- api/pyproject.toml | 1 + api/src/main.py | 6 ++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/api/poetry.lock b/api/poetry.lock index 9a68f07..79eb9d0 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -665,6 +665,54 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "sentry-sdk" +version = "1.40.5" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, + {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, +] + +[package.dependencies] +certifi = "*" +fastapi = {version = ">=0.79.0", optional = true, markers = "extra == \"fastapi\""} +sqlalchemy = {version = ">=1.2", optional = true, markers = "extra == \"sqlalchemy\""} +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=5)"] + [[package]] name = "slowapi" version = "0.1.9" @@ -835,6 +883,24 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "uvicorn" version = "0.24.0.post1" @@ -954,4 +1020,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "49ec8fef5f21cb5bf2a8bbd007f016bd5bd88f8bdf604f3a820c59c07f984060" +content-hash = "0a08a262e9f5d9468378a67d845dd192728f4fe0ec9c000a9738bb7f37a9bf97" diff --git a/api/pyproject.toml b/api/pyproject.toml index e4423e5..9075c75 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -16,6 +16,7 @@ slowapi = "^0.1.8" fastapi-pagination = "^0.12.14" pgvector = "^0.2.5" openai = "^1.12.0" +sentry-sdk = {extras = ["fastapi", "sqlalchemy"], version = "^1.40.5"} [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/main.py b/api/src/main.py index 4784bc3..572303d 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,6 +1,7 @@ import uuid from typing import Optional, Sequence +import sentry_sdk from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.sqlalchemy import paginate @@ -13,6 +14,11 @@ from . import crud, models, schemas from .db import SessionLocal, engine +sentry_sdk.init( + dsn="https://a09c69bf7b871a295a8af9a03aea3a84@o4505873635606528.ingest.sentry.io/4505903820570624", + enable_tracing=True, +) + models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done app = FastAPI() From 6714d256c893a7f31ca65c216249502a803e62fa Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 28 Feb 2024 04:30:10 -0800 Subject: [PATCH 48/85] Open Telemetry --- api/.env.template | 22 +- api/poetry.lock | 541 ++++++++++++++++++++++++++++++++++++++++++++- api/pyproject.toml | 6 + api/src/main.py | 203 ++++++++++++++++- 4 files changed, 763 insertions(+), 9 deletions(-) diff --git a/api/.env.template b/api/.env.template index 04ee33b..65fd7d0 100644 --- a/api/.env.template +++ b/api/.env.template @@ -1,7 +1,23 @@ -DATABASE_TYPE=sqlite -CONNECTION_URI=sqlite:// - DATABASE_TYPE=postgres CONNECTION_URI=postgresql://testuser:testpwd@localhost:5432/honcho OPENAI_API_KEY= + +# Sentry +SENTRY_DSN= + +Open Telemetry + +OTEL_SERVICE_NAME=honcho +OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true +OTEL_PYTHON_LOG_CORRELATION=true + +OTEL_PYTHON_LOG_LEVEL= +OTEL_EXPORTER_OTLP_PROTOCOL= +OTEL_EXPORTER_OTLP_ENDPOINT= +OTEL_EXPORTER_OTLP_HEADERS= +OTEL_RESOURCE_ATTRIBUTES= + +DEBUG_LOG_OTEL_TO_PROVIDER=false +DEBUG_LOG_OTEL_TO_CONSOLE=true + diff --git a/api/poetry.lock b/api/poetry.lock index 79eb9d0..6d2e6c0 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -38,6 +38,24 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + [[package]] name = "certifi" version = "2024.2.2" @@ -50,6 +68,106 @@ files = [ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -176,6 +294,24 @@ sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"] sqlmodel = ["sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)"] tortoise = ["tortoise-orm (>=0.16.18,<0.21.0)"] +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -248,6 +384,73 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] +[[package]] +name = "grpcio" +version = "1.62.0" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.62.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:136ffd79791b1eddda8d827b607a6285474ff8a1a5735c4947b58c481e5e4271"}, + {file = "grpcio-1.62.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d6a56ba703be6b6267bf19423d888600c3f574ac7c2cc5e6220af90662a4d6b0"}, + {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4cd356211579043fce9f52acc861e519316fff93980a212c8109cca8f47366b6"}, + {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e803e9b58d8f9b4ff0ea991611a8d51b31c68d2e24572cd1fe85e99e8cc1b4f8"}, + {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4c04fe33039b35b97c02d2901a164bbbb2f21fb9c4e2a45a959f0b044c3512c"}, + {file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:95370c71b8c9062f9ea033a0867c4c73d6f0ff35113ebd2618171ec1f1e903e0"}, + {file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c912688acc05e4ff012c8891803659d6a8a8b5106f0f66e0aed3fb7e77898fa6"}, + {file = "grpcio-1.62.0-cp310-cp310-win32.whl", hash = "sha256:821a44bd63d0f04e33cf4ddf33c14cae176346486b0df08b41a6132b976de5fc"}, + {file = "grpcio-1.62.0-cp310-cp310-win_amd64.whl", hash = "sha256:81531632f93fece32b2762247c4c169021177e58e725494f9a746ca62c83acaa"}, + {file = "grpcio-1.62.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3fa15850a6aba230eed06b236287c50d65a98f05054a0f01ccedf8e1cc89d57f"}, + {file = "grpcio-1.62.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:36df33080cd7897623feff57831eb83c98b84640b016ce443305977fac7566fb"}, + {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7a195531828b46ea9c4623c47e1dc45650fc7206f8a71825898dd4c9004b0928"}, + {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab140a3542bbcea37162bdfc12ce0d47a3cda3f2d91b752a124cc9fe6776a9e2"}, + {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f9d6c3223914abb51ac564dc9c3782d23ca445d2864321b9059d62d47144021"}, + {file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fbe0c20ce9a1cff75cfb828b21f08d0a1ca527b67f2443174af6626798a754a4"}, + {file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38f69de9c28c1e7a8fd24e4af4264726637b72f27c2099eaea6e513e7142b47e"}, + {file = "grpcio-1.62.0-cp311-cp311-win32.whl", hash = "sha256:ce1aafdf8d3f58cb67664f42a617af0e34555fe955450d42c19e4a6ad41c84bd"}, + {file = "grpcio-1.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:eef1d16ac26c5325e7d39f5452ea98d6988c700c427c52cbc7ce3201e6d93334"}, + {file = "grpcio-1.62.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8aab8f90b2a41208c0a071ec39a6e5dbba16fd827455aaa070fec241624ccef8"}, + {file = "grpcio-1.62.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:62aa1659d8b6aad7329ede5d5b077e3d71bf488d85795db517118c390358d5f6"}, + {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0d7ae7fc7dbbf2d78d6323641ded767d9ec6d121aaf931ec4a5c50797b886532"}, + {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f359d635ee9428f0294bea062bb60c478a8ddc44b0b6f8e1f42997e5dc12e2ee"}, + {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d48e5b1f8f4204889f1acf30bb57c30378e17c8d20df5acbe8029e985f735c"}, + {file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:662d3df5314ecde3184cf87ddd2c3a66095b3acbb2d57a8cada571747af03873"}, + {file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92cdb616be44c8ac23a57cce0243af0137a10aa82234f23cd46e69e115071388"}, + {file = "grpcio-1.62.0-cp312-cp312-win32.whl", hash = "sha256:0b9179478b09ee22f4a36b40ca87ad43376acdccc816ce7c2193a9061bf35701"}, + {file = "grpcio-1.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:614c3ed234208e76991992342bab725f379cc81c7dd5035ee1de2f7e3f7a9842"}, + {file = "grpcio-1.62.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:7e1f51e2a460b7394670fdb615e26d31d3260015154ea4f1501a45047abe06c9"}, + {file = "grpcio-1.62.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:bcff647e7fe25495e7719f779cc219bbb90b9e79fbd1ce5bda6aae2567f469f2"}, + {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:56ca7ba0b51ed0de1646f1735154143dcbdf9ec2dbe8cc6645def299bb527ca1"}, + {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e84bfb2a734e4a234b116be208d6f0214e68dcf7804306f97962f93c22a1839"}, + {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c1488b31a521fbba50ae86423f5306668d6f3a46d124f7819c603979fc538c4"}, + {file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98d8f4eb91f1ce0735bf0b67c3b2a4fea68b52b2fd13dc4318583181f9219b4b"}, + {file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b3d3d755cfa331d6090e13aac276d4a3fb828bf935449dc16c3d554bf366136b"}, + {file = "grpcio-1.62.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a33f2bfd8a58a02aab93f94f6c61279be0f48f99fcca20ebaee67576cd57307b"}, + {file = "grpcio-1.62.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:5e709f7c8028ce0443bddc290fb9c967c1e0e9159ef7a030e8c21cac1feabd35"}, + {file = "grpcio-1.62.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:2f3d9a4d0abb57e5f49ed5039d3ed375826c2635751ab89dcc25932ff683bbb6"}, + {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:62ccb92f594d3d9fcd00064b149a0187c246b11e46ff1b7935191f169227f04c"}, + {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:921148f57c2e4b076af59a815467d399b7447f6e0ee10ef6d2601eb1e9c7f402"}, + {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f897b16190b46bc4d4aaf0a32a4b819d559a37a756d7c6b571e9562c360eed72"}, + {file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1bc8449084fe395575ed24809752e1dc4592bb70900a03ca42bf236ed5bf008f"}, + {file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81d444e5e182be4c7856cd33a610154fe9ea1726bd071d07e7ba13fafd202e38"}, + {file = "grpcio-1.62.0-cp38-cp38-win32.whl", hash = "sha256:88f41f33da3840b4a9bbec68079096d4caf629e2c6ed3a72112159d570d98ebe"}, + {file = "grpcio-1.62.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc2836cb829895ee190813446dce63df67e6ed7b9bf76060262c55fcd097d270"}, + {file = "grpcio-1.62.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fcc98cff4084467839d0a20d16abc2a76005f3d1b38062464d088c07f500d170"}, + {file = "grpcio-1.62.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:0d3dee701e48ee76b7d6fbbba18ba8bc142e5b231ef7d3d97065204702224e0e"}, + {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b7a6be562dd18e5d5bec146ae9537f20ae1253beb971c0164f1e8a2f5a27e829"}, + {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29cb592c4ce64a023712875368bcae13938c7f03e99f080407e20ffe0a9aa33b"}, + {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eda79574aec8ec4d00768dcb07daba60ed08ef32583b62b90bbf274b3c279f7"}, + {file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7eea57444a354ee217fda23f4b479a4cdfea35fb918ca0d8a0e73c271e52c09c"}, + {file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0e97f37a3b7c89f9125b92d22e9c8323f4e76e7993ba7049b9f4ccbe8bae958a"}, + {file = "grpcio-1.62.0-cp39-cp39-win32.whl", hash = "sha256:39cd45bd82a2e510e591ca2ddbe22352e8413378852ae814549c162cf3992a93"}, + {file = "grpcio-1.62.0-cp39-cp39-win_amd64.whl", hash = "sha256:b71c65427bf0ec6a8b48c68c17356cb9fbfc96b1130d20a07cb462f4e4dcdcd5"}, + {file = "grpcio-1.62.0.tar.gz", hash = "sha256:748496af9238ac78dcd98cce65421f1adce28c3979393e3609683fcd7f3880d7"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.62.0)"] + [[package]] name = "h11" version = "0.14.0" @@ -319,6 +522,26 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "importlib-metadata" +version = "6.11.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, + {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + [[package]] name = "importlib-resources" version = "6.1.1" @@ -430,6 +653,262 @@ typing-extensions = ">=4.7,<5" [package.extras] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +[[package]] +name = "opentelemetry-api" +version = "1.23.0" +description = "OpenTelemetry Python API" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_api-1.23.0-py3-none-any.whl", hash = "sha256:cc03ea4025353048aadb9c64919099663664672ea1c6be6ddd8fee8e4cd5e774"}, + {file = "opentelemetry_api-1.23.0.tar.gz", hash = "sha256:14a766548c8dd2eb4dfc349739eb4c3893712a0daa996e5dbf945f9da665da9d"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<7.0" + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.23.0" +description = "OpenTelemetry Collector Exporters" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp-1.23.0-py3-none-any.whl", hash = "sha256:92371fdc8d7803465a45801fe30cd8c522ef355a385b0a1d5346d32f77511ea2"}, + {file = "opentelemetry_exporter_otlp-1.23.0.tar.gz", hash = "sha256:4af8798f9bc3bddb92fcbb5b4aa9d0e955d962aa1d9bceaab08891c355a9f907"}, +] + +[package.dependencies] +opentelemetry-exporter-otlp-proto-grpc = "1.23.0" +opentelemetry-exporter-otlp-proto-http = "1.23.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.23.0" +description = "OpenTelemetry Protobuf encoding" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.23.0-py3-none-any.whl", hash = "sha256:2a9e7e9d5a8b026b572684b6b24dcdefcaa58613d5ce3d644130b0c373c056c1"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.23.0.tar.gz", hash = "sha256:35e4ea909e7a0b24235bd0aaf17fba49676527feb1823b46565ff246d5a1ab18"}, +] + +[package.dependencies] +opentelemetry-proto = "1.23.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.23.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.23.0-py3-none-any.whl", hash = "sha256:40f9e3e7761eb34f2a1001f4543028783ac26e2db27e420d5374f2cca0182dad"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.23.0.tar.gz", hash = "sha256:aa1a012eea5342bfef51fcf3f7f22601dcb0f0984a07ffe6025b2fbb6d91a2a9"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.23.0" +opentelemetry-proto = "1.23.0" +opentelemetry-sdk = ">=1.23.0,<1.24.0" + +[package.extras] +test = ["pytest-grpc"] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.23.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.23.0-py3-none-any.whl", hash = "sha256:ad853b58681df8efcb2cfc93be2b5fd86351c99ff4ab47dc917da384b8650d91"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.23.0.tar.gz", hash = "sha256:088eac2320f4a604e2d9ff71aced71fdae601ac6457005fb0303d6bbbf44e6ca"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.23.0" +opentelemetry-proto = "1.23.0" +opentelemetry-sdk = ">=1.23.0,<1.24.0" +requests = ">=2.7,<3.0" + +[package.extras] +test = ["responses (>=0.22.0,<0.25)"] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.44b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation-0.44b0-py3-none-any.whl", hash = "sha256:79560f386425176bcc60c59190064597096114c4a8e5154f1cb281bb4e47d2fc"}, + {file = "opentelemetry_instrumentation-0.44b0.tar.gz", hash = "sha256:8213d02d8c0987b9b26386ae3e091e0477d6331673123df736479322e1a50b48"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +setuptools = ">=16.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.44b0" +description = "ASGI instrumentation for OpenTelemetry" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_asgi-0.44b0-py3-none-any.whl", hash = "sha256:0d95c84a8991008c8a8ac35e15d43cc7768a5bb46f95f129e802ad2990d7c366"}, + {file = "opentelemetry_instrumentation_asgi-0.44b0.tar.gz", hash = "sha256:72d4d28ec7ccd551eac11edc5ae8cac3586c0a228467d6a95fad7b6d4edd597a"}, +] + +[package.dependencies] +asgiref = ">=3.0,<4.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.44b0" +opentelemetry-semantic-conventions = "0.44b0" +opentelemetry-util-http = "0.44b0" + +[package.extras] +instruments = ["asgiref (>=3.0,<4.0)"] +test = ["opentelemetry-instrumentation-asgi[instruments]", "opentelemetry-test-utils (==0.44b0)"] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.44b0" +description = "OpenTelemetry FastAPI Instrumentation" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_fastapi-0.44b0-py3-none-any.whl", hash = "sha256:4441482944bea6676816668d56deb94af990e8c6e9582c581047e5d84c91d3c9"}, + {file = "opentelemetry_instrumentation_fastapi-0.44b0.tar.gz", hash = "sha256:67ed10b93ad9d35238ae0be73cf8acbbb65a4a61fb7444d0aee5b0c492e294db"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.44b0" +opentelemetry-instrumentation-asgi = "0.44b0" +opentelemetry-semantic-conventions = "0.44b0" +opentelemetry-util-http = "0.44b0" + +[package.extras] +instruments = ["fastapi (>=0.58,<1.0)"] +test = ["httpx (>=0.22,<1.0)", "opentelemetry-instrumentation-fastapi[instruments]", "opentelemetry-test-utils (==0.44b0)", "requests (>=2.23,<3.0)"] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.44b0" +description = "OpenTelemetry Logging instrumentation" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_logging-0.44b0-py3-none-any.whl", hash = "sha256:e0c978ee54a95e60ae6c4fd3839bcf4f0a2a26ebed6d1b9d02d03ebf7847cd8f"}, + {file = "opentelemetry_instrumentation_logging-0.44b0.tar.gz", hash = "sha256:16b75964b5a3119bc143f3e67fbc9df238eb5ba457c485b81562197b1233b36f"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.44b0" + +[package.extras] +test = ["opentelemetry-test-utils (==0.44b0)"] + +[[package]] +name = "opentelemetry-instrumentation-sqlalchemy" +version = "0.44b0" +description = "OpenTelemetry SQLAlchemy instrumentation" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_sqlalchemy-0.44b0-py3-none-any.whl", hash = "sha256:078d299e9a3788369e2f6644def8c7d45f09cf65c6215d5121a5515fdd9ca118"}, + {file = "opentelemetry_instrumentation_sqlalchemy-0.44b0.tar.gz", hash = "sha256:9341603aa0314cbc9325a5997ab9733ff67fbd38a475376753f21dd8d3c67ed7"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.44b0" +opentelemetry-semantic-conventions = "0.44b0" +packaging = ">=21.0" +wrapt = ">=1.11.2" + +[package.extras] +instruments = ["sqlalchemy"] +test = ["opentelemetry-instrumentation-sqlalchemy[instruments]", "opentelemetry-sdk (>=1.12,<2.0)", "pytest"] + +[[package]] +name = "opentelemetry-proto" +version = "1.23.0" +description = "OpenTelemetry Python Proto" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_proto-1.23.0-py3-none-any.whl", hash = "sha256:4c017deca052cb287a6003b7c989ed8b47af65baeb5d57ebf93dde0793f78509"}, + {file = "opentelemetry_proto-1.23.0.tar.gz", hash = "sha256:e6aaf8b7ace8d021942d546161401b83eed90f9f2cc6f13275008cea730e4651"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.23.0" +description = "OpenTelemetry Python SDK" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_sdk-1.23.0-py3-none-any.whl", hash = "sha256:a93c96990ac0f07c6d679e2f1015864ff7a4f5587122dd5af968034436efb1fd"}, + {file = "opentelemetry_sdk-1.23.0.tar.gz", hash = "sha256:9ddf60195837b59e72fd2033d6a47e2b59a0f74f0ec37d89387d89e3da8cab7f"}, +] + +[package.dependencies] +opentelemetry-api = "1.23.0" +opentelemetry-semantic-conventions = "0.44b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.44b0" +description = "OpenTelemetry Semantic Conventions" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_semantic_conventions-0.44b0-py3-none-any.whl", hash = "sha256:7c434546c9cbd797ab980cc88bf9ff3f4a5a28f941117cad21694e43d5d92019"}, + {file = "opentelemetry_semantic_conventions-0.44b0.tar.gz", hash = "sha256:2e997cb28cd4ca81a25a9a43365f593d0c2b76be0685015349a89abdf1aa4ffa"}, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.44b0" +description = "Web util for OpenTelemetry" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_util_http-0.44b0-py3-none-any.whl", hash = "sha256:ff018ab6a2fa349537ff21adcef99a294248b599be53843c44f367aef6bccea5"}, + {file = "opentelemetry_util_http-0.44b0.tar.gz", hash = "sha256:75896dffcbbeb5df5429ad4526e22307fc041a27114e0c5bfd90bb219381e68f"}, +] + [[package]] name = "packaging" version = "23.2" @@ -456,6 +935,27 @@ files = [ [package.dependencies] numpy = "*" +[[package]] +name = "protobuf" +version = "4.25.3" +description = "" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, +] + [[package]] name = "psycopg2-binary" version = "2.9.9" @@ -665,6 +1165,28 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "main" +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 = "sentry-sdk" version = "1.40.5" @@ -713,6 +1235,23 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "69.1.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "slowapi" version = "0.1.9" @@ -1020,4 +1559,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0a08a262e9f5d9468378a67d845dd192728f4fe0ec9c000a9738bb7f37a9bf97" +content-hash = "56189c71bc0a611428af6736b46e0f1d009a55885ca232e98dfe436895a0aa3b" diff --git a/api/pyproject.toml b/api/pyproject.toml index 9075c75..5b77fab 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -17,6 +17,12 @@ fastapi-pagination = "^0.12.14" pgvector = "^0.2.5" openai = "^1.12.0" sentry-sdk = {extras = ["fastapi", "sqlalchemy"], version = "^1.40.5"} +opentelemetry-instrumentation-fastapi = "^0.44b0" +opentelemetry-api = "^1.23.0" +opentelemetry-sdk = "^1.23.0" +opentelemetry-exporter-otlp = "^1.23.0" +opentelemetry-instrumentation-sqlalchemy = "^0.44b0" +opentelemetry-instrumentation-logging = "^0.44b0" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/main.py b/api/src/main.py index 572303d..df843ab 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,27 +1,204 @@ +import json +import logging +import os import uuid + +# from logging import LogRecord from typing import Optional, Sequence import sentry_sdk -from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request +from fastapi import ( + APIRouter, + Depends, + FastAPI, + HTTPException, + Request, +) +from fastapi.responses import PlainTextResponse from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.sqlalchemy import paginate +from opentelemetry import trace +from opentelemetry._logs import ( + SeverityNumber, + get_logger, + get_logger_provider, + set_logger_provider, + std_to_otel, +) + +# from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +# from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.exporter.otlp.proto.http._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter, +) +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from opentelemetry.instrumentation.logging import LoggingInstrumentor +from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import ( + BatchLogRecordProcessor, + ConsoleLogExporter, + SimpleLogRecordProcessor, +) +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import ( + BatchSpanProcessor, + ConsoleSpanExporter, + SimpleSpanProcessor, +) from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address from sqlalchemy.orm import Session +from starlette.exceptions import HTTPException as StarletteHTTPException from . import crud, models, schemas from .db import SessionLocal, engine +# Otel Setup + +DEBUG_LOG_OTEL_TO_PROVIDER = ( + os.getenv("DEBUG_LOG_OTEL_TO_PROVIDER", "False").lower() == "true" +) +DEBUG_LOG_OTEL_TO_CONSOLE = ( + os.getenv("DEBUG_LOG_OTEL_TO_CONSOLE", "False").lower() == "true" +) + +# logging.getLogger().setLevel(logging.WARNING) + +# class FormattedLoggingHandler(LoggingHandler): +# def emit(self, record: logging.LogRecord) -> None: +# msg = self.format(record) +# record.msg = msg +# record.args = None +# self._logger.emit(self._translate(record)) + + +def otel_get_env_vars(): + otel_http_headers = {} + try: + decoded_http_headers = os.getenv("OTEL_EXPORTER_OTLP_HEADERS", "") + key_values = decoded_http_headers.split(",") + for key_value in key_values: + key, value = key_value.split("=") + otel_http_headers[key] = value + + except Exception as e: + print(f"Error parsing OTEL_ENDPOINT_HTTP_HEADERS: {str(e)}") + otel_endpoint_url = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", None) + + return otel_endpoint_url, otel_http_headers + + +def otel_trace_init(): + trace.set_tracer_provider( + TracerProvider( + resource=Resource.create({}), + ), + ) + if DEBUG_LOG_OTEL_TO_PROVIDER: + otel_endpoint_url, otel_http_headers = otel_get_env_vars() + if otel_endpoint_url is not None: + otel_endpoint_url = otel_endpoint_url + "/v1/traces" + otlp_span_exporter = OTLPSpanExporter( + endpoint=otel_endpoint_url, headers=otel_http_headers + ) + trace.get_tracer_provider().add_span_processor( + BatchSpanProcessor(otlp_span_exporter) + ) + if DEBUG_LOG_OTEL_TO_CONSOLE: + trace.get_tracer_provider().add_span_processor( + SimpleSpanProcessor(ConsoleSpanExporter()) + ) + + +def otel_logging_init(): + # ------------Logging + # Set logging level + # CRITICAL = 50 + # ERROR = 40 + # WARNING = 30 + # INFO = 20 + # DEBUG = 10 + # NOTSET = 0 + # default = WARNING + log_level = str(os.getenv("OTEL_PYTHON_LOG_LEVEL", "INFO")).upper() + if log_level == "CRITICAL": + log_level = logging.CRITICAL + print(f"Using log level: CRITICAL / {log_level}") + elif log_level == "ERROR": + log_level = logging.ERROR + print(f"Using log level: ERROR / {log_level}") + elif log_level == "WARNING": + log_level = logging.WARNING + print(f"Using log level: WARNING / {log_level}") + elif log_level == "INFO": + log_level = logging.INFO + print(f"Using log level: INFO / {log_level}") + elif log_level == "DEBUG": + log_level = logging.DEBUG + print(f"Using log level: DEBUG / {log_level}") + elif log_level == "NOTSET": + log_level = logging.INFO + print(f"Using log level: NOTSET / {log_level}") + # ------------ Opentelemetry logging initialization + + logger_provider = LoggerProvider(resource=Resource.create({})) + set_logger_provider(logger_provider) + if DEBUG_LOG_OTEL_TO_CONSOLE: + console_log_exporter = ConsoleLogExporter() + logger_provider.add_log_record_processor( + SimpleLogRecordProcessor(console_log_exporter) + ) + if DEBUG_LOG_OTEL_TO_PROVIDER: + otel_endpoint_url, otel_http_headers = otel_get_env_vars() + if otel_endpoint_url is not None: + otel_endpoint_url = otel_endpoint_url + "/v1/logs" + otlp_log_exporter = OTLPLogExporter( + endpoint=otel_endpoint_url, headers=otel_http_headers + ) + logger_provider.add_log_record_processor( + BatchLogRecordProcessor(otlp_log_exporter) + ) + + # otel_log_handler = FormattedLoggingHandler(logger_provider=logger_provider) + otel_log_handler = LoggingHandler( + level=logging.NOTSET, logger_provider=logger_provider + ) + + otel_log_handler.setLevel(log_level) + # This has to be called first before logger.getLogger().addHandler() so that it can call logging.basicConfig first to set the logging format + # based on the environment variable OTEL_PYTHON_LOG_FORMAT + LoggingInstrumentor(log_level=log_level).instrument(log_level=log_level) + # logFormatter = logging.Formatter(os.getenv("OTEL_PYTHON_LOG_FORMAT", None)) + # otel_log_handler.setFormatter(logFormatter) + logging.getLogger().addHandler(otel_log_handler) + + +# Instrument SQLAlchemy +otel_trace_init() +otel_logging_init() + +SQLAlchemyInstrumentor().instrument(engine=engine) + +# Sentry Setup + sentry_sdk.init( - dsn="https://a09c69bf7b871a295a8af9a03aea3a84@o4505873635606528.ingest.sentry.io/4505903820570624", + dsn=os.getenv("SENTRY_DSN"), enable_tracing=True, ) models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done + app = FastAPI() +FastAPIInstrumentor().instrument_app(app) + router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") @@ -46,6 +223,22 @@ def get_db(): db.close() +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request, exc): + current_span = trace.get_current_span() + if (current_span is not None) and (current_span.is_recording()): + current_span.set_attributes( + { + "http.status_text": str(exc.detail), + "otel.status_description": f"{exc.status_code} / {str(exc.detail)}", + "otel.status_code": "ERROR", + } + ) + return PlainTextResponse( + json.dumps({"detail": str(exc.detail)}), status_code=exc.status_code + ) + + ######################################################## # App Routes ######################################################## @@ -70,10 +263,10 @@ def get_app( return app -@app.get("/apps/name/{app_name}", response_model=schemas.App) +@app.get("/apps/name/{name}", response_model=schemas.App) def get_app_by_name( request: Request, - app_name: str, + name: str, db: Session = Depends(get_db), ): """Get an App by Name @@ -85,7 +278,7 @@ def get_app_by_name( schemas.App: App object """ - app = crud.get_app_by_name(db, app_name=app_name) + app = crud.get_app_by_name(db, name=name) if app is None: raise HTTPException(status_code=404, detail="App not found") return app From da5f5557e275a15dc0285bb5f550cc528e63caa1 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:51:17 -0800 Subject: [PATCH 49/85] optional logging with environment variables --- api/.env.template | 9 +++++++-- api/src/main.py | 32 +++++++++++++++----------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/api/.env.template b/api/.env.template index 65fd7d0..afa83ba 100644 --- a/api/.env.template +++ b/api/.env.template @@ -3,10 +3,15 @@ CONNECTION_URI=postgresql://testuser:testpwd@localhost:5432/honcho OPENAI_API_KEY= -# Sentry +# Logging + +OPENTELEMETRY_ENABLED=false # Set to true to enable OpenTelemetry logging and tracing +SENTRY_ENABLED=false # Set to true to enable Sentry logging and tracing + +## Sentry SENTRY_DSN= -Open Telemetry +## Open Telemetry OTEL_SERVICE_NAME=honcho OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true diff --git a/api/src/main.py b/api/src/main.py index df843ab..087a488 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -69,15 +69,6 @@ os.getenv("DEBUG_LOG_OTEL_TO_CONSOLE", "False").lower() == "true" ) -# logging.getLogger().setLevel(logging.WARNING) - -# class FormattedLoggingHandler(LoggingHandler): -# def emit(self, record: logging.LogRecord) -> None: -# msg = self.format(record) -# record.msg = msg -# record.args = None -# self._logger.emit(self._translate(record)) - def otel_get_env_vars(): otel_http_headers = {} @@ -180,24 +171,31 @@ def otel_logging_init(): logging.getLogger().addHandler(otel_log_handler) +OPENTELEMTRY_ENABLED = os.getenv("OPENTELEMETRY_ENABLED", "False").lower() == "true" + # Instrument SQLAlchemy -otel_trace_init() -otel_logging_init() +if OPENTELEMTRY_ENABLED: + otel_trace_init() + otel_logging_init() -SQLAlchemyInstrumentor().instrument(engine=engine) + SQLAlchemyInstrumentor().instrument(engine=engine) # Sentry Setup -sentry_sdk.init( - dsn=os.getenv("SENTRY_DSN"), - enable_tracing=True, -) +SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", "False").lower() == "true" +if SENTRY_ENABLED: + sentry_sdk.init( + dsn=os.getenv("SENTRY_DSN"), + enable_tracing=True, + ) models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done app = FastAPI() -FastAPIInstrumentor().instrument_app(app) + +if OPENTELEMTRY_ENABLED: + FastAPIInstrumentor().instrument_app(app) router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") From f40ec078f3aa9edad877954cf02305efbbfd3c50 Mon Sep 17 00:00:00 2001 From: Ayush Paul Date: Wed, 6 Mar 2024 01:31:54 -0500 Subject: [PATCH 50/85] add actions again? (#29) * add postgres * add openai key * readd coverage * desyncify and add detailed coverage --- .github/workflows/run_coverage.yml | 47 +++++++++++++++ .github/workflows/run_tests.yml | 46 +++++++++++++++ sdk/poetry.lock | 92 +++++++++++++++++++++++------- sdk/pyproject.toml | 1 + 4 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/run_coverage.yml create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml new file mode 100644 index 0000000..3d31c97 --- /dev/null +++ b/.github/workflows/run_coverage.yml @@ -0,0 +1,47 @@ +name: Run Coverage +on: [pull_request] +jobs: + test: + permissions: + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install poetry + run: | + pip install poetry + - name: Start Database + run: | + cd api/local + docker compose up --wait + cd ../.. + - name: Start Server + run: | + cd api + poetry install --no-root + poetry run uvicorn src.main:app & + sleep 5 + cd .. + env: + DATABASE_TYPE: postgres + CONNECTION_URI: postgresql://testuser:testpwd@localhost:5432/honcho + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + - name: Run Tests + run: | + cd sdk + poetry install + poetry run coverage run -m pytest + poetry run coverage report --format=markdown > coverage.md + cd .. + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + with: + recreate: true + path: sdk/coverage.md + - name: Stop Server + run: | + kill $(jobs -p) || true diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..b78097a --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,46 @@ +name: Run Tests +on: [push, pull_request] +jobs: + test: + permissions: + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install poetry + run: | + pip install poetry + - name: Start Database + run: | + cd api/local + docker compose up --wait + cd ../.. + - name: Start Server + run: | + cd api + poetry install --no-root + poetry run uvicorn src.main:app & + sleep 5 + cd .. + env: + DATABASE_TYPE: postgres + CONNECTION_URI: postgresql://testuser:testpwd@localhost:5432/honcho + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + - name: Run Tests + run: | + cd sdk + poetry install + poetry run pytest + cd .. + - name: Stop Database + run: | + cd api/local + docker compose down + cd ../.. + - name: Stop Server + run: | + kill $(jobs -p) || true diff --git a/sdk/poetry.lock b/sdk/poetry.lock index e450ca8..3b53388 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -27,7 +26,6 @@ trio = ["trio (>=0.23)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -39,7 +37,6 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -47,11 +44,74 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.4.3" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, +] + +[package.extras] +toml = ["tomli"] + [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -66,7 +126,6 @@ test = ["pytest (>=6)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -78,7 +137,6 @@ files = [ name = "httpcore" version = "1.0.2" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -93,14 +151,13 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -111,21 +168,20 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = ">=1.0.0,<2.0.0" +httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -137,7 +193,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -149,7 +204,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -161,7 +215,6 @@ files = [ name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -177,7 +230,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -200,7 +252,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.23.4" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -219,7 +270,6 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -231,7 +281,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -243,7 +292,6 @@ files = [ name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -254,4 +302,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "6ccea662fa5a5bae88618123d5d05e0d4955c234b7e1a688d2fae2f90cd9f7f8" +content-hash = "e318390f6947b47bc17d2785385ad01fed4496f3f4e09357ea7cabb7b1e401d2" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 495df46..f548805 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -14,6 +14,7 @@ httpx = "^0.26.0" [tool.poetry.group.test.dependencies] pytest = "^7.4.4" pytest-asyncio = "^0.23.4" +coverage = "^7.4.3" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example From c1c35ded207029774ca93e96a303479e33722a9e Mon Sep 17 00:00:00 2001 From: Ayush Paul Date: Wed, 6 Mar 2024 01:34:15 -0500 Subject: [PATCH 51/85] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20update=20st?= =?UTF-8?q?art=20script=20in=20VS=20Code=20to=20include=20poetry=20install?= =?UTF-8?q?=20--no-root=20before=20running=20uvicorn=20(#33)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/.vscode/tasks.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/.vscode/tasks.json b/api/.vscode/tasks.json index 8113cb0..8e9b1da 100644 --- a/api/.vscode/tasks.json +++ b/api/.vscode/tasks.json @@ -4,7 +4,7 @@ { "label": "start", "type": "shell", - "command": "poetry run uvicorn src.main:app --reload", + "command": "poetry install --no-root && poetry run uvicorn src.main:app --reload", "group": "none", "presentation": { "reveal": "always", @@ -12,7 +12,8 @@ }, "runOptions": { "runOn": "folderOpen" - } + }, + "problemMatcher": [] } ] } From 8be7a9e937d95a8f3c11cb5cceb1f079604d281d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:36:09 -0800 Subject: [PATCH 52/85] Refactored code but need to tweak asyncpg --- api/poetry.lock | 400 ++++++++++++++++++++++++++------------------- api/pyproject.toml | 3 + api/src/crud.py | 257 ++++++++++++++++------------- api/src/db.py | 20 ++- api/src/main.py | 280 +++++++++++++++++-------------- 5 files changed, 545 insertions(+), 415 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index 6d2e6c0..f37eba4 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -56,6 +56,76 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +category = "main" +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 = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" +category = "main" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + [[package]] name = "certifi" version = "2024.2.2" @@ -262,14 +332,14 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "fastapi-pagination" -version = "0.12.16" +version = "0.12.19" description = "FastAPI pagination" category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "fastapi_pagination-0.12.16-py3-none-any.whl", hash = "sha256:1179edea6c8d3b6b70d3f373047470b08a948bfef817ff8e722d46969f87998c"}, - {file = "fastapi_pagination-0.12.16.tar.gz", hash = "sha256:3c74d77d42451518e9d85aa1c3633b725f42d9746d68d1e9267f6c0493750497"}, + {file = "fastapi_pagination-0.12.19-py3-none-any.whl", hash = "sha256:67838b21e2f62fae739117d130f8153a362d1fc266c2d738543e71d446618635"}, + {file = "fastapi_pagination-0.12.19.tar.gz", hash = "sha256:d947d0c91589dc2fc99a15409707eb24272970a044489d21363641af03516fd7"}, ] [package.dependencies] @@ -278,13 +348,13 @@ pydantic = ">=1.9.1" typing-extensions = ">=4.8.0,<5.0.0" [package.extras] -all = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)", "beanie (>=1.11.9,<2.0.0)", "bunnet (>=1.1.0,<2.0.0)", "databases (>=0.6.0)", "django (<5.0.0)", "mongoengine (>=0.23.1,<0.28.0)", "motor (>=2.5.1,<4.0.0)", "orm (>=0.3.1)", "ormar (>=0.11.2)", "piccolo (>=0.89,<0.122)", "pony (>=0.7.16,<0.8.0)", "scylla-driver (>=3.25.6,<4.0.0)", "sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)", "tortoise-orm (>=0.16.18,<0.21.0)"] +all = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)", "beanie (>=1.25.0)", "bunnet (>=1.1.0,<2.0.0)", "databases (>=0.6.0)", "django (<5.0.0)", "mongoengine (>=0.23.1,<0.29.0)", "motor (>=2.5.1,<4.0.0)", "orm (>=0.3.1)", "ormar (>=0.11.2)", "piccolo (>=0.89,<0.122)", "pony (>=0.7.16,<0.8.0)", "scylla-driver (>=3.25.6,<4.0.0)", "sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)", "tortoise-orm (>=0.16.18,<0.21.0)"] asyncpg = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)"] -beanie = ["beanie (>=1.11.9,<2.0.0)"] +beanie = ["beanie (>=1.25.0)"] bunnet = ["bunnet (>=1.1.0,<2.0.0)"] databases = ["databases (>=0.6.0)"] django = ["databases (>=0.6.0)", "django (<5.0.0)"] -mongoengine = ["mongoengine (>=0.23.1,<0.28.0)"] +mongoengine = ["mongoengine (>=0.23.1,<0.29.0)"] motor = ["motor (>=2.5.1,<4.0.0)"] orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"] ormar = ["ormar (>=0.11.2)"] @@ -465,14 +535,14 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -483,18 +553,18 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (>=1.0.0,<2.0.0)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] @@ -544,14 +614,14 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs [[package]] name = "importlib-resources" -version = "6.1.1" +version = "6.1.2" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, + {file = "importlib_resources-6.1.2-py3-none-any.whl", hash = "sha256:9a0a862501dc38b68adebc82970140c9e4209fc99601782925178f8386339938"}, + {file = "importlib_resources-6.1.2.tar.gz", hash = "sha256:308abf8474e2dba5f867d279237cd4076482c3de7104a40b41426370e891549b"}, ] [package.dependencies] @@ -559,7 +629,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] [[package]] name = "limits" @@ -631,14 +701,14 @@ files = [ [[package]] name = "openai" -version = "1.12.0" +version = "1.13.3" description = "The official Python library for the openai API" category = "main" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, - {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, + {file = "openai-1.13.3-py3-none-any.whl", hash = "sha256:5769b62abd02f350a8dd1a3a242d8972c947860654466171d60fb0972ae0a41c"}, + {file = "openai-1.13.3.tar.gz", hash = "sha256:ff6c6b3bc7327e715e4b3592a923a5a1c7519ff5dd764a83d69f633d49e77a7b"}, ] [package.dependencies] @@ -1040,19 +1110,19 @@ files = [ [[package]] name = "pydantic" -version = "2.6.1" +version = "2.6.3" description = "Data validation using Python type hints" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, + {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, + {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -1060,91 +1130,91 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.2" +version = "2.16.3" description = "" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] @@ -1189,14 +1259,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sentry-sdk" -version = "1.40.5" +version = "1.40.6" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, - {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, + {file = "sentry-sdk-1.40.6.tar.gz", hash = "sha256:f143f3fb4bb57c90abef6e2ad06b5f6f02b2ca13e4060ec5c0549c7a9ccce3fa"}, + {file = "sentry_sdk-1.40.6-py2.py3-none-any.whl", hash = "sha256:becda09660df63e55f307570e9817c664392655a7328bbc414b507e9cb874c67"}, ] [package.dependencies] @@ -1224,7 +1294,7 @@ huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -1272,73 +1342,73 @@ redis = ["redis (>=3.4.1,<4.0.0)"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" category = "main" 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"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.28" description = "Database Abstraction Library" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -1412,14 +1482,14 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -1559,4 +1629,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "56189c71bc0a611428af6736b46e0f1d009a55885ca232e98dfe436895a0aa3b" +content-hash = "034c3672b8736b9c809f70c7a3a2261a7e79db8a21b57c1bfde783e5a446bd16" diff --git a/api/pyproject.toml b/api/pyproject.toml index 5b77fab..a03e4b3 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -23,6 +23,7 @@ opentelemetry-sdk = "^1.23.0" opentelemetry-exporter-otlp = "^1.23.0" opentelemetry-instrumentation-sqlalchemy = "^0.44b0" opentelemetry-instrumentation-logging = "^0.44b0" +asyncpg = "^0.29.0" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example @@ -40,6 +41,8 @@ select = [ # isort "I", ] +[tool.ruff.flake8-bugbear] +extend-immutable-calls = ["fastapi.Depends"] [build-system] diff --git a/api/src/crud.py b/api/src/crud.py index 10f24e8..27dc56f 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -5,8 +5,9 @@ from openai import OpenAI from sqlalchemy import Select, select from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session +from sqlalchemy.ext.asyncio import AsyncSession +# from sqlalchemy.orm import Session from . import models, schemas openai_client = OpenAI() @@ -16,32 +17,36 @@ ######################################################## -def get_app(db: Session, app_id: uuid.UUID) -> Optional[models.App]: +async def get_app(db: AsyncSession, app_id: uuid.UUID) -> Optional[models.App]: stmt = select(models.App).where(models.App.id == app_id) - app = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + app = result.scalar_one_or_none() return app -def get_app_by_name(db: Session, name: str) -> Optional[models.App]: +async def get_app_by_name(db: AsyncSession, name: str) -> Optional[models.App]: stmt = select(models.App).where(models.App.name == name) - app = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + app = result.scalar_one_or_none() return app -# def get_apps(db: Session) -> Sequence[models.App]: +# def get_apps(db: AsyncSession) -> Sequence[models.App]: # return db.query(models.App).all() -def create_app(db: Session, app: schemas.AppCreate) -> models.App: +async def create_app(db: AsyncSession, app: schemas.AppCreate) -> models.App: honcho_app = models.App(name=app.name, h_metadata=app.metadata) db.add(honcho_app) - db.commit() - db.refresh(honcho_app) + await db.commit() + await db.refresh(honcho_app) return honcho_app -def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models.App: - honcho_app = get_app(db, app_id) +async def update_app( + db: AsyncSession, app_id: uuid.UUID, app: schemas.AppUpdate +) -> models.App: + honcho_app = await get_app(db, app_id) if honcho_app is None: raise ValueError("App not found") if app.name is not None: @@ -49,12 +54,12 @@ def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models if app.metadata is not None: honcho_app.h_metadata = app.metadata - db.commit() - db.refresh(honcho_app) + await db.commit() + await db.refresh(honcho_app) return honcho_app -# def delete_app(db: Session, app_id: uuid.UUID) -> bool: +# def delete_app(db: AsyncSession, app_id: uuid.UUID) -> bool: # existing_app = get_app(db, app_id) # if existing_app is None: # return False @@ -68,8 +73,8 @@ def update_app(db: Session, app_id: uuid.UUID, app: schemas.AppUpdate) -> models ######################################################## -def create_user( - db: Session, app_id: uuid.UUID, user: schemas.UserCreate +async def create_user( + db: AsyncSession, app_id: uuid.UUID, user: schemas.UserCreate ) -> models.User: honcho_user = models.User( app_id=app_id, @@ -77,36 +82,40 @@ def create_user( h_metadata=user.metadata, ) db.add(honcho_user) - db.commit() - db.refresh(honcho_user) + await db.commit() + await db.refresh(honcho_user) return honcho_user -def get_user( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID +async def get_user( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID ) -> Optional[models.User]: stmt = ( select(models.User) .where(models.User.app_id == app_id) .where(models.User.id == user_id) ) - user = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + user = result.scalar_one_or_none() return user -def get_user_by_name( - db: Session, app_id: uuid.UUID, name: str +async def get_user_by_name( + db: AsyncSession, app_id: uuid.UUID, name: str ) -> Optional[models.User]: stmt = ( select(models.User) .where(models.User.app_id == app_id) .where(models.User.name == name) ) - user = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + user = result.scalar_one_or_none() return user -def get_users(db: Session, app_id: uuid.UUID, reverse: bool = False) -> Select: +async def get_users( + db: AsyncSession, app_id: uuid.UUID, reverse: bool = False +) -> Select: stmt = select(models.User).where(models.User.app_id == app_id) if reverse: stmt = stmt.order_by(models.User.created_at.desc()) @@ -116,10 +125,10 @@ def get_users(db: Session, app_id: uuid.UUID, reverse: bool = False) -> Select: return stmt -def update_user( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate +async def update_user( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate ) -> models.User: - honcho_user = get_user(db, app_id, user_id) + honcho_user = await get_user(db, app_id, user_id) if honcho_user is None: raise ValueError("User not found") if user.name is not None: @@ -127,12 +136,12 @@ def update_user( if user.metadata is not None: honcho_user.h_metadata = user.metadata - db.commit() - db.refresh(honcho_user) + await db.commit() + await db.refresh(honcho_user) return honcho_user -# def delete_user(db: Session, app_id: uuid.UUID, user_id: uuid.UUID) -> bool: +# def delete_user(db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID) -> bool: # existing_user = get_user(db, app_id, user_id) # if existing_user is None: # return False @@ -145,8 +154,8 @@ def update_user( ######################################################## -def get_session( - db: Session, +async def get_session( + db: AsyncSession, app_id: uuid.UUID, session_id: uuid.UUID, user_id: Optional[uuid.UUID] = None, @@ -159,12 +168,13 @@ def get_session( ) if user_id is not None: stmt = stmt.where(models.Session.user_id == user_id) - session = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + session = result.scalar_one_or_none() return session -def get_sessions( - db: Session, +async def get_sessions( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, location_id: Optional[str] = None, @@ -192,8 +202,11 @@ def get_sessions( return stmt -def create_session( - db: Session, session: schemas.SessionCreate, app_id: uuid.UUID, user_id: uuid.UUID +async def create_session( + db: AsyncSession, + session: schemas.SessionCreate, + app_id: uuid.UUID, # TODO check if app id is associated with the right user + user_id: uuid.UUID, ) -> models.Session: honcho_session = models.Session( user_id=user_id, @@ -201,19 +214,19 @@ def create_session( h_metadata=session.metadata, ) db.add(honcho_session) - db.commit() - db.refresh(honcho_session) + await db.commit() + await db.refresh(honcho_session) return honcho_session -def update_session( - db: Session, +async def update_session( + db: AsyncSession, session: schemas.SessionUpdate, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, ) -> bool: - honcho_session = get_session( + honcho_session = await get_session( db, app_id=app_id, session_id=session_id, user_id=user_id ) if honcho_session is None: @@ -222,13 +235,13 @@ def update_session( session.metadata is not None ): # Need to explicitly be there won't make it empty by default honcho_session.h_metadata = session.metadata - db.commit() - db.refresh(honcho_session) + await db.commit() + await db.refresh(honcho_session) return honcho_session -def delete_session( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID +async def delete_session( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID ) -> bool: stmt = ( select(models.Session) @@ -237,11 +250,12 @@ def delete_session( .where(models.User.app_id == app_id) .where(models.Session.user_id == user_id) ) - honcho_session = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + honcho_session = result.scalar_one_or_none() if honcho_session is None: return False honcho_session.is_active = False - db.commit() + await db.commit() return True @@ -250,8 +264,8 @@ def delete_session( ######################################################## -def create_message( - db: Session, +async def create_message( + db: AsyncSession, message: schemas.MessageCreate, app_id: uuid.UUID, user_id: uuid.UUID, @@ -269,13 +283,13 @@ def create_message( content=message.content, ) db.add(honcho_message) - db.commit() - db.refresh(honcho_message) + await db.commit() + await db.refresh(honcho_message) return honcho_message -def get_messages( - db: Session, +async def get_messages( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, @@ -299,8 +313,8 @@ def get_messages( return stmt -def get_message( - db: Session, +async def get_message( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, @@ -316,7 +330,8 @@ def get_message( .where(models.Message.session_id == session_id) .where(models.Message.id == message_id) ) - return db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + return result.scalar_one_or_none() ######################################################## @@ -324,8 +339,8 @@ def get_message( ######################################################## -def create_metamessage( - db: Session, +async def create_metamessage( + db: AsyncSession, metamessage: schemas.MetamessageCreate, app_id: uuid.UUID, user_id: uuid.UUID, @@ -348,13 +363,13 @@ def create_metamessage( ) db.add(honcho_metamessage) - db.commit() - db.refresh(honcho_metamessage) + await db.commit() + await db.refresh(honcho_metamessage) return honcho_metamessage -def get_metamessages( - db: Session, +async def get_metamessages( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, @@ -387,8 +402,8 @@ def get_metamessages( return stmt -def get_metamessage( - db: Session, +async def get_metamessage( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, @@ -407,7 +422,8 @@ def get_metamessage( .where(models.Metamessage.message_id == message_id) .where(models.Metamessage.id == metamessage_id) ) - return db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + return result.scalar_one_or_none() ######################################################## @@ -417,8 +433,11 @@ def get_metamessage( # Should be very similar to the session methods -def get_collections( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, reverse: Optional[bool] = False +async def get_collections( + db: AsyncSession, + app_id: uuid.UUID, + user_id: uuid.UUID, + reverse: Optional[bool] = False, ) -> Select: """Get a distinct list of the names of collections associated with a user""" stmt = ( @@ -436,8 +455,8 @@ def get_collections( return stmt -def get_collection_by_id( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID +async def get_collection_by_id( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID ) -> Optional[models.Collection]: stmt = ( select(models.Collection) @@ -446,12 +465,13 @@ def get_collection_by_id( .where(models.User.id == user_id) .where(models.Collection.id == collection_id) ) - collection = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + collection = result.scalar_one_or_none() return collection -def get_collection_by_name( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, name: str +async def get_collection_by_name( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, name: str ) -> Optional[models.Collection]: stmt = ( select(models.Collection) @@ -460,12 +480,13 @@ def get_collection_by_name( .where(models.User.id == user_id) .where(models.Collection.name == name) ) - collection = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + collection = result.scalar_one_or_none() return collection -def create_collection( - db: Session, +async def create_collection( + db: AsyncSession, collection: schemas.CollectionCreate, app_id: uuid.UUID, user_id: uuid.UUID, @@ -476,38 +497,38 @@ def create_collection( ) try: db.add(honcho_collection) - db.commit() + await db.commit() except IntegrityError: - db.rollback() - raise ValueError("Collection already exists") - db.refresh(honcho_collection) + await db.rollback() + raise ValueError("Collection already exists") from None + await db.refresh(honcho_collection) return honcho_collection -def update_collection( - db: Session, +async def update_collection( + db: AsyncSession, collection: schemas.CollectionUpdate, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, ) -> models.Collection: - honcho_collection = get_collection_by_id( + honcho_collection = await get_collection_by_id( db, app_id=app_id, user_id=user_id, collection_id=collection_id ) if honcho_collection is None: raise ValueError("collection not found or does not belong to user") try: honcho_collection.name = collection.name - db.commit() + await db.commit() except IntegrityError: - db.rollback() - raise ValueError("Collection already exists") - db.refresh(honcho_collection) + await db.rollback() + raise ValueError("Collection already exists") from None + await db.refresh(honcho_collection) return honcho_collection -def delete_collection( - db: Session, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID +async def delete_collection( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID ) -> bool: """ Delete a Collection and all documents associated with it. Takes advantage of @@ -520,11 +541,12 @@ def delete_collection( .where(models.User.id == user_id) .where(models.Collection.id == collection_id) ) - honcho_collection = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + honcho_collection = result.scalar_one_or_none() if honcho_collection is None: return False - db.delete(honcho_collection) - db.commit() + await db.delete(honcho_collection) + await db.commit() return True @@ -535,8 +557,8 @@ def delete_collection( # Should be similar to the messages methods outside of query -def get_documents( - db: Session, +async def get_documents( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, @@ -559,8 +581,8 @@ def get_documents( return stmt -def get_document( - db: Session, +async def get_document( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, @@ -576,12 +598,13 @@ def get_document( .where(models.Document.id == document_id) ) - document = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + document = result.scalar_one_or_none() return document -def query_documents( - db: Session, +async def query_documents( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, @@ -604,18 +627,19 @@ def query_documents( ) # if metadata is not None: # stmt = stmt.where(models.Document.h_metadata.contains(metadata)) - return db.scalars(stmt).all() + result = await db.execute(stmt) + return result.scalars().all() -def create_document( - db: Session, +async def create_document( + db: AsyncSession, document: schemas.DocumentCreate, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, ) -> models.Document: """Embed a message as a vector and create a document""" - collection = get_collection_by_id( + collection = await get_collection_by_id( db, app_id=app_id, collection_id=collection_id, user_id=user_id ) if collection is None: @@ -634,20 +658,20 @@ def create_document( embedding=embedding, ) db.add(honcho_document) - db.commit() - db.refresh(honcho_document) + await db.commit() + await db.refresh(honcho_document) return honcho_document -def update_document( - db: Session, +async def update_document( + db: AsyncSession, document: schemas.DocumentUpdate, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, ) -> bool: - honcho_document = get_document( + honcho_document = await get_document( db, app_id=app_id, collection_id=collection_id, @@ -667,13 +691,13 @@ def update_document( if document.metadata is not None: honcho_document.h_metadata = document.metadata - db.commit() - db.refresh(honcho_document) + await db.commit() + await db.refresh(honcho_document) return honcho_document -def delete_document( - db: Session, +async def delete_document( + db: AsyncSession, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, @@ -688,9 +712,10 @@ def delete_document( .where(models.Document.collection_id == collection_id) .where(models.Document.id == document_id) ) - document = db.scalars(stmt).one_or_none() + result = await db.execute(stmt) + document = result.scalar_one_or_none() if document is None: return False - db.delete(document) - db.commit() + await db.delete(document) + await db.commit() return True diff --git a/api/src/db.py b/api/src/db.py index abbf77b..73820a7 100644 --- a/api/src/db.py +++ b/api/src/db.py @@ -1,20 +1,26 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +# from sqlalchemy import create_engine +from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine +from sqlalchemy.ext.declarative import declarative_base + +# from sqlalchemy.orm import sessionmaker + load_dotenv() connect_args = {} -if os.environ["DATABASE_TYPE"] == "sqlite": # https://fastapi.tiangolo.com/tutorial/sql-databases/#note +if ( + os.environ["DATABASE_TYPE"] == "sqlite" +): # https://fastapi.tiangolo.com/tutorial/sql-databases/#note connect_args = {"check_same_thread": False} -engine = create_engine( +engine = create_async_engine( os.environ["CONNECTION_URI"], connect_args=connect_args, echo=True ) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() diff --git a/api/src/main.py b/api/src/main.py index 087a488..d7beeeb 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -54,6 +54,7 @@ from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address +from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException @@ -205,20 +206,23 @@ def otel_logging_init(): # Add SlowAPI middleware to the application app.state.limiter = limiter -app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) +app.add_exception_handler( + exc_class_or_status_code=RateLimitExceeded, + handler=_rate_limit_exceeded_handler, # type: ignore +) app.add_middleware(SlowAPIMiddleware) add_pagination(app) -def get_db(): +async def get_db(): """FastAPI Dependency Generator for Database""" db = SessionLocal() try: yield db finally: - db.close() + await db.close() @app.exception_handler(StarletteHTTPException) @@ -241,10 +245,10 @@ async def http_exception_handler(request, exc): # App Routes ######################################################## @app.get("/apps/{app_id}", response_model=schemas.App) -def get_app( +async def get_app( request: Request, app_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get an App by ID @@ -255,17 +259,17 @@ def get_app( schemas.App: App object """ - app = crud.get_app(db, app_id=app_id) + app = await crud.get_app(db, app_id=app_id) if app is None: raise HTTPException(status_code=404, detail="App not found") return app @app.get("/apps/name/{name}", response_model=schemas.App) -def get_app_by_name( +async def get_app_by_name( request: Request, name: str, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get an App by Name @@ -276,17 +280,17 @@ def get_app_by_name( schemas.App: App object """ - app = crud.get_app_by_name(db, name=name) + app = await crud.get_app_by_name(db, name=name) if app is None: raise HTTPException(status_code=404, detail="App not found") return app @app.post("/apps", response_model=schemas.App) -def create_app( +async def create_app( request: Request, app: schemas.AppCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Create an App @@ -297,14 +301,15 @@ def create_app( schemas.App: Created App object """ - return crud.create_app(db, app=app) + + return await crud.create_app(db, app=app) @app.get("/apps/get_or_create/{name}", response_model=schemas.App) -def get_or_create_app( +async def get_or_create_app( request: Request, name: str, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get or Create an App @@ -315,18 +320,18 @@ def get_or_create_app( schemas.App: App object """ - app = crud.get_app_by_name(db, name=name) + app = await crud.get_app_by_name(db, name=name) if app is None: - app = crud.create_app(db, app=schemas.AppCreate(name=name)) + app = await crud.create_app(db, app=schemas.AppCreate(name=name)) return app @app.put("/apps/{app_id}", response_model=schemas.App) -def update_app( +async def update_app( request: Request, app_id: uuid.UUID, app: schemas.AppUpdate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Update an App @@ -338,7 +343,7 @@ def update_app( schemas.App: The App object of the updated App """ - honcho_app = crud.update_app(db, app_id=app_id, app=app) + honcho_app = await crud.update_app(db, app_id=app_id, app=app) if honcho_app is None: raise HTTPException(status_code=404, detail="App not found") return honcho_app @@ -350,31 +355,32 @@ def update_app( @app.post("/apps/{app_id}/users", response_model=schemas.User) -def create_user( +async def create_user( request: Request, app_id: uuid.UUID, user: schemas.UserCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Create a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user (schemas.UserCreate): The User object containing any metadata Returns: schemas.User: Created User object """ - return crud.create_user(db, app_id=app_id, user=user) + return await crud.create_user(db, app_id=app_id, user=user) @app.get("/apps/{app_id}/users", response_model=Page[schemas.User]) -def get_users( +async def get_users( request: Request, app_id: uuid.UUID, reverse: bool = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get All Users for an App @@ -386,61 +392,66 @@ def get_users( list[schemas.User]: List of User objects """ - return paginate(db, crud.get_users(db, app_id=app_id, reverse=reverse)) + return paginate(db, await crud.get_users(db, app_id=app_id, reverse=reverse)) @app.get("/apps/{app_id}/users/{name}", response_model=schemas.User) -def get_user_by_name( +async def get_user_by_name( request: Request, app_id: uuid.UUID, name: str, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user Returns: schemas.User: User object """ - return crud.get_user_by_name(db, app_id=app_id, name=name) + return await crud.get_user_by_name(db, app_id=app_id, name=name) @app.get("/apps/{app_id}/users/get_or_create/{name}", response_model=schemas.User) -def get_or_create_user( - request: Request, app_id: uuid.UUID, name: str, db: Session = Depends(get_db) +async def get_or_create_user( + request: Request, app_id: uuid.UUID, name: str, db: AsyncSession = Depends(get_db) ): """Get or Create a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user Returns: schemas.User: User object """ - user = crud.get_user_by_name(db, app_id=app_id, name=name) + user = await crud.get_user_by_name(db, app_id=app_id, name=name) if user is None: - user = crud.create_user(db, app_id=app_id, user=schemas.UserCreate(name=name)) + user = await crud.create_user( + db, app_id=app_id, user=schemas.UserCreate(name=name) + ) return user @app.put("/apps/{app_id}/users/{user_id}", response_model=schemas.User) -def update_user( +async def update_user( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, user: schemas.UserUpdate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Update a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user user (schemas.UserCreate): The User object containing any metadata @@ -448,7 +459,7 @@ def update_user( schemas.User: Updated User object """ - return crud.update_user(db, app_id=app_id, user_id=user_id, user=user) + return await crud.update_user(db, app_id=app_id, user_id=user_id, user=user) ######################################################## @@ -457,21 +468,23 @@ def update_user( @router.get("/sessions", response_model=Page[schemas.Session]) -def get_sessions( +async def get_sessions( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, location_id: Optional[str] = None, is_active: Optional[bool] = False, reverse: Optional[bool] = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get All Sessions for a User Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (uuid.UUID): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a session + location_id (str, optional): Optional Location ID representing the location of a + session Returns: list[schemas.Session]: List of Session objects @@ -479,7 +492,7 @@ def get_sessions( """ return paginate( db, - crud.get_sessions( + await crud.get_sessions( db, app_id=app_id, user_id=user_id, @@ -491,12 +504,12 @@ def get_sessions( @router.post("/sessions", response_model=schemas.Session) -def create_session( +async def create_session( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session: schemas.SessionCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Create a Session for a User @@ -511,23 +524,26 @@ def create_session( schemas.Session: The Session object of the new Session """ - value = crud.create_session(db, app_id=app_id, user_id=user_id, session=session) + value = await crud.create_session( + db, app_id=app_id, user_id=user_id, session=session + ) return value @router.put("/sessions/{session_id}", response_model=schemas.Session) -def update_session( +async def update_session( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, session: schemas.SessionUpdate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Update the metadata of a Session Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (uuid.UUID): The User ID representing the user, managed by the user session_id (uuid.UUID): The ID of the Session to update session (schemas.SessionUpdate): The Session object containing any new metadata @@ -541,25 +557,26 @@ def update_session( status_code=400, detail="Session metadata cannot be empty" ) # TODO TEST if I can set the metadata to be blank with this try: - return crud.update_session( + return await crud.update_session( db, app_id=app_id, user_id=user_id, session_id=session_id, session=session ) except ValueError: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail="Session not found") from None @router.delete("/sessions/{session_id}") -def delete_session( +async def delete_session( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Delete a session by marking it as inactive Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (uuid.UUID): The User ID representing the user, managed by the user session_id (uuid.UUID): The ID of the Session to delete @@ -570,7 +587,7 @@ def delete_session( HTTPException: If the session is not found """ - response = crud.delete_session( + response = await crud.delete_session( db, app_id=app_id, user_id=user_id, session_id=session_id ) if response: @@ -580,17 +597,18 @@ def delete_session( @router.get("/sessions/{session_id}", response_model=schemas.Session) -def get_session( +async def get_session( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get a specific session for a user by ID Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (uuid.UUID): The User ID representing the user, managed by the user session_id (uuid.UUID): The ID of the Session to retrieve @@ -600,7 +618,7 @@ def get_session( Raises: HTTPException: If the session is not found """ - honcho_session = crud.get_session( + honcho_session = await crud.get_session( db, app_id=app_id, session_id=session_id, user_id=user_id ) if honcho_session is None: @@ -614,21 +632,23 @@ def get_session( @router.post("/sessions/{session_id}/messages", response_model=schemas.Message) -def create_message_for_session( +async def create_message_for_session( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, message: schemas.MessageCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Adds a message to a session Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to add the message to - message (schemas.MessageCreate): The Message object to add containing the message content and type + message (schemas.MessageCreate): The Message object to add containing the + message content and type Returns: schemas.Message: The Message object of the added message @@ -638,26 +658,27 @@ def create_message_for_session( """ try: - return crud.create_message( + return await crud.create_message( db, message=message, app_id=app_id, user_id=user_id, session_id=session_id ) except ValueError: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail="Session not found") from None @router.get("/sessions/{session_id}/messages", response_model=Page[schemas.Message]) -def get_messages( +async def get_messages( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, reverse: Optional[bool] = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get all messages for a session Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve reverse (bool): Whether to reverse the order of the messages @@ -672,7 +693,7 @@ def get_messages( try: return paginate( db, - crud.get_messages( + await crud.get_messages( db, app_id=app_id, user_id=user_id, @@ -681,22 +702,22 @@ def get_messages( ), ) except ValueError: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail="Session not found") from None @router.get( "sessions/{session_id}/messages/{message_id}", response_model=schemas.Message ) -def get_message( +async def get_message( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """ """ - honcho_message = crud.get_message( + honcho_message = await crud.get_message( db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id ) if honcho_message is None: @@ -710,21 +731,23 @@ def get_message( @router.post("/sessions/{session_id}/metamessages", response_model=schemas.Metamessage) -def create_metamessage( +async def create_metamessage( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, metamessage: schemas.MetamessageCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Adds a message to a session Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to add the message to - message (schemas.MessageCreate): The Message object to add containing the message content and type + message (schemas.MessageCreate): The Message object to add containing the + message content and type Returns: schemas.Message: The Message object of the added message @@ -734,7 +757,7 @@ def create_metamessage( """ try: - return crud.create_metamessage( + return await crud.create_metamessage( db, metamessage=metamessage, app_id=app_id, @@ -742,13 +765,13 @@ def create_metamessage( session_id=session_id, ) except ValueError: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail="Session not found") from None @router.get( "/sessions/{session_id}/metamessages", response_model=Page[schemas.Metamessage] ) -def get_metamessages( +async def get_metamessages( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, @@ -756,12 +779,13 @@ def get_metamessages( message_id: Optional[uuid.UUID] = None, metamessage_type: Optional[str] = None, reverse: Optional[bool] = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get all messages for a session Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve reverse (bool): Whether to reverse the order of the metamessages @@ -776,7 +800,7 @@ def get_metamessages( try: return paginate( db, - crud.get_metamessages( + await crud.get_metamessages( db, app_id=app_id, user_id=user_id, @@ -787,26 +811,27 @@ def get_metamessages( ), ) except ValueError: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail="Session not found") from None @router.get( "/sessions/{session_id}/metamessages/{metamessage_id}", response_model=schemas.Metamessage, ) -def get_metamessage( +async def get_metamessage( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, message_id: uuid.UUID, metamessage_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): """Get a specific session for a user by ID Args: - app_id (uuid.UUID): The ID of the app representing the client application using honcho + app_id (uuid.UUID): The ID of the app representing the client application using + honcho user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve @@ -816,7 +841,7 @@ def get_metamessage( Raises: HTTPException: If the session is not found """ - honcho_metamessage = crud.get_metamessage( + honcho_metamessage = await crud.get_metamessage( db, app_id=app_id, session_id=session_id, @@ -835,15 +860,16 @@ def get_metamessage( @router.get("/collections", response_model=Page[schemas.Collection]) -def get_collections( +async def get_collections( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, reverse: Optional[bool] = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): return paginate( - db, crud.get_collections(db, app_id=app_id, user_id=user_id, reverse=reverse) + db, + await crud.get_collections(db, app_id=app_id, user_id=user_id, reverse=reverse), ) @@ -853,7 +879,7 @@ def get_collections( # app_id: uuid.UUID, # user_id: uuid.UUID, # collection_id: uuid.UUID, -# db: Session = Depends(get_db), +# db: AsyncSession = Depends(get_db), # ) -> schemas.Collection: # honcho_collection = crud.get_collection_by_id( # db, app_id=app_id, user_id=user_id, collection_id=collection_id @@ -866,14 +892,14 @@ def get_collections( @router.get("/collections/{name}", response_model=schemas.Collection) -def get_collection_by_name( +async def get_collection_by_name( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, name: str, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ) -> schemas.Collection: - honcho_collection = crud.get_collection_by_name( + honcho_collection = await crud.get_collection_by_name( db, app_id=app_id, user_id=user_id, name=name ) if honcho_collection is None: @@ -884,39 +910,39 @@ def get_collection_by_name( @router.post("/collections", response_model=schemas.Collection) -def create_collection( +async def create_collection( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection: schemas.CollectionCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): try: - return crud.create_collection( + return await crud.create_collection( db, collection=collection, app_id=app_id, user_id=user_id ) except ValueError: raise HTTPException( status_code=406, detail="Error invalid collection configuration - name may already exist", - ) + ) from None @router.put("/collections/{collection_id}", response_model=schemas.Collection) -def update_collection( +async def update_collection( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, collection: schemas.CollectionUpdate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): if collection.name is None: raise HTTPException( status_code=400, detail="invalid request - name cannot be None" ) try: - honcho_collection = crud.update_collection( + honcho_collection = await crud.update_collection( db, collection=collection, app_id=app_id, @@ -927,19 +953,19 @@ def update_collection( raise HTTPException( status_code=406, detail="Error invalid collection configuration - name may already exist", - ) + ) from None return honcho_collection @router.delete("/collections/{collection_id}") -def delete_collection( +async def delete_collection( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): - response = crud.delete_collection( + response = await crud.delete_collection( db, app_id=app_id, user_id=user_id, collection_id=collection_id ) if response: @@ -958,18 +984,18 @@ def delete_collection( @router.get( "/collections/{collection_id}/documents", response_model=Page[schemas.Document] ) -def get_documents( +async def get_documents( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, reverse: Optional[bool] = False, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): try: return paginate( db, - crud.get_documents( + await crud.get_documents( db, app_id=app_id, user_id=user_id, @@ -982,7 +1008,7 @@ def get_documents( ): # TODO can probably remove this exception ok to return empty here raise HTTPException( status_code=404, detail="collection not found or does not belong to user" - ) + ) from None router.get( @@ -991,15 +1017,15 @@ def get_documents( ) -def get_document( +async def get_document( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): - honcho_document = crud.get_document( + honcho_document = await crud.get_document( db, app_id=app_id, user_id=user_id, @@ -1016,18 +1042,18 @@ def get_document( @router.get( "/collections/{collection_id}/query", response_model=Sequence[schemas.Document] ) -def query_documents( +async def query_documents( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, query: str, top_k: int = 5, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): if top_k is not None and top_k > 50: top_k = 50 # TODO see if we need to paginate this - return crud.query_documents( + return await crud.query_documents( db=db, app_id=app_id, user_id=user_id, @@ -1038,16 +1064,16 @@ def query_documents( @router.post("/collections/{collection_id}/documents", response_model=schemas.Document) -def create_document( +async def create_document( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, document: schemas.DocumentCreate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): try: - return crud.create_document( + return await crud.create_document( db, document=document, app_id=app_id, @@ -1057,27 +1083,27 @@ def create_document( except ValueError: raise HTTPException( status_code=404, detail="collection not found or does not belong to user" - ) + ) from None @router.put( "/collections/{collection_id}/documents/{document_id}", response_model=schemas.Document, ) -def update_document( +async def update_document( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, document: schemas.DocumentUpdate, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): if document.content is None and document.metadata is None: raise HTTPException( status_code=400, detail="content and metadata cannot both be None" ) - return crud.update_document( + return await crud.update_document( db, document=document, app_id=app_id, @@ -1088,15 +1114,15 @@ def update_document( @router.delete("/collections/{collection_id}/documents/{document_id}") -def delete_document( +async def delete_document( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, document_id: uuid.UUID, - db: Session = Depends(get_db), + db: AsyncSession = Depends(get_db), ): - response = crud.delete_document( + response = await crud.delete_document( db, app_id=app_id, user_id=user_id, From a46fa2b388df9d1d39ad6c36d3d19fa3e0b60cba Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:28:44 -0800 Subject: [PATCH 53/85] Working Async API using Psycopg3 --- api/.env.template | 2 +- api/poetry.lock | 190 +++++++++++++-------------------------------- api/pyproject.toml | 4 +- api/src/db.py | 15 ++-- api/src/main.py | 30 +++---- 5 files changed, 82 insertions(+), 159 deletions(-) diff --git a/api/.env.template b/api/.env.template index afa83ba..747fcaa 100644 --- a/api/.env.template +++ b/api/.env.template @@ -1,5 +1,5 @@ DATABASE_TYPE=postgres -CONNECTION_URI=postgresql://testuser:testpwd@localhost:5432/honcho +CONNECTION_URI=postgresql+psycopg://testuser:testpwd@localhost:5432/honcho OPENAI_API_KEY= diff --git a/api/poetry.lock b/api/poetry.lock index f37eba4..2eb3f1b 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -57,74 +57,33 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" +name = "backports-zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" category = "main" 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 = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -category = "main" -optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.6" files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} - [package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] +tzdata = ["tzdata"] [[package]] name = "certifi" @@ -1027,87 +986,30 @@ files = [ ] [[package]] -name = "psycopg2-binary" -version = "2.9.9" -description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg" +version = "3.1.18" +description = "PostgreSQL database adapter for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, + {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, + {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, ] +[package.dependencies] +"backports.zoneinfo" = {version = ">=0.2.0", markers = "python_version < \"3.9\""} +typing-extensions = ">=4.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +binary = ["psycopg-binary (==3.1.18)"] +c = ["psycopg-c (==3.1.18)"] +dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] +docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] +pool = ["psycopg-pool"] +test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] + [[package]] name = "pydantic" version = "2.6.3" @@ -1492,6 +1394,18 @@ files = [ {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "urllib3" version = "2.2.1" @@ -1629,4 +1543,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "034c3672b8736b9c809f70c7a3a2261a7e79db8a21b57c1bfde783e5a446bd16" +content-hash = "4979725da4f6fb1ed958fac1aa4fa46c078a4fa804852cd82551275ab0820dec" diff --git a/api/pyproject.toml b/api/pyproject.toml index a03e4b3..30b0a9e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -11,7 +11,6 @@ fastapi = "^0.109.0" uvicorn = "^0.24.0.post1" python-dotenv = "^1.0.0" sqlalchemy = "^2.0.25" -psycopg2-binary = "^2.9.9" slowapi = "^0.1.8" fastapi-pagination = "^0.12.14" pgvector = "^0.2.5" @@ -23,7 +22,8 @@ opentelemetry-sdk = "^1.23.0" opentelemetry-exporter-otlp = "^1.23.0" opentelemetry-instrumentation-sqlalchemy = "^0.44b0" opentelemetry-instrumentation-logging = "^0.44b0" -asyncpg = "^0.29.0" +psycopg = "^3.1.18" +greenlet = "^3.0.3" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/db.py b/api/src/db.py index 73820a7..7da3dd2 100644 --- a/api/src/db.py +++ b/api/src/db.py @@ -1,16 +1,12 @@ import os from dotenv import load_dotenv - -# from sqlalchemy import create_engine +from sqlalchemy import create_engine from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from sqlalchemy.ext.declarative import declarative_base -# from sqlalchemy.orm import sessionmaker - load_dotenv() - connect_args = {} if ( @@ -24,3 +20,12 @@ SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() + + +def scaffold_db(): + """Use a Sync Engine for scaffolding the database. DDL operations are unavailable + with Async Engines + """ + engine = create_engine(os.environ["CONNECTION_URI"], echo=True) + Base.metadata.create_all(bind=engine) + engine.dispose() diff --git a/api/src/main.py b/api/src/main.py index d7beeeb..523911d 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -2,8 +2,7 @@ import logging import os import uuid - -# from logging import LogRecord +from contextlib import asynccontextmanager from typing import Optional, Sequence import sentry_sdk @@ -58,8 +57,8 @@ from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException -from . import crud, models, schemas -from .db import SessionLocal, engine +from . import crud, schemas +from .db import SessionLocal, engine, scaffold_db # Otel Setup @@ -190,10 +189,15 @@ def otel_logging_init(): enable_tracing=True, ) -models.Base.metadata.create_all(bind=engine) # Scaffold Database if not already done + +@asynccontextmanager +async def lifespan(app: FastAPI): + scaffold_db() # Scaffold Database on Startup + yield + await engine.dispose() -app = FastAPI() +app = FastAPI(lifespan=lifespan) if OPENTELEMTRY_ENABLED: FastAPIInstrumentor().instrument_app(app) @@ -218,7 +222,7 @@ def otel_logging_init(): async def get_db(): """FastAPI Dependency Generator for Database""" - db = SessionLocal() + db: AsyncSession = SessionLocal() try: yield db finally: @@ -392,7 +396,7 @@ async def get_users( list[schemas.User]: List of User objects """ - return paginate(db, await crud.get_users(db, app_id=app_id, reverse=reverse)) + return await paginate(db, await crud.get_users(db, app_id=app_id, reverse=reverse)) @app.get("/apps/{app_id}/users/{name}", response_model=schemas.User) @@ -490,7 +494,7 @@ async def get_sessions( list[schemas.Session]: List of Session objects """ - return paginate( + return await paginate( db, await crud.get_sessions( db, @@ -691,7 +695,7 @@ async def get_messages( """ try: - return paginate( + return await paginate( db, await crud.get_messages( db, @@ -798,7 +802,7 @@ async def get_metamessages( """ try: - return paginate( + return await paginate( db, await crud.get_metamessages( db, @@ -867,7 +871,7 @@ async def get_collections( reverse: Optional[bool] = False, db: AsyncSession = Depends(get_db), ): - return paginate( + return await paginate( db, await crud.get_collections(db, app_id=app_id, user_id=user_id, reverse=reverse), ) @@ -993,7 +997,7 @@ async def get_documents( db: AsyncSession = Depends(get_db), ): try: - return paginate( + return await paginate( db, await crud.get_documents( db, From 9be9c1b9f13f7df55f2acb01f9a33035d79e15c8 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:54:44 -0800 Subject: [PATCH 54/85] Update Workflow Connection URI --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index b78097a..adafe9f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -28,7 +28,7 @@ jobs: cd .. env: DATABASE_TYPE: postgres - CONNECTION_URI: postgresql://testuser:testpwd@localhost:5432/honcho + CONNECTION_URI: postgresql+psycopg://testuser:testpwd@localhost:5432/honcho OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Run Tests run: | From 0e6b56a64b0315bfd454f3b7d3c52a28986c65a8 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:35:37 -0800 Subject: [PATCH 55/85] Update Workflow Connection URI in coverage test as well --- .github/workflows/run_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 3d31c97..a0cf695 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -28,7 +28,7 @@ jobs: cd .. env: DATABASE_TYPE: postgres - CONNECTION_URI: postgresql://testuser:testpwd@localhost:5432/honcho + CONNECTION_URI: postgresql+psycopg://testuser:testpwd@localhost:5432/honcho OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Run Tests run: | From 825893372717a578a4852c48f4217473f3d5cc5f Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:52:49 -0800 Subject: [PATCH 56/85] Skeleton for Dialectic API --- api/poetry.lock | 126 ++++++++++++++++++++++++++++++++++++++++++- api/pyproject.toml | 1 + api/src/agent.py | 10 ++++ api/src/harvester.py | 23 ++++++++ api/src/main.py | 36 +++++++++++++ 5 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 api/src/agent.py create mode 100644 api/src/harvester.py diff --git a/api/poetry.lock b/api/poetry.lock index 2eb3f1b..c734132 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1122,6 +1122,21 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.1" @@ -1137,6 +1152,23 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "realtime" +version = "1.0.2" +description = "" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "realtime-1.0.2-py3-none-any.whl", hash = "sha256:8f8375199fd917cd0ded818702321f91b208ab72794ade0a33cee9d55ae30f11"}, + {file = "realtime-1.0.2.tar.gz", hash = "sha256:776170a4329edc869b91e104c554cda02c8bf8e052cbb93c377e22482870959c"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1,<3.0.0" +typing-extensions = ">=4.2.0,<5.0.0" +websockets = ">=11.0,<12.0" + [[package]] name = "requests" version = "2.31.0" @@ -1224,6 +1256,18 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +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 = "slowapi" version = "0.1.9" @@ -1444,6 +1488,86 @@ 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 = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + [[package]] name = "wrapt" version = "1.16.0" @@ -1543,4 +1667,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "4979725da4f6fb1ed958fac1aa4fa46c078a4fa804852cd82551275ab0820dec" +content-hash = "4b93c08110175da4cd6dd845ceed32c09a9cd1b8ad684fd9bf13aba2b5c0e0d0" diff --git a/api/pyproject.toml b/api/pyproject.toml index 30b0a9e..cdb5525 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -24,6 +24,7 @@ opentelemetry-instrumentation-sqlalchemy = "^0.44b0" opentelemetry-instrumentation-logging = "^0.44b0" psycopg = "^3.1.18" greenlet = "^3.0.3" +realtime = "^1.0.2" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/agent.py b/api/src/agent.py new file mode 100644 index 0000000..d6342fd --- /dev/null +++ b/api/src/agent.py @@ -0,0 +1,10 @@ +async def chat(): + pass + + +async def hydrate(): + pass + + +async def insight(): + pass diff --git a/api/src/harvester.py b/api/src/harvester.py new file mode 100644 index 0000000..a03aa05 --- /dev/null +++ b/api/src/harvester.py @@ -0,0 +1,23 @@ +import os + +from dotenv import load_dotenv +from realtime.connection import Socket + +load_dotenv() + +SUPABASE_ID = os.getenv("SUPABASE_ID") +API_KEY = os.getenv("SUPABASE_API_KEY") + + +def derive_facts(payload): + print("Derive Facts: ", payload) + + +if __name__ == "__main__": + URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={API_KEY}&vsn=1.0.0" + s = Socket(URL) + s.connect() + + channel = s.set_channel("realtime:public:messages") + channel.join().on("INSERT", derive_facts) + s.listen() diff --git a/api/src/main.py b/api/src/main.py index 523911d..0940293 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1141,4 +1141,40 @@ async def delete_document( ) +router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") + + +@router.get("/sessions/{session_id}/chat", response_model=Sequence[schemas.Message]) +async def get_chat( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + pass + + +@router.get("/sessions/{session_id}/hydrate") +async def hydrate( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + pass + + +@router.get("/sessions/{session_id}/insight") +async def get_insight( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + pass + + app.include_router(router) From bf6c1a7a9f0110997ebc89bde97f7bba9c8aff88 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:11:07 -0700 Subject: [PATCH 57/85] Fixes DEV-217 URL Encoding --- api/fly.toml | 2 +- api/poetry.lock | 78 +++++++++++++++++++++++++- api/pyproject.toml | 2 +- api/src/main.py | 3 - example/cli/poetry.lock | 73 +++++++++++++++++++++--- scripts/syncronizer.py | 2 + sdk/honcho/client.py | 82 ++++++++++++++++++++------- sdk/honcho/sync_client.py | 114 ++++++++++++++++++++++++-------------- 8 files changed, 281 insertions(+), 75 deletions(-) mode change 100644 => 100755 scripts/syncronizer.py diff --git a/api/fly.toml b/api/fly.toml index d062971..aa53938 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -17,7 +17,7 @@ kill_timeout = "5s" [http_service] internal_port = 8000 - auto_stop_machines = true + auto_stop_machines = false auto_start_machines = true min_machines_running = 1 processes = ["api"] diff --git a/api/poetry.lock b/api/poetry.lock index c734132..eb3311c 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -999,6 +999,7 @@ files = [ [package.dependencies] "backports.zoneinfo" = {version = ">=0.2.0", markers = "python_version < \"3.9\""} +psycopg-binary = {version = "3.1.18", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} typing-extensions = ">=4.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} @@ -1010,6 +1011,81 @@ docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)" pool = ["psycopg-pool"] test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +[[package]] +name = "psycopg-binary" +version = "3.1.18" +description = "PostgreSQL database adapter for Python -- C optimisation distribution" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57"}, + {file = "psycopg_binary-3.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d"}, + {file = "psycopg_binary-3.1.18-cp311-cp311-win_amd64.whl", hash = "sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5"}, + {file = "psycopg_binary-3.1.18-cp312-cp312-win_amd64.whl", hash = "sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080"}, + {file = "psycopg_binary-3.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e"}, + {file = "psycopg_binary-3.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d"}, + {file = "psycopg_binary-3.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679"}, +] + [[package]] name = "pydantic" version = "2.6.3" @@ -1667,4 +1743,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "4b93c08110175da4cd6dd845ceed32c09a9cd1b8ad684fd9bf13aba2b5c0e0d0" +content-hash = "049befe5515a3295e10aba589ec19ab6f01e7682555dc701712afeceac57a7f0" diff --git a/api/pyproject.toml b/api/pyproject.toml index cdb5525..33fdd79 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -22,9 +22,9 @@ opentelemetry-sdk = "^1.23.0" opentelemetry-exporter-otlp = "^1.23.0" opentelemetry-instrumentation-sqlalchemy = "^0.44b0" opentelemetry-instrumentation-logging = "^0.44b0" -psycopg = "^3.1.18" greenlet = "^3.0.3" realtime = "^1.0.2" +psycopg = {extras = ["binary"], version = "^3.1.18"} [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/main.py b/api/src/main.py index 0940293..19269dc 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1141,9 +1141,6 @@ async def delete_document( ) -router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") - - @router.get("/sessions/{session_id}/chat", response_model=Sequence[schemas.Message]) async def get_chat( request: Request, diff --git a/example/cli/poetry.lock b/example/cli/poetry.lock index 79a9761..0632c39 100644 --- a/example/cli/poetry.lock +++ b/example/cli/poetry.lock @@ -127,7 +127,7 @@ files = [ name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -180,7 +180,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -451,9 +451,21 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +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 = "honcho-ai" -version = "0.0.1" +version = "0.0.4" description = "Python Client SDK for Honcho" category = "main" optional = false @@ -462,12 +474,59 @@ files = [] develop = true [package.dependencies] -requests = "^2.31.0" +httpx = "^0.26.0" [package.source] type = "directory" url = "../../sdk" +[[package]] +name = "httpcore" +version = "1.0.4" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = ">=1.0.0,<2.0.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + [[package]] name = "idna" version = "3.6" @@ -956,7 +1015,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -978,7 +1037,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1121,7 +1180,7 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" +category = "dev" optional = false python-versions = ">=3.8" files = [ diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py old mode 100644 new mode 100755 index 0630b8f..0740bb4 --- a/scripts/syncronizer.py +++ b/scripts/syncronizer.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os import re diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 2d25ee0..427bbef 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -402,8 +402,14 @@ async def get_users( Returns: AsyncGetUserPage: Paginated list of users """ - url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" - response = await self.client.get(url) + # url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/users" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = await self.client.get(url, params=params) response.raise_for_status() data = response.json() return AsyncGetUserPage(data, self, reverse) @@ -542,11 +548,20 @@ async def get_sessions( AsyncGetSessionPage: Page or results for get_sessions query """ - url = ( - f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" - + (f"&location_id={location_id}" if location_id else "") - ) - response = await self.honcho.client.get(url) + # url = ( + # f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" + # + (f"&location_id={location_id}" if location_id else "") + # ) + url = f"{self.base_url}/sessions" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + "is_active": is_active, + } + if location_id: + params["location_id"] = location_id + response = await self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return AsyncGetSessionPage(data, self, reverse, location_id, is_active) @@ -675,8 +690,14 @@ async def get_collections( AsyncGetCollectionPage: Page or results for get_collections query """ - url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - response = await self.honcho.client.get(url) + # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/collections" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = await self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return AsyncGetCollectionPage(data, self, reverse) @@ -802,8 +823,13 @@ async def get_messages( AsyncGetMessagePage: Page of Message objects """ - url = f"{self.base_url}/messages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - response = await self.user.honcho.client.get(url) + url = f"{self.base_url}/messages" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return AsyncGetMessagePage(data, self, reverse) @@ -905,12 +931,20 @@ async def get_metamessages( list[dict]: List of Message objects """ - url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/metamessages" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } if metamessage_type: - url += f"&metamessage_type={metamessage_type}" + # url += f"&metamessage_type={metamessage_type}" + params["metamessage_type"] = metamessage_type if message: - url += f"&message_id={message.id}" - response = await self.user.honcho.client.get(url) + # url += f"&message_id={message.id}" + params["message_id"] = message.id + response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() message_id = message.id if message else None @@ -1087,10 +1121,14 @@ async def get_documents( AsyncGetDocumentPage: Page of Document objects """ - url = ( - f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - ) - response = await self.user.honcho.client.get(url) + # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/documents" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return AsyncGetDocumentPage(data, self, reverse) @@ -1125,8 +1163,10 @@ async def query(self, query: str, top_k: int = 5) -> list[Document]: Returns: List[Document]: The response from the query with matching documents """ - url = f"{self.base_url}/query?query={query}&top_k={top_k}" - response = await self.user.honcho.client.get(url) + # url = f"{self.base_url}/query?query={query}&top_k={top_k}" + url = f"{self.base_url}/query" + params = {"query": query, "top_k": top_k} + response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = [ Document( diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 4893f80..f6e8e94 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -147,9 +147,7 @@ def next(self): """ if self.page >= self.pages: return None - return self.session.get_messages( - (self.page + 1), self.page_size, self.reverse - ) + return self.session.get_messages((self.page + 1), self.page_size, self.reverse) class GetMetamessagePage(GetPage): @@ -294,9 +292,7 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict def initialize(self): - res = self.client.get( - f"{self.server_url}/apps/get_or_create/{self.app_name}" - ) + res = self.client.get(f"{self.server_url}/apps/get_or_create/{self.app_name}") res.raise_for_status() data = res.json() self.app_id: uuid.UUID = data["id"] @@ -337,9 +333,7 @@ def create_user(self, name: str, metadata: Optional[dict] = None): if metadata is None: metadata = {} url = f"{self.base_url}/users" - response = self.client.post( - url, json={"name": name, "metadata": metadata} - ) + response = self.client.post(url, json={"name": name, "metadata": metadata}) response.raise_for_status() data = response.json() return User( @@ -389,9 +383,7 @@ def get_or_create_user(self, name: str): created_at=data["created_at"], ) - def get_users( - self, page: int = 1, page_size: int = 50, reverse: bool = False - ): + def get_users(self, page: int = 1, page_size: int = 50, reverse: bool = False): """Get Paginated list of users Args: @@ -402,8 +394,14 @@ def get_users( Returns: GetUserPage: Paginated list of users """ - url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" - response = self.client.get(url) + # url = f"{self.base_url}/users?page={page}&size={page_size}&reverse={reverse}" + url = f"{self.base_url}/users" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = self.client.get(url, params=params) response.raise_for_status() data = response.json() return GetUserPage(data, self, reverse) @@ -475,7 +473,9 @@ def base_url(self): def __str__(self): """String representation of User""" - return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + return ( + f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + ) def update(self, metadata: dict): """Updates a user's metadata @@ -542,11 +542,20 @@ def get_sessions( GetSessionPage: Page or results for get_sessions query """ - url = ( - f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" - + (f"&location_id={location_id}" if location_id else "") - ) - response = self.honcho.client.get(url) + # url = ( + # f"{self.base_url}/sessions?page={page}&size={page_size}&reverse={reverse}&is_active={is_active}" + # + (f"&location_id={location_id}" if location_id else "") + # ) + url = f"{self.base_url}/sessions" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + "is_active": is_active, + } + if location_id: + params["location_id"] = location_id + response = self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return GetSessionPage(data, self, reverse, location_id, is_active) @@ -675,8 +684,14 @@ def get_collections( GetCollectionPage: Page or results for get_collections query """ - url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - response = self.honcho.client.get(url) + # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/collections" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return GetCollectionPage(data, self, reverse) @@ -802,8 +817,13 @@ def get_messages( GetMessagePage: Page of Message objects """ - url = f"{self.base_url}/messages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - response = self.user.honcho.client.get(url) + url = f"{self.base_url}/messages" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return GetMessagePage(data, self, reverse) @@ -829,9 +849,7 @@ def get_messages_generator(self, reverse: bool = False): get_messages_page = new_messages - def create_metamessage( - self, message: Message, metamessage_type: str, content: str - ): + def create_metamessage(self, message: Message, metamessage_type: str, content: str): """Adds a metamessage to a session and links it to a specific message Args: @@ -905,18 +923,24 @@ def get_metamessages( list[dict]: List of Message objects """ - url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/metamessages" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } if metamessage_type: - url += f"&metamessage_type={metamessage_type}" + # url += f"&metamessage_type={metamessage_type}" + params["metamessage_type"] = metamessage_type if message: - url += f"&message_id={message.id}" - response = self.user.honcho.client.get(url) + # url += f"&message_id={message.id}" + params["message_id"] = message.id + response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() message_id = message.id if message else None - return GetMetamessagePage( - data, self, reverse, message_id, metamessage_type - ) + return GetMetamessagePage(data, self, reverse, message_id, metamessage_type) def get_metamessages_generator( self, @@ -1001,7 +1025,9 @@ def base_url(self): def __str__(self): """String representation of Collection""" - return f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 + return ( + f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 + ) def update(self, name: str): """Update the name of the collection @@ -1087,10 +1113,14 @@ def get_documents( GetDocumentPage: Page of Document objects """ - url = ( - f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 - ) - response = self.user.honcho.client.get(url) + # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + url = f"{self.base_url}/documents" + params = { + "page": page, + "size": page_size, + "reverse": reverse, + } + response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() return GetDocumentPage(data, self, reverse) @@ -1125,8 +1155,10 @@ def query(self, query: str, top_k: int = 5) -> list[Document]: Returns: List[Document]: The response from the query with matching documents """ - url = f"{self.base_url}/query?query={query}&top_k={top_k}" - response = self.user.honcho.client.get(url) + # url = f"{self.base_url}/query?query={query}&top_k={top_k}" + url = f"{self.base_url}/query" + params = {"query": query, "top_k": top_k} + response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = [ Document( From b5935ecbb327b05d56f08f1e5ac970a557d7e83d Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:40:53 -0700 Subject: [PATCH 58/85] Add Built-in Langchain Utility function --- example/cli/main.py | 16 +- example/cli/poetry.lock | 677 ++++++++++-------- example/discord/honcho-dspy-personas/bot.py | 24 +- example/discord/honcho-dspy-personas/chain.py | 131 ++-- example/discord/honcho-fact-memory/bot.py | 3 +- example/discord/honcho-fact-memory/chain.py | 163 +++-- example/discord/simple-roast-bot/main.py | 11 +- sdk/honcho/ext/__init__.py | 0 sdk/honcho/ext/langchain.py | 29 + 9 files changed, 615 insertions(+), 439 deletions(-) create mode 100644 sdk/honcho/ext/__init__.py create mode 100644 sdk/honcho/ext/langchain.py diff --git a/example/cli/main.py b/example/cli/main.py index f1cd9e2..a44f0a6 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -2,14 +2,18 @@ from uuid import uuid4 from langchain.prompts import ChatPromptTemplate + from langchain.schema import AIMessage, HumanMessage, SystemMessage from langchain_community.chat_models.fake import FakeListChatModel from honcho import Honcho +from honcho.ext.langchain import langchain_message_converter app_name = str(uuid4()) -# honcho = Honcho(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local +# honcho = Honcho( +# app_name=app_name, base_url="http://localhost:8000" +# ) # uncomment to use local honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev honcho.initialize() @@ -24,16 +28,6 @@ session = user.create_session() -def langchain_message_converter(messages: List): - new_messages = [] - for message in messages: - if message.is_user: - new_messages.append(HumanMessage(content=message.content)) - else: - new_messages.append(AIMessage(content=message.content)) - return new_messages - - def chat(): while True: user_input = input("User: ") diff --git a/example/cli/poetry.lock b/example/cli/poetry.lock index 0632c39..546d3c0 100644 --- a/example/cli/poetry.lock +++ b/example/cli/poetry.lock @@ -125,14 +125,14 @@ files = [ [[package]] name = "anyio" -version = "4.2.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] @@ -166,14 +166,14 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -278,14 +278,14 @@ files = [ [[package]] name = "dataclasses-json" -version = "0.6.3" +version = "0.6.4" description = "Easily serialize dataclasses to and from JSON." category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "dataclasses_json-0.6.3-py3-none-any.whl", hash = "sha256:4aeb343357997396f6bca1acae64e486c3a723d8f5c76301888abeccf0c45176"}, - {file = "dataclasses_json-0.6.3.tar.gz", hash = "sha256:35cb40aae824736fdf959801356641836365219cfe14caeb115c39136f775d2a"}, + {file = "dataclasses_json-0.6.4-py3-none-any.whl", hash = "sha256:f90578b8a3177f7552f4e1a6e535e84293cd5da421fcce0642d49c0d7bdf8df2"}, + {file = "dataclasses_json-0.6.4.tar.gz", hash = "sha256:73696ebf24936560cca79a2430cbc4f3dd23ac7bf46ed17f38e5e5e7657a6377"}, ] [package.dependencies] @@ -568,23 +568,24 @@ files = [ [[package]] name = "langchain" -version = "0.1.4" +version = "0.1.11" description = "Building applications with LLMs through composability" category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain-0.1.4-py3-none-any.whl", hash = "sha256:6befdd6221f5f326092e31a3c19efdc7ce3d7d1f2e2cab065141071451730ed7"}, - {file = "langchain-0.1.4.tar.gz", hash = "sha256:8767a9461e2b717ce9a35b1fa20659de89ea86ba9c2a4ff516e05d47ab2d195d"}, + {file = "langchain-0.1.11-py3-none-any.whl", hash = "sha256:b5e678ac50d85370b9bc28f2c97ad5f029aac1c0cca79cac9354adf72741bc6e"}, + {file = "langchain-0.1.11.tar.gz", hash = "sha256:03f08cae7cd3f341c54f1042b3fe24d88f39eba7b7eda942735d8ced13fe6da9"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" jsonpatch = ">=1.33,<2.0" -langchain-community = ">=0.0.14,<0.1" -langchain-core = ">=0.1.16,<0.2" -langsmith = ">=0.0.83,<0.1" +langchain-community = ">=0.0.25,<0.1" +langchain-core = ">=0.1.29,<0.2" +langchain-text-splitters = ">=0.0.1,<0.1" +langsmith = ">=0.1.17,<0.2.0" numpy = ">=1,<2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -593,13 +594,13 @@ SQLAlchemy = ">=1.4,<3" tenacity = ">=8.1.0,<9.0.0" [package.extras] -azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.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 (<2)"] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.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 (<2)"] clarifai = ["clarifai (>=9.1.0)"] cli = ["typer (>=0.9.0,<0.10.0)"] cohere = ["cohere (>=4,<5)"] docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "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)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "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)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.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 (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"] @@ -608,21 +609,21 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] name = "langchain-community" -version = "0.0.16" +version = "0.0.28" description = "Community contributed LangChain integrations." category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_community-0.0.16-py3-none-any.whl", hash = "sha256:0f1dfc1a6205ce8d39931d3515974a208a9f69c16157c649f83490a7cc830b73"}, - {file = "langchain_community-0.0.16.tar.gz", hash = "sha256:c06512a93013a06fba7679cd5a1254ff8b927cddd2d1fbe0cc444bf7bbdf0b8c"}, + {file = "langchain_community-0.0.28-py3-none-any.whl", hash = "sha256:bdb015ac455ae68432ea104628717583dce041e1abdfcefe86e39f034f5e90b8"}, + {file = "langchain_community-0.0.28.tar.gz", hash = "sha256:8664d243a90550fc5ddc137b712034e02c8d43afc8d4cc832ba5842b44c864ce"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" -langchain-core = ">=0.1.16,<0.2" -langsmith = ">=0.0.83,<0.1" +langchain-core = ">=0.1.31,<0.2.0" +langsmith = ">=0.1.0,<0.2.0" numpy = ">=1,<2" PyYAML = ">=5.3" requests = ">=2,<3" @@ -631,24 +632,24 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.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)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.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)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] [[package]] name = "langchain-core" -version = "0.1.17" +version = "0.1.31" description = "Building applications with LLMs through composability" category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.17-py3-none-any.whl", hash = "sha256:026155cf97867bde410ab1834799ab4c5ba64c39380f2a4328bcf9c78623ca64"}, - {file = "langchain_core-0.1.17.tar.gz", hash = "sha256:59016e457cd6a1708d83a3a454acc97cf02c2a2c3af95626d13f83894fd4e777"}, + {file = "langchain_core-0.1.31-py3-none-any.whl", hash = "sha256:ff028f00db8ff03565b542cea81be27426022a72c6545b54d8de66fa00948ab3"}, + {file = "langchain_core-0.1.31.tar.gz", hash = "sha256:d660cf209bb6ce61cb1c853107b091aaa809015a55dce9e0ce19b51d4c8f2a70"}, ] [package.dependencies] anyio = ">=3,<5" jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.0.83,<0.1" +langsmith = ">=0.1.0,<0.2.0" packaging = ">=23.2,<24.0" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -658,125 +659,159 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] extended-testing = ["jinja2 (>=3,<4)"] +[[package]] +name = "langchain-text-splitters" +version = "0.0.1" +description = "LangChain text splitting utilities" +category = "dev" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_text_splitters-0.0.1-py3-none-any.whl", hash = "sha256:f5b802f873f5ff6a8b9259ff34d53ed989666ef4e1582e6d1adb3b5520e3839a"}, + {file = "langchain_text_splitters-0.0.1.tar.gz", hash = "sha256:ac459fa98799f5117ad5425a9330b21961321e30bc19a2a2f9f761ddadd62aa1"}, +] + +[package.dependencies] +langchain-core = ">=0.1.28,<0.2.0" + +[package.extras] +extended-testing = ["lxml (>=5.1.0,<6.0.0)"] + [[package]] name = "langsmith" -version = "0.0.85" +version = "0.1.24" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.85-py3-none-any.whl", hash = "sha256:9d0ccbcda7b69c83828060603a51bb4319e43b8dc807fbd90b6355f8ec709500"}, - {file = "langsmith-0.0.85.tar.gz", hash = "sha256:fefc631fc30d836b54d4e3f99961c41aea497633898b8f09e305b6c7216c2c54"}, + {file = "langsmith-0.1.24-py3-none-any.whl", hash = "sha256:898ef5265bca8fc912f7fbf207e1d69cacd86055faecf6811bd42641e6319840"}, + {file = "langsmith-0.1.24.tar.gz", hash = "sha256:432b829e763f5077df411bc59bb35449813f18174d2ebc8bbbb38427071d5e7d"}, ] [package.dependencies] +orjson = ">=3.9.14,<4.0.0" pydantic = ">=1,<3" requests = ">=2,<3" [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.21.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "multidict" -version = "6.0.4" +version = "6.0.5" description = "multidict implementation" category = "dev" 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"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] @@ -793,48 +828,108 @@ files = [ [[package]] name = "numpy" -version = "1.26.3" +version = "1.26.4" description = "Fundamental package for array computing in Python" category = "dev" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, - {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, - {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, - {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, - {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, - {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, - {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, - {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, - {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, - {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, - {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "orjson" +version = "3.9.15" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, + {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, + {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, + {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, + {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, + {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, + {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, + {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, + {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, + {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, + {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, + {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, + {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, + {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, + {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, + {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, + {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, + {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, + {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, + {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, + {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, + {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, + {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, + {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, + {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, ] [[package]] @@ -851,19 +946,19 @@ files = [ [[package]] name = "pydantic" -version = "2.6.0" +version = "2.6.4" description = "Data validation using Python type hints" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae"}, - {file = "pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf"}, + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.1" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -871,91 +966,91 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.1" +version = "2.16.3" description = "" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948"}, - {file = "pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17"}, - {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388"}, - {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7"}, - {file = "pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4"}, - {file = "pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c"}, - {file = "pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da"}, - {file = "pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864"}, - {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7"}, - {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae"}, - {file = "pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1"}, - {file = "pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c"}, - {file = "pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b"}, - {file = "pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51"}, - {file = "pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e"}, - {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8"}, - {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f"}, - {file = "pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212"}, - {file = "pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f"}, - {file = "pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd"}, - {file = "pydantic_core-2.16.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706"}, - {file = "pydantic_core-2.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95"}, - {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8"}, - {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca"}, - {file = "pydantic_core-2.16.1-cp38-none-win32.whl", hash = "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610"}, - {file = "pydantic_core-2.16.1-cp38-none-win_amd64.whl", hash = "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e"}, - {file = "pydantic_core-2.16.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196"}, - {file = "pydantic_core-2.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e"}, - {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d"}, - {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640"}, - {file = "pydantic_core-2.16.1-cp39-none-win32.whl", hash = "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f"}, - {file = "pydantic_core-2.16.1-cp39-none-win_amd64.whl", hash = "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76"}, - {file = "pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] @@ -1035,73 +1130,73 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" category = "main" 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"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "sqlalchemy" -version = "2.0.25" +version = "2.0.28" description = "Database Abstraction Library" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, - {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, - {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -1150,14 +1245,14 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -1178,14 +1273,14 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index 77a7abf..ea91ec7 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -2,9 +2,9 @@ from uuid import uuid1 import discord from honcho import Honcho +from honcho.ext.langchain import langchain_message_converter from graph import chat from dspy import Example -from chain import langchain_message_converter intents = discord.Intents.default() intents.messages = True @@ -51,7 +51,9 @@ async def on_message(message): user = honcho.get_or_create_user(user_id) location_id = str(message.channel.id) - sessions = list(user.get_sessions_generator(location_id, is_active=True, reverse=True)) + sessions = list( + user.get_sessions_generator(location_id, is_active=True, reverse=True) + ) if len(sessions) > 0: session = sessions[0] @@ -86,7 +88,9 @@ async def on_reaction_add(reaction, user): honcho_user = honcho.get_or_create_user(user_id) location_id = str(reaction.message.channel.id) - sessions = list(honcho_user.get_sessions_generator(location_id, is_active=True, reverse=True)) + sessions = list( + honcho_user.get_sessions_generator(location_id, is_active=True, reverse=True) + ) if len(sessions) > 0: session = sessions[0] else: @@ -100,25 +104,29 @@ async def on_reaction_add(reaction, user): user_response = user_responses[0] user_state_storage = dict(honcho_user.metadata) - user_state = list(session.get_metamessages_generator(metamessage_type="user_state", message=user_response, reverse=True))[0].content + user_state = list( + session.get_metamessages_generator( + metamessage_type="user_state", message=user_response, reverse=True + ) + )[0].content examples = user_state_storage[user_state]["examples"] # Check if the reaction is a thumbs up if str(reaction.emoji) == "👍": example = Example( - chat_input=user_response.content, + chat_input=user_response.content, response=ai_response, assessment_dimension=user_state, - label='yes' + label="yes", ).with_inputs("chat_input", "response", "assessment_dimension") examples.append(example.toDict()) # Check if the reaction is a thumbs down elif str(reaction.emoji) == "👎": example = Example( - chat_input=user_response.content, + chat_input=user_response.content, response=ai_response, assessment_dimension=user_state, - label='no' + label="no", ).with_inputs("chat_input", "response", "assessment_dimension") examples.append(example.toDict()) diff --git a/example/discord/honcho-dspy-personas/chain.py b/example/discord/honcho-dspy-personas/chain.py index eede40e..be92c21 100644 --- a/example/discord/honcho-dspy-personas/chain.py +++ b/example/discord/honcho-dspy-personas/chain.py @@ -2,7 +2,11 @@ from typing import List, Union from dotenv import load_dotenv from langchain_openai import ChatOpenAI -from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, load_prompt +from langchain_core.prompts import ( + ChatPromptTemplate, + SystemMessagePromptTemplate, + load_prompt, +) from langchain_core.messages import AIMessage, HumanMessage from honcho import Message @@ -10,84 +14,103 @@ load_dotenv() # langchain prompts -SYSTEM_STATE_COMMENTARY = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_commentary.yaml')) -SYSTEM_STATE_LABELING = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_labeling.yaml')) -SYSTEM_STATE_CHECK = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_check.yaml')) - -# quick utility function to convert messages from honcho to langchain -def langchain_message_converter(messages: List[Message]) -> List[Union[AIMessage, HumanMessage]]: - new_messages = [] - for message in messages: - if message.is_user: - new_messages.append(HumanMessage(content=message.content)) - else: - new_messages.append(AIMessage(content=message.content)) - return new_messages +SYSTEM_STATE_COMMENTARY = load_prompt( + os.path.join(os.path.dirname(__file__), "langchain_prompts/state_commentary.yaml") +) +SYSTEM_STATE_LABELING = load_prompt( + os.path.join(os.path.dirname(__file__), "langchain_prompts/state_labeling.yaml") +) +SYSTEM_STATE_CHECK = load_prompt( + os.path.join(os.path.dirname(__file__), "langchain_prompts/state_check.yaml") +) # convert chat history and user input into a string def format_chat_history(chat_history: List[Message], user_input=None): - messages = [("user: " + message.content if isinstance(message, HumanMessage) else "ai: " + message.content) for message in chat_history] + messages = [ + ( + "user: " + message.content + if isinstance(message, HumanMessage) + else "ai: " + message.content + ) + for message in chat_history + ] if user_input: messages.append(f"user: {user_input}") return "\n".join(messages) - class StateExtractor: """Wrapper class for all the DSPy and LangChain code for user state labeling and pipeline optimization""" - lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name = "gpt-4") - lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name = "gpt-3.5-turbo") - system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_COMMENTARY) - system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_LABELING) - system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_CHECK) + + lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name="gpt-4") + lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name="gpt-3.5-turbo") + system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_STATE_COMMENTARY + ) + system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_STATE_LABELING + ) + system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_STATE_CHECK + ) def __init__(self) -> None: pass @classmethod - async def generate_state_commentary(cls, existing_states: List[str], chat_history: List[Message], input: str) -> str: + async def generate_state_commentary( + cls, existing_states: List[str], chat_history: List[Message], input: str + ) -> str: """Generate a commentary on the current state of the user""" # format existing states existing_states = "\n".join(existing_states) # format prompt - state_commentary = ChatPromptTemplate.from_messages([ - cls.system_state_commentary - ]) + state_commentary = ChatPromptTemplate.from_messages( + [cls.system_state_commentary] + ) # LCEL chain = state_commentary | cls.lc_gpt_4 # inference - response = await chain.ainvoke({ - "chat_history": chat_history, - "user_input": input, - "existing_states": existing_states, - }) + response = await chain.ainvoke( + { + "chat_history": chat_history, + "user_input": input, + "existing_states": existing_states, + } + ) # return output return response.content - + @classmethod - async def generate_state_label(cls, existing_states: List[str], state_commentary: str) -> str: + async def generate_state_label( + cls, existing_states: List[str], state_commentary: str + ) -> str: """Generate a state label from a commetary on the user's state""" # format existing states existing_states = "\n".join(existing_states) # format prompt - state_labeling = ChatPromptTemplate.from_messages([ - cls.system_state_labeling, - ]) + state_labeling = ChatPromptTemplate.from_messages( + [ + cls.system_state_labeling, + ] + ) # LCEL chain = state_labeling | cls.lc_gpt_4 # inference - response = await chain.ainvoke({ - "state_commentary": state_commentary, - "existing_states": existing_states, - }) + response = await chain.ainvoke( + { + "state_commentary": state_commentary, + "existing_states": existing_states, + } + ) # strip anything that's not letters - clean_response = ''.join(c for c in response.content if c.isalpha()) + clean_response = "".join(c for c in response.content if c.isalpha()) # return output return clean_response - + @classmethod async def check_state_exists(cls, existing_states: List[str], state: str): """Check if a user state is new or already is stored""" @@ -96,32 +119,36 @@ async def check_state_exists(cls, existing_states: List[str], state: str): existing_states = "\n".join(existing_states) # format prompt - state_check = ChatPromptTemplate.from_messages([ - cls.system_state_check - ]) + state_check = ChatPromptTemplate.from_messages([cls.system_state_check]) # LCEL chain = state_check | cls.lc_gpt_turbo # inference - response = await chain.ainvoke({ - "existing_states": existing_states, - "state": state, - }) + response = await chain.ainvoke( + { + "existing_states": existing_states, + "state": state, + } + ) # return output return response.content @classmethod - async def generate_state(cls, existing_states: List[str], chat_history: List[Message], input: str): - """"Determine the user's state from the current conversation state""" + async def generate_state( + cls, existing_states: List[str], chat_history: List[Message], input: str + ): + """ "Determine the user's state from the current conversation state""" # Generate label - state_commentary = await cls.generate_state_commentary(existing_states, chat_history, input) + state_commentary = await cls.generate_state_commentary( + existing_states, chat_history, input + ) state_label = await cls.generate_state_label(existing_states, state_commentary) # Determine if state is new # if True, it doesn't exist, state is new # if False, it does exist, state is not new, existing_state was returned existing_state = await cls.check_state_exists(existing_states, state_label) - is_state_new = existing_state == "None" + is_state_new = existing_state == "None" # return existing state if we found one if is_state_new: diff --git a/example/discord/honcho-fact-memory/bot.py b/example/discord/honcho-fact-memory/bot.py index a72e54a..aca2650 100644 --- a/example/discord/honcho-fact-memory/bot.py +++ b/example/discord/honcho-fact-memory/bot.py @@ -2,7 +2,8 @@ from uuid import uuid1 import discord from honcho import Honcho -from chain import langchain_message_converter, LMChain +from honcho.ext.langchain import langchain_message_converter +from chain import LMChain intents = discord.Intents.default() diff --git a/example/discord/honcho-fact-memory/chain.py b/example/discord/honcho-fact-memory/chain.py index a2b828b..0c528e1 100644 --- a/example/discord/honcho-fact-memory/chain.py +++ b/example/discord/honcho-fact-memory/chain.py @@ -2,7 +2,11 @@ from typing import List from dotenv import load_dotenv from langchain_openai import ChatOpenAI -from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, load_prompt +from langchain_core.prompts import ( + ChatPromptTemplate, + SystemMessagePromptTemplate, + load_prompt, +) from langchain_core.output_parsers import NumberedListOutputParser from langchain_core.messages import AIMessage, HumanMessage @@ -10,30 +14,37 @@ load_dotenv() -SYSTEM_DERIVE_FACTS = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/core/derive_facts.yaml')) -SYSTEM_INTROSPECTION = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/core/introspection.yaml')) -SYSTEM_RESPONSE = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/core/response.yaml')) -SYSTEM_CHECK_DUPS = load_prompt(os.path.join(os.path.dirname(__file__), 'prompts/utils/check_dup_facts.yaml')) - - -def langchain_message_converter(messages: List): - new_messages = [] - for message in messages: - if message.is_user: - new_messages.append(HumanMessage(content=message.content)) - else: - new_messages.append(AIMessage(content=message.content)) - return new_messages +SYSTEM_DERIVE_FACTS = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/core/derive_facts.yaml") +) +SYSTEM_INTROSPECTION = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/core/introspection.yaml") +) +SYSTEM_RESPONSE = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/core/response.yaml") +) +SYSTEM_CHECK_DUPS = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/utils/check_dup_facts.yaml") +) class LMChain: "Wrapper class for encapsulating the multiple different chains used" + output_parser = NumberedListOutputParser() - llm: ChatOpenAI = ChatOpenAI(model_name = "gpt-3.5-turbo") - system_derive_facts: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_DERIVE_FACTS) - system_introspection: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_INTROSPECTION) - system_response: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_RESPONSE) - system_check_dups: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_CHECK_DUPS) + llm: ChatOpenAI = ChatOpenAI(model_name="gpt-3.5-turbo") + system_derive_facts: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_DERIVE_FACTS + ) + system_introspection: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_INTROSPECTION + ) + system_response: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_RESPONSE + ) + system_check_dups: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_CHECK_DUPS + ) def __init__(self) -> None: pass @@ -43,18 +54,25 @@ async def derive_facts(cls, chat_history: List, input: str): """Derive facts from the user input""" # format prompt - fact_derivation = ChatPromptTemplate.from_messages([ - cls.system_derive_facts - ]) + fact_derivation = ChatPromptTemplate.from_messages([cls.system_derive_facts]) # LCEL chain = fact_derivation | cls.llm - + # inference - response = await chain.ainvoke({ - "chat_history": [("user: " + message.content if isinstance(message, HumanMessage) else "ai: " + message.content) for message in chat_history], - "user_input": input - }) + response = await chain.ainvoke( + { + "chat_history": [ + ( + "user: " + message.content + if isinstance(message, HumanMessage) + else "ai: " + message.content + ) + for message in chat_history + ], + "user_input": input, + } + ) # parse output facts = cls.output_parser.parse(response.content) @@ -62,28 +80,31 @@ async def derive_facts(cls, chat_history: List, input: str): print(f"DERIVED FACTS: {facts}") return facts - + @classmethod - async def check_dups(cls, user_message: Message, session: Session, collection: Collection, facts: List): + async def check_dups( + cls, + user_message: Message, + session: Session, + collection: Collection, + facts: List, + ): """Check that we're not storing duplicate facts""" # format prompt - check_duplication = ChatPromptTemplate.from_messages([ - cls.system_check_dups - ]) + check_duplication = ChatPromptTemplate.from_messages([cls.system_check_dups]) query = " ".join(facts) - result = collection.query(query=query, top_k=10) + result = collection.query(query=query, top_k=10) existing_facts = [document.content for document in result] # LCEL chain = check_duplication | cls.llm # inference - response = await chain.ainvoke({ - "existing_facts": existing_facts, - "facts": facts - }) + response = await chain.ainvoke( + {"existing_facts": existing_facts, "facts": facts} + ) # parse output new_facts = cls.output_parser.parse(response.content) @@ -96,28 +117,30 @@ async def check_dups(cls, user_message: Message, session: Session, collection: C # add facts as metamessages for fact in new_facts: - session.create_metamessage(message=user_message, metamessage_type="fact", content=fact) + session.create_metamessage( + message=user_message, metamessage_type="fact", content=fact + ) return - @classmethod - async def introspect(cls, user_message: Message, session: Session, chat_history: List, input:str): + async def introspect( + cls, user_message: Message, session: Session, chat_history: List, input: str + ): """Generate questions about the user to use as retrieval over the fact store""" # format prompt - introspection_prompt = ChatPromptTemplate.from_messages([ - cls.system_introspection - ]) + introspection_prompt = ChatPromptTemplate.from_messages( + [cls.system_introspection] + ) # LCEL chain = introspection_prompt | cls.llm # inference - response = await chain.ainvoke({ - "chat_history": chat_history, - "user_input": input - }) + response = await chain.ainvoke( + {"chat_history": chat_history, "user_input": input} + ) # parse output questions = cls.output_parser.parse(response.content) @@ -126,21 +149,22 @@ async def introspect(cls, user_message: Message, session: Session, chat_history: # write questions as metamessages for question in questions: - session.create_metamessage(message=user_message, metamessage_type="introspect", content=question) + session.create_metamessage( + message=user_message, metamessage_type="introspect", content=question + ) return questions - @classmethod - async def respond(cls, collection: Collection, chat_history: List, questions: List, input: str): + async def respond( + cls, collection: Collection, chat_history: List, questions: List, input: str + ): """Take the facts and chat history and generate a personalized response""" # format prompt - response_prompt = ChatPromptTemplate.from_messages([ - cls.system_response, - *chat_history, - HumanMessage(content=input) - ]) + response_prompt = ChatPromptTemplate.from_messages( + [cls.system_response, *chat_history, HumanMessage(content=input)] + ) retrieved_facts = collection.query(query=questions, top_k=10) retrieved_facts_content = [document.content for document in retrieved_facts] @@ -149,18 +173,29 @@ async def respond(cls, collection: Collection, chat_history: List, questions: Li chain = response_prompt | cls.llm # inference - response = await chain.ainvoke({ - "facts": retrieved_facts_content, - }) + response = await chain.ainvoke( + { + "facts": retrieved_facts_content, + } + ) return response.content - + @classmethod - async def chat(cls, chat_history: List, user_message: Message, session: Session, collection: Collection, input: str): + async def chat( + cls, + chat_history: List, + user_message: Message, + session: Session, + collection: Collection, + input: str, + ): """Chat with the model""" facts = await cls.derive_facts(chat_history, input) - await cls.check_dups(user_message, session, collection, facts) if facts is not None else None + await cls.check_dups( + user_message, session, collection, facts + ) if facts is not None else None # introspect questions = await cls.introspect(user_message, session, chat_history, input) @@ -169,7 +204,3 @@ async def chat(cls, chat_history: List, user_message: Message, session: Session, response = await cls.respond(collection, chat_history, questions, input) return response - - - - diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py index 0f59931..2a7a4b2 100644 --- a/example/discord/simple-roast-bot/main.py +++ b/example/discord/simple-roast-bot/main.py @@ -11,6 +11,7 @@ from langchain_core.messages import AIMessage, HumanMessage from honcho import Honcho +from honcho.ext.langchain import langchain_message_converter load_dotenv() @@ -43,16 +44,6 @@ chain = prompt | model | output_parser -def langchain_message_converter(messages: List): - new_messages = [] - for message in messages: - if message.is_user: - new_messages.append(HumanMessage(content=message.content)) - else: - new_messages.append(AIMessage(content=message.content)) - return new_messages - - @bot.event async def on_ready(): print(f"We have logged in as {bot.user}") diff --git a/sdk/honcho/ext/__init__.py b/sdk/honcho/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sdk/honcho/ext/langchain.py b/sdk/honcho/ext/langchain.py new file mode 100644 index 0000000..c84a0be --- /dev/null +++ b/sdk/honcho/ext/langchain.py @@ -0,0 +1,29 @@ +import functools +import importlib +from typing import List + +from honcho.schemas import Message + + +def requires_langchain(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if importlib.util.find_spec("langchain") is None: + raise ImportError("Langchain must be installed to use this feature") + # raise RuntimeError("langchain is not installed") + return func(*args, **kwargs) + + return wrapper + + +@requires_langchain +def langchain_message_converter(messages: List[Message]): + from langchain_core.messages import AIMessage, HumanMessage + + new_messages = [] + for message in messages: + if message.is_user: + new_messages.append(HumanMessage(content=message.content)) + else: + new_messages.append(AIMessage(content=message.content)) + return new_messages From 29b9365a41aa752755d5f90295372759e2781a96 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:48:49 -0700 Subject: [PATCH 59/85] Sphinx Docs MVP --- .gitignore | 2 +- sdk/docs/Makefile | 20 ++ sdk/docs/conf.py | 41 +++ sdk/docs/index.rst | 33 ++ sdk/docs/make.bat | 35 ++ sdk/docs/source/honcho.ext.rst | 21 ++ sdk/docs/source/honcho.rst | 53 +++ sdk/docs/source/modules.rst | 7 + sdk/honcho/client.py | 8 +- sdk/honcho/sync_client.py | 40 ++- sdk/poetry.lock | 593 +++++++++++++++++++++++++++++++-- sdk/pyproject.toml | 4 + 12 files changed, 808 insertions(+), 49 deletions(-) create mode 100644 sdk/docs/Makefile create mode 100644 sdk/docs/conf.py create mode 100644 sdk/docs/index.rst create mode 100644 sdk/docs/make.bat create mode 100644 sdk/docs/source/honcho.ext.rst create mode 100644 sdk/docs/source/honcho.rst create mode 100644 sdk/docs/source/modules.rst diff --git a/.gitignore b/.gitignore index 7ac7eb3..3c42912 100644 --- a/.gitignore +++ b/.gitignore @@ -74,7 +74,7 @@ instance/ .scrapy # Sphinx documentation -docs/_build/ +**/docs/_build/ # PyBuilder .pybuilder/ diff --git a/sdk/docs/Makefile b/sdk/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/sdk/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/sdk/docs/conf.py b/sdk/docs/conf.py new file mode 100644 index 0000000..3798508 --- /dev/null +++ b/sdk/docs/conf.py @@ -0,0 +1,41 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import sys + +project = "Honcho" +copyright = "2024, Plastic Labs" +author = "Plastic Labs" + + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../honcho")) +) + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +] +autodoc_member_order = "bysource" + +autosummary_generate = True + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +viewcode_import = "import honcho" + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] diff --git a/sdk/docs/index.rst b/sdk/docs/index.rst new file mode 100644 index 0000000..70458a3 --- /dev/null +++ b/sdk/docs/index.rst @@ -0,0 +1,33 @@ +.. Honcho documentation master file, created by + sphinx-quickstart on Wed Mar 13 11:35:31 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Honcho's documentation! +================================== + +.. .. automodule:: honcho.client +.. :members: + +.. .. automodule:: honcho.sync_client +.. :members: + +.. .. automodule:: honcho.ext.langchain +.. :members: + +.. .. autosummary:: +.. :toctree: _autosummary +.. :recursive: + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + source/modules + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/sdk/docs/make.bat b/sdk/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/sdk/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/sdk/docs/source/honcho.ext.rst b/sdk/docs/source/honcho.ext.rst new file mode 100644 index 0000000..44831b9 --- /dev/null +++ b/sdk/docs/source/honcho.ext.rst @@ -0,0 +1,21 @@ +honcho.ext package +================== + +Submodules +---------- + +honcho.ext.langchain module +--------------------------- + +.. automodule:: honcho.ext.langchain + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: honcho.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/sdk/docs/source/honcho.rst b/sdk/docs/source/honcho.rst new file mode 100644 index 0000000..517ea98 --- /dev/null +++ b/sdk/docs/source/honcho.rst @@ -0,0 +1,53 @@ +honcho package +============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + honcho.ext + +Submodules +---------- + +honcho.cache module +------------------- + +.. automodule:: honcho.cache + :members: + :undoc-members: + :show-inheritance: + +honcho.client module +-------------------- + +.. automodule:: honcho.client + :members: + :undoc-members: + :show-inheritance: + +honcho.schemas module +--------------------- + +.. automodule:: honcho.schemas + :members: + :undoc-members: + :show-inheritance: + +honcho.sync\_client module +-------------------------- + +.. automodule:: honcho.sync_client + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: honcho + :members: + :undoc-members: + :show-inheritance: diff --git a/sdk/docs/source/modules.rst b/sdk/docs/source/modules.rst new file mode 100644 index 0000000..640054b --- /dev/null +++ b/sdk/docs/source/modules.rst @@ -0,0 +1,7 @@ +honcho +====== + +.. toctree:: + :maxdepth: 4 + + honcho diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 427bbef..24659cc 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -99,9 +99,9 @@ def __init__( async def next(self): """Get the next page of results + Returns: - AsyncGetSessionPage | None: Next Page of Results or None if there - are no more sessions to retreive from a query + AsyncGetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query """ if self.page >= self.pages: return None @@ -141,6 +141,7 @@ def __init__(self, response: dict, session: AsyncSession, reverse: bool): async def next(self): """Get the next page of results + Returns: AsyncGetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query @@ -189,6 +190,7 @@ def __init__( async def next(self): """Get the next page of results + Returns: AsyncGetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query @@ -232,6 +234,7 @@ def __init__(self, response: dict, collection, reverse: bool) -> None: async def next(self): """Get the next page of results + Returns: AsyncGetDocumentPage | None: Next Page of Results or None if there are no more sessions to retreive from a query @@ -269,6 +272,7 @@ def __init__(self, response: dict, user: AsyncUser, reverse: bool): async def next(self): """Get the next page of results + Returns: AsyncGetCollectionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index f6e8e94..2d7b260 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -99,9 +99,9 @@ def __init__( def next(self): """Get the next page of results + Returns: - GetSessionPage | None: Next Page of Results or None if there - are no more sessions to retreive from a query + GetSessionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query """ if self.page >= self.pages: return None @@ -141,13 +141,16 @@ def __init__(self, response: dict, session: Session, reverse: bool): def next(self): """Get the next page of results + Returns: GetMessagePage | None: Next Page of Results or None if there are no more messages to retreive from a query """ if self.page >= self.pages: return None - return self.session.get_messages((self.page + 1), self.page_size, self.reverse) + return self.session.get_messages( + (self.page + 1), self.page_size, self.reverse + ) class GetMetamessagePage(GetPage): @@ -187,6 +190,7 @@ def __init__( def next(self): """Get the next page of results + Returns: GetMetamessagePage | None: Next Page of Results or None if there are no more metamessages to retreive from a query @@ -230,6 +234,7 @@ def __init__(self, response: dict, collection, reverse: bool) -> None: def next(self): """Get the next page of results + Returns: GetDocumentPage | None: Next Page of Results or None if there are no more sessions to retreive from a query @@ -267,6 +272,7 @@ def __init__(self, response: dict, user: User, reverse: bool): def next(self): """Get the next page of results + Returns: GetCollectionPage | None: Next Page of Results or None if there are no more sessions to retreive from a query @@ -292,7 +298,9 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict def initialize(self): - res = self.client.get(f"{self.server_url}/apps/get_or_create/{self.app_name}") + res = self.client.get( + f"{self.server_url}/apps/get_or_create/{self.app_name}" + ) res.raise_for_status() data = res.json() self.app_id: uuid.UUID = data["id"] @@ -333,7 +341,9 @@ def create_user(self, name: str, metadata: Optional[dict] = None): if metadata is None: metadata = {} url = f"{self.base_url}/users" - response = self.client.post(url, json={"name": name, "metadata": metadata}) + response = self.client.post( + url, json={"name": name, "metadata": metadata} + ) response.raise_for_status() data = response.json() return User( @@ -383,7 +393,9 @@ def get_or_create_user(self, name: str): created_at=data["created_at"], ) - def get_users(self, page: int = 1, page_size: int = 50, reverse: bool = False): + def get_users( + self, page: int = 1, page_size: int = 50, reverse: bool = False + ): """Get Paginated list of users Args: @@ -473,9 +485,7 @@ def base_url(self): def __str__(self): """String representation of User""" - return ( - f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 - ) + return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 def update(self, metadata: dict): """Updates a user's metadata @@ -849,7 +859,9 @@ def get_messages_generator(self, reverse: bool = False): get_messages_page = new_messages - def create_metamessage(self, message: Message, metamessage_type: str, content: str): + def create_metamessage( + self, message: Message, metamessage_type: str, content: str + ): """Adds a metamessage to a session and links it to a specific message Args: @@ -940,7 +952,9 @@ def get_metamessages( response.raise_for_status() data = response.json() message_id = message.id if message else None - return GetMetamessagePage(data, self, reverse, message_id, metamessage_type) + return GetMetamessagePage( + data, self, reverse, message_id, metamessage_type + ) def get_metamessages_generator( self, @@ -1025,9 +1039,7 @@ def base_url(self): def __str__(self): """String representation of Collection""" - return ( - f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 - ) + return f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 def update(self, name: str): """Update the name of the collection diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 3b53388..989bc10 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,14 +1,27 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] [[package]] name = "anyio" -version = "4.2.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] @@ -22,21 +35,160 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -48,6 +200,7 @@ files = [ name = "coverage" version = "7.4.3" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -108,10 +261,23 @@ files = [ [package.extras] toml = ["tomli"] +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -122,10 +288,29 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "furo" +version = "2024.1.29" +description = "A clean customisable Sphinx documentation theme." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2024.1.29-py3-none-any.whl", hash = "sha256:3548be2cef45a32f8cdc0272d415fcb3e5fa6a0eb4ddfe21df3ecf1fe45a13cf"}, + {file = "furo-2024.1.29.tar.gz", hash = "sha256:4d6b2fe3f10a6e36eb9cc24c1e7beb38d7a23fc7b3c382867503b7fcac8a1e02"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<8.0" +sphinx-basic-ng = "*" + [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -135,13 +320,14 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.4" description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -151,13 +337,14 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -168,20 +355,21 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = "==1.*" +httpcore = ">=1.0.0,<2.0.0" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -189,10 +377,23 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -200,36 +401,143 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -250,37 +558,239 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.4" +version = "0.23.5.post1" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"}, - {file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"}, + {file = "pytest-asyncio-0.23.5.post1.tar.gz", hash = "sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813"}, + {file = "pytest_asyncio-0.23.5.post1-py3-none-any.whl", hash = "sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e"}, ] [package.dependencies] -pytest = ">=7.0.0,<8" +pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "dev" +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 = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" +category = "main" 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"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.8" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.5" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -290,16 +800,35 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e318390f6947b47bc17d2785385ad01fed4496f3f4e09357ea7cabb7b1e401d2" +content-hash = "10e19253c5f33dbed0b26e0fc333ac804fcc459e5a0bf8f2e32277d7efcd264b" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index f548805..c07b0f6 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -16,6 +16,10 @@ pytest = "^7.4.4" pytest-asyncio = "^0.23.4" coverage = "^7.4.3" +[tool.poetry.group.docs.dependencies] +sphinx = "^7.2.6" +furo = "^2024.1.29" + [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example select = [ From c4b0151edeeaa287ed364e028195ba5497c59fa1 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:03:14 -0700 Subject: [PATCH 60/85] Metadata filtering for all fixes dev-261 --- api/src/crud.py | 101 ++++++++++++++++- api/src/main.py | 120 +++++++++++++++++++- api/src/models.py | 3 + api/src/schemas.py | 55 +++++++++- sdk/honcho/client.py | 225 ++++++++++++++++++++++++++++++++------ sdk/honcho/schemas.py | 37 ++++++- sdk/honcho/sync_client.py | 225 ++++++++++++++++++++++++++++++++------ sdk/tests/test_async.py | 39 +++++++ sdk/tests/test_sync.py | 37 +++++++ 9 files changed, 755 insertions(+), 87 deletions(-) diff --git a/api/src/crud.py b/api/src/crud.py index 27dc56f..2b342e5 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -114,9 +114,16 @@ async def get_user_by_name( async def get_users( - db: AsyncSession, app_id: uuid.UUID, reverse: bool = False + db: AsyncSession, + app_id: uuid.UUID, + reverse: bool = False, + filter: Optional[dict] = None, ) -> Select: stmt = select(models.User).where(models.User.app_id == app_id) + + if filter is not None: + stmt = stmt.where(models.User.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.User.created_at.desc()) else: @@ -180,6 +187,7 @@ async def get_sessions( location_id: Optional[str] = None, reverse: Optional[bool] = False, is_active: Optional[bool] = False, + filter: Optional[dict] = None, ) -> Select: stmt = ( select(models.Session) @@ -191,6 +199,9 @@ async def get_sessions( if is_active: stmt = stmt.where(models.Session.is_active.is_(True)) + if filter is not None: + stmt = stmt.where(models.Session.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.Session.created_at.desc()) else: @@ -205,9 +216,12 @@ async def get_sessions( async def create_session( db: AsyncSession, session: schemas.SessionCreate, - app_id: uuid.UUID, # TODO check if app id is associated with the right user + app_id: uuid.UUID, user_id: uuid.UUID, ) -> models.Session: + honcho_user = await get_user(db, app_id=app_id, user_id=user_id) + if honcho_user is None: + raise ValueError("User not found") honcho_session = models.Session( user_id=user_id, location_id=session.location_id, @@ -281,6 +295,7 @@ async def create_message( session_id=session_id, is_user=message.is_user, content=message.content, + h_metadata=message.metadata, ) db.add(honcho_message) await db.commit() @@ -294,6 +309,7 @@ async def get_messages( user_id: uuid.UUID, session_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[dict] = None, ) -> Select: stmt = ( select(models.Message) @@ -305,6 +321,9 @@ async def get_messages( .where(models.Message.session_id == session_id) ) + if filter is not None: + stmt = stmt.where(models.Message.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.Message.created_at.desc()) else: @@ -334,6 +353,28 @@ async def get_message( return result.scalar_one_or_none() +async def update_message( + db: AsyncSession, + message: schemas.MessageUpdate, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, +) -> bool: + honcho_message = await get_message( + db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id + ) + if honcho_message is None: + raise ValueError("Message not found or does not belong to user") + if ( + message.metadata is not None + ): # Need to explicitly be there won't make it empty by default + honcho_message.h_metadata = message.metadata + await db.commit() + await db.refresh(honcho_message) + return honcho_message + + ######################################################## # metamessage methods ######################################################## @@ -360,6 +401,7 @@ async def create_metamessage( message_id=metamessage.message_id, metamessage_type=metamessage.metamessage_type, content=metamessage.content, + h_metadata=metamessage.metadata, ) db.add(honcho_metamessage) @@ -375,6 +417,7 @@ async def get_metamessages( session_id: uuid.UUID, message_id: Optional[uuid.UUID], metamessage_type: Optional[str] = None, + filter: Optional[dict] = None, reverse: Optional[bool] = False, ) -> Select: stmt = ( @@ -394,6 +437,9 @@ async def get_metamessages( if metamessage_type is not None: stmt = stmt.where(models.Metamessage.metamessage_type == metamessage_type) + if filter is not None: + stmt = stmt.where(models.Metamessage.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.Metamessage.created_at.desc()) else: @@ -426,6 +472,35 @@ async def get_metamessage( return result.scalar_one_or_none() +async def update_metamessage( + db: AsyncSession, + metamessage: schemas.MetamessageUpdate, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + metamessage_id: uuid.UUID, +) -> bool: + honcho_metamessage = await get_metamessage( + db, + app_id=app_id, + session_id=session_id, + user_id=user_id, + message_id=metamessage.message_id, + metamessage_id=metamessage_id, + ) + if honcho_metamessage is None: + raise ValueError("Metamessage not found or does not belong to user") + if ( + metamessage.metadata is not None + ): # Need to explicitly be there won't make it empty by default + honcho_metamessage.h_metadata = metamessage.metadata + if metamessage.metamessage_type is not None: + honcho_metamessage.metamessage_type = metamessage.metamessage_type + await db.commit() + await db.refresh(honcho_metamessage) + return honcho_metamessage + + ######################################################## # collection methods ######################################################## @@ -438,6 +513,7 @@ async def get_collections( app_id: uuid.UUID, user_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[dict] = None, ) -> Select: """Get a distinct list of the names of collections associated with a user""" stmt = ( @@ -447,6 +523,9 @@ async def get_collections( .where(models.User.id == user_id) ) + if filter is not None: + stmt = stmt.where(models.Collection.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.Collection.created_at.desc()) else: @@ -494,6 +573,7 @@ async def create_collection( honcho_collection = models.Collection( user_id=user_id, name=collection.name, + h_metadata=collection.metadata, ) try: db.add(honcho_collection) @@ -517,6 +597,8 @@ async def update_collection( ) if honcho_collection is None: raise ValueError("collection not found or does not belong to user") + if collection.metadata is not None: + honcho_collection.h_metadata = collection.metadata try: honcho_collection.name = collection.name await db.commit() @@ -563,6 +645,7 @@ async def get_documents( user_id: uuid.UUID, collection_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[dict] = None, ) -> Select: stmt = ( select(models.Document) @@ -573,6 +656,9 @@ async def get_documents( .where(models.Document.collection_id == collection_id) ) + if filter is not None: + stmt = stmt.where(models.Document.h_metadata.contains(filter)) + if reverse: stmt = stmt.order_by(models.Document.created_at.desc()) else: @@ -609,6 +695,7 @@ async def query_documents( user_id: uuid.UUID, collection_id: uuid.UUID, query: str, + filter: Optional[dict] = None, top_k: int = 5, ) -> Sequence[models.Document]: response = openai_client.embeddings.create( @@ -622,11 +709,13 @@ async def query_documents( .where(models.User.app_id == app_id) .where(models.User.id == user_id) .where(models.Document.collection_id == collection_id) - .order_by(models.Document.embedding.cosine_distance(embedding_query)) - .limit(top_k) + # .limit(top_k) + ) + if filter is not None: + stmt = stmt.where(models.Document.h_metadata.contains(filter)) + stmt = stmt.limit(top_k).order_by( + models.Document.embedding.cosine_distance(embedding_query) ) - # if metadata is not None: - # stmt = stmt.where(models.Document.h_metadata.contains(metadata)) result = await db.execute(stmt) return result.scalars().all() diff --git a/api/src/main.py b/api/src/main.py index 19269dc..9dcf7db 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -384,6 +384,7 @@ async def get_users( request: Request, app_id: uuid.UUID, reverse: bool = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): """Get All Users for an App @@ -396,7 +397,13 @@ async def get_users( list[schemas.User]: List of User objects """ - return await paginate(db, await crud.get_users(db, app_id=app_id, reverse=reverse)) + data = None + if filter is not None: + data = json.loads(filter) + + return await paginate( + db, await crud.get_users(db, app_id=app_id, reverse=reverse, filter=data) + ) @app.get("/apps/{app_id}/users/{name}", response_model=schemas.User) @@ -479,6 +486,7 @@ async def get_sessions( location_id: Optional[str] = None, is_active: Optional[bool] = False, reverse: Optional[bool] = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): """Get All Sessions for a User @@ -494,6 +502,11 @@ async def get_sessions( list[schemas.Session]: List of Session objects """ + + data = None + if filter is not None: + data = json.loads(filter) + return await paginate( db, await crud.get_sessions( @@ -503,6 +516,7 @@ async def get_sessions( location_id=location_id, reverse=reverse, is_active=is_active, + filter=data, ), ) @@ -557,9 +571,7 @@ async def update_session( """ if session.metadata is None: - raise HTTPException( - status_code=400, detail="Session metadata cannot be empty" - ) # TODO TEST if I can set the metadata to be blank with this + raise HTTPException(status_code=400, detail="Session metadata cannot be empty") try: return await crud.update_session( db, app_id=app_id, user_id=user_id, session_id=session_id, session=session @@ -676,6 +688,7 @@ async def get_messages( user_id: uuid.UUID, session_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): """Get all messages for a session @@ -695,6 +708,9 @@ async def get_messages( """ try: + data = None + if filter is not None: + data = json.loads(filter) return await paginate( db, await crud.get_messages( @@ -702,6 +718,7 @@ async def get_messages( app_id=app_id, user_id=user_id, session_id=session_id, + filter=data, reverse=reverse, ), ) @@ -729,6 +746,34 @@ async def get_message( return honcho_message +@router.put( + "sessions/{session_id}/messages/{message_id}", response_model=schemas.Message +) +async def update_message( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, + message: schemas.MessageUpdate, + db: AsyncSession = Depends(get_db), +): + """Update's the metadata of a message""" + if message.metadata is None: + raise HTTPException(status_code=400, detail="Message metadata cannot be empty") + try: + return await crud.update_message( + db, + message=message, + app_id=app_id, + user_id=user_id, + session_id=session_id, + message_id=message_id, + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + ######################################################## # metamessage routes ######################################################## @@ -783,6 +828,7 @@ async def get_metamessages( message_id: Optional[uuid.UUID] = None, metamessage_type: Optional[str] = None, reverse: Optional[bool] = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): """Get all messages for a session @@ -802,6 +848,9 @@ async def get_metamessages( """ try: + data = None + if filter is not None: + data = json.loads(filter) return await paginate( db, await crud.get_metamessages( @@ -811,6 +860,7 @@ async def get_metamessages( session_id=session_id, message_id=message_id, metamessage_type=metamessage_type, + filter=data, reverse=reverse, ), ) @@ -831,7 +881,7 @@ async def get_metamessage( metamessage_id: uuid.UUID, db: AsyncSession = Depends(get_db), ): - """Get a specific session for a user by ID + """Get a specific Metamessage by ID Args: app_id (uuid.UUID): The ID of the app representing the client application using @@ -858,6 +908,37 @@ async def get_metamessage( return honcho_metamessage +@router.put( + "sessions/{session_id}/metamessages/{metamessage_id}", + response_model=schemas.Metamessage, +) +async def update_metamessage( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + metamessage_id: uuid.UUID, + metamessage: schemas.MetamessageUpdate, + db: AsyncSession = Depends(get_db), +): + """Update's the metadata of a metamessage""" + if metamessage.metadata is None: + raise HTTPException( + status_code=400, detail="Metamessage metadata cannot be empty" + ) + try: + return await crud.update_metamessage( + db, + metamessage=metamessage, + app_id=app_id, + user_id=user_id, + session_id=session_id, + metamessage_id=metamessage_id, + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + ######################################################## # collection routes ######################################################## @@ -869,11 +950,28 @@ async def get_collections( app_id: uuid.UUID, user_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): + """Get All Collections for a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client + application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + + Returns: + list[schemas.Collection]: List of Collection objects + + """ + data = None + if filter is not None: + data = json.loads(filter) return await paginate( db, - await crud.get_collections(db, app_id=app_id, user_id=user_id, reverse=reverse), + await crud.get_collections( + db, app_id=app_id, user_id=user_id, filter=data, reverse=reverse + ), ) @@ -994,9 +1092,13 @@ async def get_documents( user_id: uuid.UUID, collection_id: uuid.UUID, reverse: Optional[bool] = False, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): try: + data = None + if filter is not None: + data = json.loads(filter) return await paginate( db, await crud.get_documents( @@ -1004,6 +1106,7 @@ async def get_documents( app_id=app_id, user_id=user_id, collection_id=collection_id, + filter=data, reverse=reverse, ), ) @@ -1053,16 +1156,21 @@ async def query_documents( collection_id: uuid.UUID, query: str, top_k: int = 5, + filter: Optional[str] = None, db: AsyncSession = Depends(get_db), ): if top_k is not None and top_k > 50: top_k = 50 # TODO see if we need to paginate this + data = None + if filter is not None: + data = json.loads(filter) return await crud.query_documents( db=db, app_id=app_id, user_id=user_id, collection_id=collection_id, query=query, + filter=data, top_k=top_k, ) diff --git a/api/src/models.py b/api/src/models.py index ebd7cdd..0790b73 100644 --- a/api/src/models.py +++ b/api/src/models.py @@ -87,6 +87,7 @@ class Message(Base): session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("sessions.id"), index=True) is_user: Mapped[bool] content: Mapped[str] = mapped_column(String(65535)) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), default=datetime.datetime.utcnow @@ -111,6 +112,7 @@ class Metamessage(Base): created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), default=datetime.datetime.utcnow ) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) def __repr__(self) -> str: return f"Metamessages(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content[10:]})" @@ -125,6 +127,7 @@ class Collection(Base): created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), default=datetime.datetime.utcnow ) + h_metadata: Mapped[dict] = mapped_column("metadata", ColumnType, default={}) documents = relationship( "Document", back_populates="collection", cascade="all, delete, delete-orphan" ) diff --git a/api/src/schemas.py b/api/src/schemas.py index a0a779c..eb09713 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -1,7 +1,8 @@ -from pydantic import BaseModel, validator import datetime import uuid +from pydantic import BaseModel, validator + class AppBase(BaseModel): pass @@ -68,21 +69,37 @@ class Config: class MessageBase(BaseModel): + pass + + +class MessageCreate(MessageBase): content: str is_user: bool + metadata: dict | None = {} -class MessageCreate(MessageBase): - pass +class MessageUpdate(MessageBase): + metadata: dict | None = None class Message(MessageBase): + content: str + is_user: bool session_id: uuid.UUID id: uuid.UUID + h_metadata: dict + metadata: dict created_at: datetime.datetime + @validator("metadata", pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if "h_metadata" in values: + return values["h_metadata"] + return {} + class Config: from_attributes = True + schema_extra = {"exclude": ["h_metadata"]} class SessionBase(BaseModel): @@ -120,21 +137,40 @@ class Config: class MetamessageBase(BaseModel): + pass + + +class MetamessageCreate(MetamessageBase): metamessage_type: str content: str + message_id: uuid.UUID + metadata: dict | None = {} -class MetamessageCreate(MetamessageBase): +class MetamessageUpdate(MetamessageBase): message_id: uuid.UUID + metamessage_type: str | None = None + metadata: dict | None = None class Metamessage(MetamessageBase): + metamessage_type: str + content: str id: uuid.UUID message_id: uuid.UUID + h_metadata: dict + metadata: dict created_at: datetime.datetime + @validator("metadata", pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if "h_metadata" in values: + return values["h_metadata"] + return {} + class Config: from_attributes = True + schema_extra = {"exclude": ["h_metadata"]} class CollectionBase(BaseModel): @@ -143,20 +179,31 @@ class CollectionBase(BaseModel): class CollectionCreate(CollectionBase): name: str + metadata: dict | None = {} class CollectionUpdate(CollectionBase): name: str + metadata: dict | None = None class Collection(CollectionBase): id: uuid.UUID name: str user_id: uuid.UUID + h_metadata: dict + metadata: dict created_at: datetime.datetime + @validator("metadata", pre=True, allow_reuse=True) + def fetch_h_metadata(cls, value, values): + if "h_metadata" in values: + return values["h_metadata"] + return {} + class Config: from_attributes = True + schema_extra = {"exclude": ["h_metadata"]} class DocumentBase(BaseModel): diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 24659cc..1bc7262 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import json import uuid from typing import Optional @@ -32,7 +33,13 @@ async def next(self): class AsyncGetUserPage(AsyncGetPage): """Paginated Results for Get User Requests""" - def __init__(self, response: dict, honcho: AsyncHoncho, reverse: bool): + def __init__( + self, + response: dict, + honcho: AsyncHoncho, + filter: Optional[dict], + reverse: bool, + ): """Constructor for Page Result from User Get Request Args: @@ -42,6 +49,7 @@ def __init__(self, response: dict, honcho: AsyncHoncho, reverse: bool): """ super().__init__(response) self.honcho = honcho + self.filter = filter self.reverse = reverse self.items = [ AsyncUser( @@ -57,7 +65,10 @@ async def next(self): if self.page >= self.pages: return None return await self.honcho.get_users( - page=(self.page + 1), page_size=self.page_size, reverse=self.reverse + filter=self.filter, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, ) @@ -70,6 +81,7 @@ def __init__( user: AsyncUser, reverse: bool, location_id: Optional[str], + filter: Optional[dict], is_active: bool, ): """Constructor for Page Result from Session Get Request @@ -85,6 +97,7 @@ def __init__( self.location_id = location_id self.reverse = reverse self.is_active = is_active + self.filter = filter self.items = [ AsyncSession( user=user, @@ -107,6 +120,7 @@ async def next(self): return None return await self.user.get_sessions( location_id=self.location_id, + filter=self.filter, page=(self.page + 1), page_size=self.page_size, reverse=self.reverse, @@ -117,7 +131,13 @@ async def next(self): class AsyncGetMessagePage(AsyncGetPage): """Paginated Results for Get Session Requests""" - def __init__(self, response: dict, session: AsyncSession, reverse: bool): + def __init__( + self, + response: dict, + session: AsyncSession, + filter: Optional[dict], + reverse: bool, + ): """Constructor for Page Result from Session Get Request Args: @@ -127,6 +147,7 @@ def __init__(self, response: dict, session: AsyncSession, reverse: bool): """ super().__init__(response) self.session = session + self.filter = filter self.reverse = reverse self.items = [ Message( @@ -134,6 +155,7 @@ def __init__(self, response: dict, session: AsyncSession, reverse: bool): id=message["id"], is_user=message["is_user"], content=message["content"], + metadata=message["metadata"], created_at=message["created_at"], ) for message in response["items"] @@ -149,7 +171,7 @@ async def next(self): if self.page >= self.pages: return None return await self.session.get_messages( - (self.page + 1), self.page_size, self.reverse + self.filter, (self.page + 1), self.page_size, self.reverse ) @@ -158,6 +180,7 @@ def __init__( self, response: dict, session, + filter: Optional[dict], reverse: bool, message_id: Optional[uuid.UUID], metamessage_type: Optional[str], @@ -176,11 +199,13 @@ def __init__( self.session = session self.message_id = message_id self.metamessage_type = metamessage_type + self.filter = filter self.reverse = reverse self.items = [ Metamessage( id=metamessage["id"], message_id=metamessage["message_id"], + metadata=metamessage["metadata"], metamessage_type=metamessage["metamessage_type"], content=metamessage["content"], created_at=metamessage["created_at"], @@ -199,6 +224,7 @@ async def next(self): return None return await self.session.get_metamessages( metamessage_type=self.metamessage_type, + filter=self.filter, message=self.message_id, page=(self.page + 1), page_size=self.page_size, @@ -209,7 +235,9 @@ async def next(self): class AsyncGetDocumentPage(AsyncGetPage): """Paginated results for Get Document requests""" - def __init__(self, response: dict, collection, reverse: bool) -> None: + def __init__( + self, response: dict, collection, filter: Optional[dict], reverse: bool + ) -> None: """Constructor for Page Result from Document Get Request Args: @@ -220,6 +248,7 @@ def __init__(self, response: dict, collection, reverse: bool) -> None: """ super().__init__(response) self.collection = collection + self.filter = filter self.reverse = reverse self.items = [ Document( @@ -242,14 +271,19 @@ async def next(self): if self.page >= self.pages: return None return await self.collection.get_documents( - page=self.page + 1, page_size=self.page_size, reverse=self.reverse + filter=self.filter, + page=self.page + 1, + page_size=self.page_size, + reverse=self.reverse, ) class AsyncGetCollectionPage(AsyncGetPage): """Paginated results for Get Collection requests""" - def __init__(self, response: dict, user: AsyncUser, reverse: bool): + def __init__( + self, response: dict, user: AsyncUser, filter: Optional[dict], reverse: bool + ): """Constructor for page result from Get Collection Request Args: @@ -259,12 +293,14 @@ def __init__(self, response: dict, user: AsyncUser, reverse: bool): """ super().__init__(response) self.user = user + self.filter = filter self.reverse = reverse self.items = [ AsyncCollection( user=user, id=collection["id"], name=collection["name"], + metadata=collection["metadata"], created_at=collection["created_at"], ) for collection in response["items"] @@ -280,6 +316,7 @@ async def next(self): if self.page >= self.pages: return None return await self.user.get_collections( + filter=self.filter, page=self.page + 1, page_size=self.page_size, reverse=self.reverse, @@ -394,7 +431,11 @@ async def get_or_create_user(self, name: str): ) async def get_users( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ): """Get Paginated list of users @@ -413,13 +454,17 @@ async def get_users( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = await self.client.get(url, params=params) response.raise_for_status() data = response.json() - return AsyncGetUserPage(data, self, reverse) + return AsyncGetUserPage(data, self, filter, reverse) async def get_users_generator( self, + filter: Optional[dict] = None, reverse: bool = False, ): """Shortcut Generator for get_users. Generator to iterate through @@ -434,7 +479,7 @@ async def get_users_generator( """ page = 1 page_size = 50 - get_user_response = await self.get_users(page, page_size, reverse) + get_user_response = await self.get_users(filter, page, page_size, reverse) while True: for session in get_user_response.items: yield session @@ -533,6 +578,7 @@ async def get_session(self, session_id: uuid.UUID): async def get_sessions( self, location_id: Optional[str] = None, + filter: Optional[dict] = None, page: int = 1, page_size: int = 50, reverse: bool = False, @@ -565,16 +611,20 @@ async def get_sessions( } if location_id: params["location_id"] = location_id + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = await self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return AsyncGetSessionPage(data, self, reverse, location_id, is_active) + return AsyncGetSessionPage(data, self, reverse, location_id, filter, is_active) async def get_sessions_generator( self, location_id: Optional[str] = None, reverse: bool = False, is_active: bool = False, + filter: Optional[dict] = None, ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -592,7 +642,7 @@ async def get_sessions_generator( page = 1 page_size = 50 get_session_response = await self.get_sessions( - location_id, page, page_size, reverse, is_active + location_id, filter, page, page_size, reverse, is_active ) while True: for session in get_session_response.items: @@ -637,6 +687,7 @@ async def create_session( async def create_collection( self, name: str, + metadata: Optional[dict] = None, ): """Create a collection for a user @@ -647,7 +698,9 @@ async def create_collection( AsyncCollection: The Collection object of the new Collection """ - data = {"name": name} + if metadata is None: + metadata = {} + data = {"name": name, "metadata": metadata} url = f"{self.base_url}/collections" response = await self.honcho.client.post(url, json=data) response.raise_for_status() @@ -656,6 +709,7 @@ async def create_collection( self, id=data["id"], name=name, + metadata=metadata, created_at=data["created_at"], ) @@ -677,11 +731,16 @@ async def get_collection(self, name: str): user=self, id=data["id"], name=data["name"], + metadata=data["metadata"], created_at=data["created_at"], ) async def get_collections( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ): """Return collections associated with a user paginated @@ -701,12 +760,17 @@ async def get_collections( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = await self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return AsyncGetCollectionPage(data, self, reverse) + return AsyncGetCollectionPage(data, self, filter, reverse) - async def get_collections_generator(self, reverse: bool = False): + async def get_collections_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -719,7 +783,9 @@ async def get_collections_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_collection_response = await self.get_collections(page, page_size, reverse) + get_collection_response = await self.get_collections( + filter, page, page_size, reverse + ) while True: for collection in get_collection_response.items: yield collection @@ -765,7 +831,9 @@ def is_active(self): """Returns whether the session is active - made property to prevent tampering""" return self._is_active - async def create_message(self, is_user: bool, content: str): + async def create_message( + self, is_user: bool, content: str, metadata: Optional[dict] = None + ): """Adds a message to the session Args: @@ -778,7 +846,9 @@ async def create_message(self, is_user: bool, content: str): """ if not self.is_active: raise Exception("Session is inactive") - data = {"is_user": is_user, "content": content} + if metadata is None: + metadata = {} + data = {"is_user": is_user, "content": content, "metadata": metadata} url = f"{self.base_url}/messages" response = await self.user.honcho.client.post(url, json=data) response.raise_for_status() @@ -788,6 +858,7 @@ async def create_message(self, is_user: bool, content: str): id=data["id"], is_user=is_user, content=content, + metadata=metadata, created_at=data["created_at"], ) @@ -810,11 +881,16 @@ async def get_message(self, message_id: uuid.UUID) -> Message: id=data["id"], is_user=data["is_user"], content=data["content"], + metadata=data["metadata"], created_at=data["created_at"], ) async def get_messages( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ) -> AsyncGetMessagePage: """Get all messages for a session @@ -833,12 +909,17 @@ async def get_messages( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return AsyncGetMessagePage(data, self, reverse) + return AsyncGetMessagePage(data, self, filter, reverse) - async def get_messages_generator(self, reverse: bool = False): + async def get_messages_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app @@ -848,7 +929,7 @@ async def get_messages_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_messages_page = await self.get_messages(page, page_size, reverse) + get_messages_page = await self.get_messages(filter, page, page_size, reverse) while True: for message in get_messages_page.items: yield message @@ -860,7 +941,11 @@ async def get_messages_generator(self, reverse: bool = False): get_messages_page = new_messages async def create_metamessage( - self, message: Message, metamessage_type: str, content: str + self, + message: Message, + metamessage_type: str, + content: str, + metadata: Optional[dict] = None, ): """Adds a metamessage to a session and links it to a specific message @@ -875,10 +960,13 @@ async def create_metamessage( """ if not self.is_active: raise Exception("Session is inactive") + if metadata is None: + metadata = {} data = { "metamessage_type": metamessage_type, "content": content, "message_id": message.id, + "metadata": metadata, } url = f"{self.base_url}/metamessages" response = await self.user.honcho.client.post(url, json=data) @@ -889,6 +977,7 @@ async def create_metamessage( message_id=message.id, metamessage_type=metamessage_type, content=content, + metadata=metadata, created_at=data["created_at"], ) @@ -911,6 +1000,7 @@ async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], + metadata=data["metadata"], created_at=data["created_at"], ) @@ -918,6 +1008,7 @@ async def get_metamessages( self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, + filter: Optional[dict] = None, page: int = 1, page_size: int = 50, reverse: bool = False, @@ -948,18 +1039,22 @@ async def get_metamessages( if message: # url += f"&message_id={message.id}" params["message_id"] = message.id + if filter is not None: + json_metadata = json.dumps(filter) + params["filter"] = json_metadata response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() message_id = message.id if message else None return AsyncGetMetamessagePage( - data, self, reverse, message_id, metamessage_type + data, self, filter, reverse, message_id, metamessage_type ) async def get_metamessages_generator( self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, + filter: Optional[dict] = None, reverse: bool = False, ): """Shortcut Generator for get_metamessages. Generator to iterate @@ -978,6 +1073,7 @@ async def get_metamessages_generator( get_metamessages_page = await self.get_metamessages( metamessage_type=metamessage_type, message=message, + filter=filter, page=page, page_size=page_size, reverse=reverse, @@ -1008,6 +1104,50 @@ async def update(self, metadata: dict): self.metadata = metadata return success + async def update_message(self, message: Message, metadata: dict): + """Update the metadata of a message + + Args: + message (Message): The message to update + metadata (dict): The new metadata for the message + + Returns: + boolean: Whether the message was successfully updated + """ + info = {"metadata": metadata} + url = f"{self.base_url}/messages/{message.id}" + response = await self.user.honcho.client.put(url, json=info) + success = response.status_code < 400 + message.metadata = metadata + return success + + async def update_metamessage( + self, + metamessage: Metamessage, + metamessage_type: Optional[str], + metadata: Optional[dict], + ): + """Update the metadata of a metamessage + + Args: + metamessage (Metamessage): The metamessage to update + metadata (dict): The new metadata for the metamessage + + Returns: + boolean: Whether the metamessage was successfully updated + """ + if metadata is None and metamessage_type is None: + raise ValueError("metadata and metamessage_type cannot both be None") + info = {"metamessage_type": metamessage_type, "metadata": metadata} + url = f"{self.base_url}/metamessages/{metamessage.id}" + response = await self.user.honcho.client.put(url, json=info) + success = response.status_code < 400 + if metamessage_type is not None: + metamessage.metamessage_type = metamessage_type + if metadata is not None: + metamessage.metadata = metadata + return success + async def close(self): """Closes a session by marking it as inactive""" url = f"{self.base_url}" @@ -1024,12 +1164,14 @@ def __init__( user: AsyncUser, id: uuid.UUID, name: str, + metadata: dict, created_at: datetime.datetime, ): """Constructor for Collection""" self.user = user self.id: uuid.UUID = id self.name: str = name + self.metadata: dict = metadata self.created_at: datetime.datetime = created_at @property @@ -1041,7 +1183,7 @@ def __str__(self): """String representation of Collection""" return f"AsyncCollection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 - async def update(self, name: str): + async def update(self, name: Optional[str] = None, metadata: Optional[dict] = None): """Update the name of the collection Args: @@ -1050,12 +1192,17 @@ async def update(self, name: str): Returns: boolean: Whether the session was successfully updated """ - info = {"name": name} + if metadata is None and name is None: + raise ValueError("metadata and name cannot both be None") + info = {"name": name, "metadata": metadata} url = f"{self.base_url}" response = await self.user.honcho.client.put(url, json=info) response.raise_for_status() success = response.status_code < 400 - self.name = name + if name is not None: + self.name = name + if metadata is not None: + self.metadata = metadata return success async def delete(self): @@ -1113,7 +1260,11 @@ async def get_document(self, document_id: uuid.UUID) -> Document: ) async def get_documents( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ) -> AsyncGetDocumentPage: """Get all documents for a collection @@ -1132,12 +1283,17 @@ async def get_documents( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = await self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return AsyncGetDocumentPage(data, self, reverse) + return AsyncGetDocumentPage(data, self, filter, reverse) - async def get_documents_generator(self, reverse: bool = False): + async def get_documents_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app @@ -1147,7 +1303,7 @@ async def get_documents_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_documents_page = await self.get_documents(page, page_size, reverse) + get_documents_page = await self.get_documents(filter, page, page_size, reverse) while True: for document in get_documents_page.items: yield document @@ -1185,7 +1341,10 @@ async def query(self, query: str, top_k: int = 5) -> list[Document]: return data async def update_document( - self, document: Document, content: Optional[str], metadata: Optional[dict] + self, + document: Document, + content: Optional[str] = None, + metadata: Optional[dict] = None, ) -> Document: """Update a document in the collection diff --git a/sdk/honcho/schemas.py b/sdk/honcho/schemas.py index b5f74d2..1a0a2fe 100644 --- a/sdk/honcho/schemas.py +++ b/sdk/honcho/schemas.py @@ -1,32 +1,60 @@ import uuid import datetime + class Message: - def __init__(self, session_id: uuid.UUID, id: uuid.UUID, is_user: bool, content: str, created_at: datetime.datetime): + def __init__( + self, + session_id: uuid.UUID, + id: uuid.UUID, + is_user: bool, + content: str, + metadata: dict, + created_at: datetime.datetime, + ): """Constructor for Message""" self.session_id = session_id self.id = id self.is_user = is_user self.content = content + self.metadata = metadata self.created_at = created_at def __str__(self): return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" + class Metamessage: - def __init__(self, id: uuid.UUID, message_id: uuid.UUID, metamessage_type: str, content: str, created_at: datetime.datetime): + def __init__( + self, + id: uuid.UUID, + message_id: uuid.UUID, + metamessage_type: str, + content: str, + metadata: dict, + created_at: datetime.datetime, + ): """Constructor for Metamessage""" self.id = id self.message_id = message_id self.metamessage_type = metamessage_type self.content = content + self.metadata = metadata self.created_at = created_at def __str__(self): return f"Metamessage(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content})" - + + class Document: - def __init__(self, id: uuid.UUID, collection_id: uuid.UUID, content: str, metadata: dict, created_at: datetime.datetime): + def __init__( + self, + id: uuid.UUID, + collection_id: uuid.UUID, + content: str, + metadata: dict, + created_at: datetime.datetime, + ): """Constructor for Document""" self.collection_id = collection_id self.id = id @@ -36,4 +64,3 @@ def __init__(self, id: uuid.UUID, collection_id: uuid.UUID, content: str, metada def __str__(self) -> str: return f"Document(id={self.id}, metadata={self.metadata}, content={self.content}, created_at={self.created_at})" - diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 2d7b260..58fe0eb 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import json import uuid from typing import Optional @@ -32,7 +33,13 @@ def next(self): class GetUserPage(GetPage): """Paginated Results for Get User Requests""" - def __init__(self, response: dict, honcho: Honcho, reverse: bool): + def __init__( + self, + response: dict, + honcho: Honcho, + filter: Optional[dict], + reverse: bool, + ): """Constructor for Page Result from User Get Request Args: @@ -42,6 +49,7 @@ def __init__(self, response: dict, honcho: Honcho, reverse: bool): """ super().__init__(response) self.honcho = honcho + self.filter = filter self.reverse = reverse self.items = [ User( @@ -57,7 +65,10 @@ def next(self): if self.page >= self.pages: return None return self.honcho.get_users( - page=(self.page + 1), page_size=self.page_size, reverse=self.reverse + filter=self.filter, + page=(self.page + 1), + page_size=self.page_size, + reverse=self.reverse, ) @@ -70,6 +81,7 @@ def __init__( user: User, reverse: bool, location_id: Optional[str], + filter: Optional[dict], is_active: bool, ): """Constructor for Page Result from Session Get Request @@ -85,6 +97,7 @@ def __init__( self.location_id = location_id self.reverse = reverse self.is_active = is_active + self.filter = filter self.items = [ Session( user=user, @@ -107,6 +120,7 @@ def next(self): return None return self.user.get_sessions( location_id=self.location_id, + filter=self.filter, page=(self.page + 1), page_size=self.page_size, reverse=self.reverse, @@ -117,7 +131,13 @@ def next(self): class GetMessagePage(GetPage): """Paginated Results for Get Session Requests""" - def __init__(self, response: dict, session: Session, reverse: bool): + def __init__( + self, + response: dict, + session: Session, + filter: Optional[dict], + reverse: bool, + ): """Constructor for Page Result from Session Get Request Args: @@ -127,6 +147,7 @@ def __init__(self, response: dict, session: Session, reverse: bool): """ super().__init__(response) self.session = session + self.filter = filter self.reverse = reverse self.items = [ Message( @@ -134,6 +155,7 @@ def __init__(self, response: dict, session: Session, reverse: bool): id=message["id"], is_user=message["is_user"], content=message["content"], + metadata=message["metadata"], created_at=message["created_at"], ) for message in response["items"] @@ -149,7 +171,7 @@ def next(self): if self.page >= self.pages: return None return self.session.get_messages( - (self.page + 1), self.page_size, self.reverse + self.filter, (self.page + 1), self.page_size, self.reverse ) @@ -158,6 +180,7 @@ def __init__( self, response: dict, session, + filter: Optional[dict], reverse: bool, message_id: Optional[uuid.UUID], metamessage_type: Optional[str], @@ -176,11 +199,13 @@ def __init__( self.session = session self.message_id = message_id self.metamessage_type = metamessage_type + self.filter = filter self.reverse = reverse self.items = [ Metamessage( id=metamessage["id"], message_id=metamessage["message_id"], + metadata=metamessage["metadata"], metamessage_type=metamessage["metamessage_type"], content=metamessage["content"], created_at=metamessage["created_at"], @@ -199,6 +224,7 @@ def next(self): return None return self.session.get_metamessages( metamessage_type=self.metamessage_type, + filter=self.filter, message=self.message_id, page=(self.page + 1), page_size=self.page_size, @@ -209,7 +235,9 @@ def next(self): class GetDocumentPage(GetPage): """Paginated results for Get Document requests""" - def __init__(self, response: dict, collection, reverse: bool) -> None: + def __init__( + self, response: dict, collection, filter: Optional[dict], reverse: bool + ) -> None: """Constructor for Page Result from Document Get Request Args: @@ -220,6 +248,7 @@ def __init__(self, response: dict, collection, reverse: bool) -> None: """ super().__init__(response) self.collection = collection + self.filter = filter self.reverse = reverse self.items = [ Document( @@ -242,14 +271,19 @@ def next(self): if self.page >= self.pages: return None return self.collection.get_documents( - page=self.page + 1, page_size=self.page_size, reverse=self.reverse + filter=self.filter, + page=self.page + 1, + page_size=self.page_size, + reverse=self.reverse, ) class GetCollectionPage(GetPage): """Paginated results for Get Collection requests""" - def __init__(self, response: dict, user: User, reverse: bool): + def __init__( + self, response: dict, user: User, filter: Optional[dict], reverse: bool + ): """Constructor for page result from Get Collection Request Args: @@ -259,12 +293,14 @@ def __init__(self, response: dict, user: User, reverse: bool): """ super().__init__(response) self.user = user + self.filter = filter self.reverse = reverse self.items = [ Collection( user=user, id=collection["id"], name=collection["name"], + metadata=collection["metadata"], created_at=collection["created_at"], ) for collection in response["items"] @@ -280,6 +316,7 @@ def next(self): if self.page >= self.pages: return None return self.user.get_collections( + filter=self.filter, page=self.page + 1, page_size=self.page_size, reverse=self.reverse, @@ -394,7 +431,11 @@ def get_or_create_user(self, name: str): ) def get_users( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ): """Get Paginated list of users @@ -413,13 +454,17 @@ def get_users( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = self.client.get(url, params=params) response.raise_for_status() data = response.json() - return GetUserPage(data, self, reverse) + return GetUserPage(data, self, filter, reverse) def get_users_generator( self, + filter: Optional[dict] = None, reverse: bool = False, ): """Shortcut Generator for get_users. Generator to iterate through @@ -434,7 +479,7 @@ def get_users_generator( """ page = 1 page_size = 50 - get_user_response = self.get_users(page, page_size, reverse) + get_user_response = self.get_users(filter, page, page_size, reverse) while True: for session in get_user_response.items: yield session @@ -533,6 +578,7 @@ def get_session(self, session_id: uuid.UUID): def get_sessions( self, location_id: Optional[str] = None, + filter: Optional[dict] = None, page: int = 1, page_size: int = 50, reverse: bool = False, @@ -565,16 +611,20 @@ def get_sessions( } if location_id: params["location_id"] = location_id + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return GetSessionPage(data, self, reverse, location_id, is_active) + return GetSessionPage(data, self, reverse, location_id, filter, is_active) def get_sessions_generator( self, location_id: Optional[str] = None, reverse: bool = False, is_active: bool = False, + filter: Optional[dict] = None, ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -592,7 +642,7 @@ def get_sessions_generator( page = 1 page_size = 50 get_session_response = self.get_sessions( - location_id, page, page_size, reverse, is_active + location_id, filter, page, page_size, reverse, is_active ) while True: for session in get_session_response.items: @@ -637,6 +687,7 @@ def create_session( def create_collection( self, name: str, + metadata: Optional[dict] = None, ): """Create a collection for a user @@ -647,7 +698,9 @@ def create_collection( Collection: The Collection object of the new Collection """ - data = {"name": name} + if metadata is None: + metadata = {} + data = {"name": name, "metadata": metadata} url = f"{self.base_url}/collections" response = self.honcho.client.post(url, json=data) response.raise_for_status() @@ -656,6 +709,7 @@ def create_collection( self, id=data["id"], name=name, + metadata=metadata, created_at=data["created_at"], ) @@ -677,11 +731,16 @@ def get_collection(self, name: str): user=self, id=data["id"], name=data["name"], + metadata=data["metadata"], created_at=data["created_at"], ) def get_collections( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ): """Return collections associated with a user paginated @@ -701,12 +760,17 @@ def get_collections( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = self.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return GetCollectionPage(data, self, reverse) + return GetCollectionPage(data, self, filter, reverse) - def get_collections_generator(self, reverse: bool = False): + def get_collections_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_sessions. Generator to iterate through all sessions for a user in an app @@ -719,7 +783,9 @@ def get_collections_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_collection_response = self.get_collections(page, page_size, reverse) + get_collection_response = self.get_collections( + filter, page, page_size, reverse + ) while True: for collection in get_collection_response.items: yield collection @@ -765,7 +831,9 @@ def is_active(self): """Returns whether the session is active - made property to prevent tampering""" return self._is_active - def create_message(self, is_user: bool, content: str): + def create_message( + self, is_user: bool, content: str, metadata: Optional[dict] = None + ): """Adds a message to the session Args: @@ -778,7 +846,9 @@ def create_message(self, is_user: bool, content: str): """ if not self.is_active: raise Exception("Session is inactive") - data = {"is_user": is_user, "content": content} + if metadata is None: + metadata = {} + data = {"is_user": is_user, "content": content, "metadata": metadata} url = f"{self.base_url}/messages" response = self.user.honcho.client.post(url, json=data) response.raise_for_status() @@ -788,6 +858,7 @@ def create_message(self, is_user: bool, content: str): id=data["id"], is_user=is_user, content=content, + metadata=metadata, created_at=data["created_at"], ) @@ -810,11 +881,16 @@ def get_message(self, message_id: uuid.UUID) -> Message: id=data["id"], is_user=data["is_user"], content=data["content"], + metadata=data["metadata"], created_at=data["created_at"], ) def get_messages( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ) -> GetMessagePage: """Get all messages for a session @@ -833,12 +909,17 @@ def get_messages( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return GetMessagePage(data, self, reverse) + return GetMessagePage(data, self, filter, reverse) - def get_messages_generator(self, reverse: bool = False): + def get_messages_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app @@ -848,7 +929,7 @@ def get_messages_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_messages_page = self.get_messages(page, page_size, reverse) + get_messages_page = self.get_messages(filter, page, page_size, reverse) while True: for message in get_messages_page.items: yield message @@ -860,7 +941,11 @@ def get_messages_generator(self, reverse: bool = False): get_messages_page = new_messages def create_metamessage( - self, message: Message, metamessage_type: str, content: str + self, + message: Message, + metamessage_type: str, + content: str, + metadata: Optional[dict] = None, ): """Adds a metamessage to a session and links it to a specific message @@ -875,10 +960,13 @@ def create_metamessage( """ if not self.is_active: raise Exception("Session is inactive") + if metadata is None: + metadata = {} data = { "metamessage_type": metamessage_type, "content": content, "message_id": message.id, + "metadata": metadata, } url = f"{self.base_url}/metamessages" response = self.user.honcho.client.post(url, json=data) @@ -889,6 +977,7 @@ def create_metamessage( message_id=message.id, metamessage_type=metamessage_type, content=content, + metadata=metadata, created_at=data["created_at"], ) @@ -911,6 +1000,7 @@ def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: message_id=data["message_id"], metamessage_type=data["metamessage_type"], content=data["content"], + metadata=data["metadata"], created_at=data["created_at"], ) @@ -918,6 +1008,7 @@ def get_metamessages( self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, + filter: Optional[dict] = None, page: int = 1, page_size: int = 50, reverse: bool = False, @@ -948,18 +1039,22 @@ def get_metamessages( if message: # url += f"&message_id={message.id}" params["message_id"] = message.id + if filter is not None: + json_metadata = json.dumps(filter) + params["filter"] = json_metadata response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() message_id = message.id if message else None return GetMetamessagePage( - data, self, reverse, message_id, metamessage_type + data, self, filter, reverse, message_id, metamessage_type ) def get_metamessages_generator( self, metamessage_type: Optional[str] = None, message: Optional[Message] = None, + filter: Optional[dict] = None, reverse: bool = False, ): """Shortcut Generator for get_metamessages. Generator to iterate @@ -978,6 +1073,7 @@ def get_metamessages_generator( get_metamessages_page = self.get_metamessages( metamessage_type=metamessage_type, message=message, + filter=filter, page=page, page_size=page_size, reverse=reverse, @@ -1008,6 +1104,50 @@ def update(self, metadata: dict): self.metadata = metadata return success + def update_message(self, message: Message, metadata: dict): + """Update the metadata of a message + + Args: + message (Message): The message to update + metadata (dict): The new metadata for the message + + Returns: + boolean: Whether the message was successfully updated + """ + info = {"metadata": metadata} + url = f"{self.base_url}/messages/{message.id}" + response = self.user.honcho.client.put(url, json=info) + success = response.status_code < 400 + message.metadata = metadata + return success + + def update_metamessage( + self, + metamessage: Metamessage, + metamessage_type: Optional[str], + metadata: Optional[dict], + ): + """Update the metadata of a metamessage + + Args: + metamessage (Metamessage): The metamessage to update + metadata (dict): The new metadata for the metamessage + + Returns: + boolean: Whether the metamessage was successfully updated + """ + if metadata is None and metamessage_type is None: + raise ValueError("metadata and metamessage_type cannot both be None") + info = {"metamessage_type": metamessage_type, "metadata": metadata} + url = f"{self.base_url}/metamessages/{metamessage.id}" + response = self.user.honcho.client.put(url, json=info) + success = response.status_code < 400 + if metamessage_type is not None: + metamessage.metamessage_type = metamessage_type + if metadata is not None: + metamessage.metadata = metadata + return success + def close(self): """Closes a session by marking it as inactive""" url = f"{self.base_url}" @@ -1024,12 +1164,14 @@ def __init__( user: User, id: uuid.UUID, name: str, + metadata: dict, created_at: datetime.datetime, ): """Constructor for Collection""" self.user = user self.id: uuid.UUID = id self.name: str = name + self.metadata: dict = metadata self.created_at: datetime.datetime = created_at @property @@ -1041,7 +1183,7 @@ def __str__(self): """String representation of Collection""" return f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 - def update(self, name: str): + def update(self, name: Optional[str] = None, metadata: Optional[dict] = None): """Update the name of the collection Args: @@ -1050,12 +1192,17 @@ def update(self, name: str): Returns: boolean: Whether the session was successfully updated """ - info = {"name": name} + if metadata is None and name is None: + raise ValueError("metadata and name cannot both be None") + info = {"name": name, "metadata": metadata} url = f"{self.base_url}" response = self.user.honcho.client.put(url, json=info) response.raise_for_status() success = response.status_code < 400 - self.name = name + if name is not None: + self.name = name + if metadata is not None: + self.metadata = metadata return success def delete(self): @@ -1113,7 +1260,11 @@ def get_document(self, document_id: uuid.UUID) -> Document: ) def get_documents( - self, page: int = 1, page_size: int = 50, reverse: bool = False + self, + filter: Optional[dict] = None, + page: int = 1, + page_size: int = 50, + reverse: bool = False, ) -> GetDocumentPage: """Get all documents for a collection @@ -1132,12 +1283,17 @@ def get_documents( "size": page_size, "reverse": reverse, } + if filter is not None: + json_filter = json.dumps(filter) + params["filter"] = json_filter response = self.user.honcho.client.get(url, params=params) response.raise_for_status() data = response.json() - return GetDocumentPage(data, self, reverse) + return GetDocumentPage(data, self, filter, reverse) - def get_documents_generator(self, reverse: bool = False): + def get_documents_generator( + self, filter: Optional[dict] = None, reverse: bool = False + ): """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app @@ -1147,7 +1303,7 @@ def get_documents_generator(self, reverse: bool = False): """ page = 1 page_size = 50 - get_documents_page = self.get_documents(page, page_size, reverse) + get_documents_page = self.get_documents(filter, page, page_size, reverse) while True: for document in get_documents_page.items: yield document @@ -1185,7 +1341,10 @@ def query(self, query: str, top_k: int = 5) -> list[Document]: return data def update_document( - self, document: Document, content: Optional[str], metadata: Optional[dict] + self, + document: Document, + content: Optional[str] = None, + metadata: Optional[dict] = None, ) -> Document: """Update a document in the collection diff --git a/sdk/tests/test_async.py b/sdk/tests/test_async.py index 04ffc4a..f18ac99 100644 --- a/sdk/tests/test_async.py +++ b/sdk/tests/test_async.py @@ -15,6 +15,45 @@ from honcho import AsyncHoncho as Honcho +@pytest.mark.asyncio +async def test_session_metadata_filter(): + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + await user.create_session() + await user.create_session(metadata={"foo": "bar"}) + await user.create_session(metadata={"foo": "bar"}) + + response = await user.get_sessions(filter={"foo": "bar"}) + retrieved_sessions = response.items + + assert len(retrieved_sessions) == 2 + + response = await user.get_sessions() + + assert len(response.items) == 3 + + +@pytest.mark.asyncio +async def test_delete_session_metadata(): + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + await honcho.initialize() + user = await honcho.create_user(user_name) + retrieved_session = await user.create_session(metadata={"foo": "bar"}) + + assert retrieved_session.metadata == {"foo": "bar"} + + await retrieved_session.update(metadata={}) + + session_copy = await user.get_session(retrieved_session.id) + + assert session_copy.metadata == {} + + @pytest.mark.asyncio async def test_user_update(): user_name = str(uuid1()) diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index fd92234..0077ff0 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -15,6 +15,43 @@ from honcho import Honcho as Honcho +def test_session_metadata_filter(): + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + user.create_session() + user.create_session(metadata={"foo": "bar"}) + user.create_session(metadata={"foo": "bar"}) + + response = user.get_sessions(filter={"foo": "bar"}) + retrieved_sessions = response.items + + assert len(retrieved_sessions) == 2 + + response = user.get_sessions() + + assert len(response.items) == 3 + + +def test_delete_session_metadata(): + app_name = str(uuid1()) + user_name = str(uuid1()) + honcho = Honcho(app_name, "http://localhost:8000") + honcho.initialize() + user = honcho.create_user(user_name) + retrieved_session = user.create_session(metadata={"foo": "bar"}) + + assert retrieved_session.metadata == {"foo": "bar"} + + retrieved_session.update(metadata={}) + + session_copy = user.get_session(retrieved_session.id) + + assert session_copy.metadata == {} + + def test_user_update(): user_name = str(uuid1()) app_name = str(uuid1()) From d59ea186f2534b2b12bf786f9f50926312600882 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 14 Mar 2024 02:37:07 -0700 Subject: [PATCH 61/85] Basic Dialectic Endpoint fixes dev-253 --- api/poetry.lock | 957 ++++++++++++++++++++++++++++++++- api/pyproject.toml | 4 +- api/src/agent.py | 70 ++- api/src/main.py | 42 +- api/src/prompts/dialectic.yaml | 11 + api/src/schemas.py | 4 + sdk/honcho/client.py | 8 + sdk/honcho/sync_client.py | 8 + sdk/pyproject.toml | 2 +- 9 files changed, 1075 insertions(+), 31 deletions(-) create mode 100644 api/src/prompts/dialectic.yaml diff --git a/api/poetry.lock b/api/poetry.lock index eb3311c..3275945 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,5 +1,117 @@ # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +[[package]] +name = "aiohttp" +version = "3.9.3" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +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.6.0" @@ -56,6 +168,38 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +category = "main" +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.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "backports-zoneinfo" version = "0.2.1" @@ -224,6 +368,22 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataclasses-json" +version = "0.6.4" +description = "Easily serialize dataclasses to and from JSON." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "dataclasses_json-0.6.4-py3-none-any.whl", hash = "sha256:f90578b8a3177f7552f4e1a6e535e84293cd5da421fcce0642d49c0d7bdf8df2"}, + {file = "dataclasses_json-0.6.4.tar.gz", hash = "sha256:73696ebf24936560cca79a2430cbc4f3dd23ac7bf46ed17f38e5e5e7657a6377"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + [[package]] name = "deprecated" version = "1.2.14" @@ -323,6 +483,93 @@ sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"] sqlmodel = ["sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)"] tortoise = ["tortoise-orm (>=0.16.18,<0.21.0)"] +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "googleapis-common-protos" version = "1.62.0" @@ -590,6 +837,179 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "langchain" +version = "0.1.12" +description = "Building applications with LLMs through composability" +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain-0.1.12-py3-none-any.whl", hash = "sha256:b4dd1760e2d035daefad08af60a209b96b729ee45492d34e3e127e553a471034"}, + {file = "langchain-0.1.12.tar.gz", hash = "sha256:5f612761ba548b81748ed8dc70535e8de0531445415028a82de3fd8255bfa8a3"}, +] + +[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.7" +jsonpatch = ">=1.33,<2.0" +langchain-community = ">=0.0.28,<0.1" +langchain-core = ">=0.1.31,<0.2.0" +langchain-text-splitters = ">=0.0.1,<0.1" +langsmith = ">=0.1.17,<0.2.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] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.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 (<2)"] +clarifai = ["clarifai (>=9.1.0)"] +cli = ["typer (>=0.9.0,<0.10.0)"] +cohere = ["cohere (>=4,<5)"] +docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] +embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "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)", "cohere (>=4,<5)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.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 (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] +openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +text-helpers = ["chardet (>=5.1.0,<6.0.0)"] + +[[package]] +name = "langchain-community" +version = "0.0.28" +description = "Community contributed LangChain integrations." +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_community-0.0.28-py3-none-any.whl", hash = "sha256:bdb015ac455ae68432ea104628717583dce041e1abdfcefe86e39f034f5e90b8"}, + {file = "langchain_community-0.0.28.tar.gz", hash = "sha256:8664d243a90550fc5ddc137b712034e02c8d43afc8d4cc832ba5842b44c864ce"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +dataclasses-json = ">=0.5.7,<0.7" +langchain-core = ">=0.1.31,<0.2.0" +langsmith = ">=0.1.0,<0.2.0" +numpy = ">=1,<2" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +cli = ["typer (>=0.9.0,<0.10.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.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)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.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)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.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)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] + +[[package]] +name = "langchain-core" +version = "0.1.31" +description = "Building applications with LLMs through composability" +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_core-0.1.31-py3-none-any.whl", hash = "sha256:ff028f00db8ff03565b542cea81be27426022a72c6545b54d8de66fa00948ab3"}, + {file = "langchain_core-0.1.31.tar.gz", hash = "sha256:d660cf209bb6ce61cb1c853107b091aaa809015a55dce9e0ce19b51d4c8f2a70"}, +] + +[package.dependencies] +anyio = ">=3,<5" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.0,<0.2.0" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + +[[package]] +name = "langchain-openai" +version = "0.0.8" +description = "An integration package connecting OpenAI and LangChain" +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_openai-0.0.8-py3-none-any.whl", hash = "sha256:4862fc72cecbee0240aaa6df0234d5893dd30cd33ca23ac5cfdd86c11d2c44df"}, + {file = "langchain_openai-0.0.8.tar.gz", hash = "sha256:b7aba7fcc52305e78b08197ebc54fc45cc06dbc40ba5b913bc48a22b30a4f5c9"}, +] + +[package.dependencies] +langchain-core = ">=0.1.27,<0.2.0" +openai = ">=1.10.0,<2.0.0" +tiktoken = ">=0.5.2,<1" + +[[package]] +name = "langchain-text-splitters" +version = "0.0.1" +description = "LangChain text splitting utilities" +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_text_splitters-0.0.1-py3-none-any.whl", hash = "sha256:f5b802f873f5ff6a8b9259ff34d53ed989666ef4e1582e6d1adb3b5520e3839a"}, + {file = "langchain_text_splitters-0.0.1.tar.gz", hash = "sha256:ac459fa98799f5117ad5425a9330b21961321e30bc19a2a2f9f761ddadd62aa1"}, +] + +[package.dependencies] +langchain-core = ">=0.1.28,<0.2.0" + +[package.extras] +extended-testing = ["lxml (>=5.1.0,<6.0.0)"] + +[[package]] +name = "langsmith" +version = "0.1.25" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +category = "main" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langsmith-0.1.25-py3-none-any.whl", hash = "sha256:4790405a90dce8e24cbf5bd77278ffe5cf7cc1047056138bd189533a22ac6dee"}, + {file = "langsmith-0.1.25.tar.gz", hash = "sha256:250949a4f239dd1a1eda70bae006b96271d164938258d527c484b7388da61fcb"}, +] + +[package.dependencies] +orjson = ">=3.9.14,<4.0.0" +pydantic = ">=1,<3" +requests = ">=2,<3" + [[package]] name = "limits" version = "3.9.0" @@ -620,6 +1040,138 @@ mongodb = ["pymongo (>4.1,<5)"] redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +[[package]] +name = "marshmallow" +version = "3.21.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "main" +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 = "numpy" version = "1.24.4" @@ -938,6 +1490,66 @@ files = [ {file = "opentelemetry_util_http-0.44b0.tar.gz", hash = "sha256:75896dffcbbeb5df5429ad4526e22307fc041a27114e0c5bfd90bb219381e68f"}, ] +[[package]] +name = "orjson" +version = "3.9.15" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, + {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, + {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, + {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, + {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, + {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, + {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, + {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, + {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, + {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, + {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, + {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, + {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, + {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, + {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, + {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, + {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, + {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, + {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, + {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, + {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, + {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, + {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, + {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, + {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, + {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, + {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, + {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, + {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, + {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, +] + [[package]] name = "packaging" version = "23.2" @@ -1228,6 +1840,56 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +category = "main" +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-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-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {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-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-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 = "realtime" version = "1.0.2" @@ -1245,6 +1907,109 @@ python-dateutil = ">=2.8.1,<3.0.0" typing-extensions = ">=4.2.0,<5.0.0" websockets = ">=11.0,<12.0" +[[package]] +name = "regex" +version = "2023.12.25" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1481,6 +2246,74 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +category = "main" +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.6.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:277de84ccd8fa12730a6b4067456e5cf72fef6300bea61d506c09e45658d41ac"}, + {file = "tiktoken-0.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c44433f658064463650d61387623735641dcc4b6c999ca30bc0f8ba3fccaf5c"}, + {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afb9a2a866ae6eef1995ab656744287a5ac95acc7e0491c33fad54d053288ad3"}, + {file = "tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c62c05b3109fefca26fedb2820452a050074ad8e5ad9803f4652977778177d9f"}, + {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ef917fad0bccda07bfbad835525bbed5f3ab97a8a3e66526e48cdc3e7beacf7"}, + {file = "tiktoken-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e095131ab6092d0769a2fda85aa260c7c383072daec599ba9d8b149d2a3f4d8b"}, + {file = "tiktoken-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:05b344c61779f815038292a19a0c6eb7098b63c8f865ff205abb9ea1b656030e"}, + {file = "tiktoken-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cefb9870fb55dca9e450e54dbf61f904aab9180ff6fe568b61f4db9564e78871"}, + {file = "tiktoken-0.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:702950d33d8cabc039845674107d2e6dcabbbb0990ef350f640661368df481bb"}, + {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d49d076058f23254f2aff9af603863c5c5f9ab095bc896bceed04f8f0b013a"}, + {file = "tiktoken-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:430bc4e650a2d23a789dc2cdca3b9e5e7eb3cd3935168d97d43518cbb1f9a911"}, + {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:293cb8669757301a3019a12d6770bd55bec38a4d3ee9978ddbe599d68976aca7"}, + {file = "tiktoken-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bd1a288b7903aadc054b0e16ea78e3171f70b670e7372432298c686ebf9dd47"}, + {file = "tiktoken-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac76e000183e3b749634968a45c7169b351e99936ef46f0d2353cd0d46c3118d"}, + {file = "tiktoken-0.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17cc8a4a3245ab7d935c83a2db6bb71619099d7284b884f4b2aea4c74f2f83e3"}, + {file = "tiktoken-0.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:284aebcccffe1bba0d6571651317df6a5b376ff6cfed5aeb800c55df44c78177"}, + {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c1a3a5d33846f8cd9dd3b7897c1d45722f48625a587f8e6f3d3e85080559be8"}, + {file = "tiktoken-0.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6318b2bb2337f38ee954fd5efa82632c6e5ced1d52a671370fa4b2eff1355e91"}, + {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f5f0f2ed67ba16373f9a6013b68da298096b27cd4e1cf276d2d3868b5c7efd1"}, + {file = "tiktoken-0.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:75af4c0b16609c2ad02581f3cdcd1fb698c7565091370bf6c0cf8624ffaba6dc"}, + {file = "tiktoken-0.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:45577faf9a9d383b8fd683e313cf6df88b6076c034f0a16da243bb1c139340c3"}, + {file = "tiktoken-0.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c1492ab90c21ca4d11cef3a236ee31a3e279bb21b3fc5b0e2210588c4209e68"}, + {file = "tiktoken-0.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e2b380c5b7751272015400b26144a2bab4066ebb8daae9c3cd2a92c3b508fe5a"}, + {file = "tiktoken-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f497598b9f58c99cbc0eb764b4a92272c14d5203fc713dd650b896a03a50ad"}, + {file = "tiktoken-0.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e65e8bd6f3f279d80f1e1fbd5f588f036b9a5fa27690b7f0cc07021f1dfa0839"}, + {file = "tiktoken-0.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5f1495450a54e564d236769d25bfefbf77727e232d7a8a378f97acddee08c1ae"}, + {file = "tiktoken-0.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6c4e4857d99f6fb4670e928250835b21b68c59250520a1941618b5b4194e20c3"}, + {file = "tiktoken-0.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:168d718f07a39b013032741867e789971346df8e89983fe3c0ef3fbd5a0b1cb9"}, + {file = "tiktoken-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:47fdcfe11bd55376785a6aea8ad1db967db7f66ea81aed5c43fad497521819a4"}, + {file = "tiktoken-0.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fb7d2ccbf1a7784810aff6b80b4012fb42c6fc37eaa68cb3b553801a5cc2d1fc"}, + {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ccb7a111ee76af5d876a729a347f8747d5ad548e1487eeea90eaf58894b3138"}, + {file = "tiktoken-0.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2048e1086b48e3c8c6e2ceeac866561374cd57a84622fa49a6b245ffecb7744"}, + {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07f229a5eb250b6403a61200199cecf0aac4aa23c3ecc1c11c1ca002cbb8f159"}, + {file = "tiktoken-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:432aa3be8436177b0db5a2b3e7cc28fd6c693f783b2f8722539ba16a867d0c6a"}, + {file = "tiktoken-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:8bfe8a19c8b5c40d121ee7938cd9c6a278e5b97dc035fd61714b4f0399d2f7a1"}, + {file = "tiktoken-0.6.0.tar.gz", hash = "sha256:ace62a4ede83c75b0374a2ddfa4b76903cf483e9cb06247f566be3bf14e6beed"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + [[package]] name = "tqdm" version = "4.66.2" @@ -1514,6 +2347,22 @@ files = [ {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +category = "main" +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 = "tzdata" version = "2024.1" @@ -1724,6 +2573,110 @@ files = [ {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [[package]] name = "zipp" version = "3.17.0" @@ -1742,5 +2695,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "049befe5515a3295e10aba589ec19ab6f01e7682555dc701712afeceac57a7f0" +python-versions = "^3.8.1" +content-hash = "aa7928d0a259d20dd9805ab5b159eebb293eac0b2e46a40d5df55f797222a8f3" diff --git a/api/pyproject.toml b/api/pyproject.toml index 33fdd79..3fc9cbc 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Plastic Labs "] readme = "README.md" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.8.1" fastapi = "^0.109.0" uvicorn = "^0.24.0.post1" python-dotenv = "^1.0.0" @@ -25,6 +25,8 @@ opentelemetry-instrumentation-logging = "^0.44b0" greenlet = "^3.0.3" realtime = "^1.0.2" psycopg = {extras = ["binary"], version = "^3.1.18"} +langchain = "^0.1.12" +langchain-openai = "^0.0.8" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/agent.py b/api/src/agent.py index d6342fd..8ade84b 100644 --- a/api/src/agent.py +++ b/api/src/agent.py @@ -1,5 +1,71 @@ -async def chat(): - pass +import os +import uuid +from typing import Optional + +from dotenv import load_dotenv +from langchain_core.prompts import ( + ChatPromptTemplate, + SystemMessagePromptTemplate, + load_prompt, +) +from langchain_openai import ChatOpenAI +from sqlalchemy.ext.asyncio import AsyncSession + +from . import crud, schemas + +load_dotenv() + +# from supabase import Client + +SYSTEM_DIALECTIC = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/dialectic.yaml") +) +system_dialectic: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_DIALECTIC +) + +llm: ChatOpenAI = ChatOpenAI(model_name="gpt-4") + + +async def chat( + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + query: str, + db: AsyncSession, +): + collection = await crud.get_collection_by_name(db, app_id, user_id, "honcho") + retrieved_facts = None + if collection is None: + collection_create = schemas.CollectionCreate(name="honcho", metadata={}) + collection = await crud.create_collection( + db, + collection=collection_create, + app_id=app_id, + user_id=user_id, + ) + else: + retrieved_documents = await crud.query_documents( + db=db, + app_id=app_id, + user_id=user_id, + collection_id=collection.id, + query=query, + top_k=1, + ) + if len(retrieved_documents) > 0: + retrieved_facts = retrieved_documents[0].content + + dialectic_prompt = ChatPromptTemplate.from_messages([system_dialectic]) + chain = dialectic_prompt | llm + response = await chain.ainvoke( + { + "agent_input": query, + "retrieved_facts": retrieved_facts if retrieved_facts else "None", + } + ) + + return schemas.AgentChat(content=response.content) async def hydrate(): diff --git a/api/src/main.py b/api/src/main.py index 9dcf7db..e7f20b1 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -57,7 +57,7 @@ from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException -from . import crud, schemas +from . import agent, crud, schemas from .db import SessionLocal, engine, scaffold_db # Otel Setup @@ -1019,6 +1019,11 @@ async def create_collection( collection: schemas.CollectionCreate, db: AsyncSession = Depends(get_db), ): + if collection.name == "honcho": + raise HTTPException( + status_code=406, + detail="error invalid collection configuration - honcho is a reserved name", + ) try: return await crud.create_collection( db, collection=collection, app_id=app_id, user_id=user_id @@ -1043,6 +1048,12 @@ async def update_collection( raise HTTPException( status_code=400, detail="invalid request - name cannot be None" ) + if collection.name == "honcho": + raise HTTPException( + status_code=406, + detail="error invalid collection configuration - honcho is a reserved name", + ) + try: honcho_collection = await crud.update_collection( db, @@ -1249,37 +1260,18 @@ async def delete_document( ) -@router.get("/sessions/{session_id}/chat", response_model=Sequence[schemas.Message]) +@router.get("/sessions/{session_id}/chat", response_model=schemas.AgentChat) async def get_chat( request: Request, app_id: uuid.UUID, user_id: uuid.UUID, session_id: uuid.UUID, + query: str, db: AsyncSession = Depends(get_db), ): - pass - - -@router.get("/sessions/{session_id}/hydrate") -async def hydrate( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - pass - - -@router.get("/sessions/{session_id}/insight") -async def get_insight( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - pass + return await agent.chat( + app_id=app_id, user_id=user_id, session_id=session_id, query=query, db=db + ) app.include_router(router) diff --git a/api/src/prompts/dialectic.yaml b/api/src/prompts/dialectic.yaml new file mode 100644 index 0000000..ba880a2 --- /dev/null +++ b/api/src/prompts/dialectic.yaml @@ -0,0 +1,11 @@ + +_type: prompt +input_variables: + ["agent_input", "retrieved_facts"] +template: > + You are tasked with responding to the query based on the context provided. + --- + query: {agent_input} + context: {retrieved_facts} + --- + Provide a brief, matter-of-fact, and appropriate response to the query based on the context provided. If the context provided doesn't aid in addressing the query, return None. diff --git a/api/src/schemas.py b/api/src/schemas.py index eb09713..e99e181 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -236,3 +236,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True schema_extra = {"exclude": ["h_metadata"]} + + +class AgentChat(BaseModel): + content: str diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 1bc7262..b35ec1e 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1155,6 +1155,14 @@ async def close(self): response.raise_for_status() self._is_active = False + async def chat(self, query) -> str: + url = f"{self.base_url}/chat" + params = {"query": query} + response = await self.user.honcho.client.get(url, params=params) + response.raise_for_status() + data = response.json() + return data["content"] + class AsyncCollection: """Represents a single collection for a user in an app""" diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 58fe0eb..5322812 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1155,6 +1155,14 @@ def close(self): response.raise_for_status() self._is_active = False + def chat(self, query) -> str: + url = f"{self.base_url}/chat" + params = {"query": query} + response = self.user.honcho.client.get(url, params=params) + response.raise_for_status() + data = response.json() + return data["content"] + class Collection: """Represents a single collection for a user in an app""" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index c07b0f6..8830d24 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" packages = [{include = "honcho"}] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.8.1" httpx = "^0.26.0" [tool.poetry.group.test.dependencies] From 52233efa1fa7a42b3224afb85e4924d0e4fadf50 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:10:15 -0700 Subject: [PATCH 62/85] Working Fact Deriver --- .gitignore | 2 + api/fly.toml | 1 + api/src/harvester.py | 190 ++++++++++++++++++++++++++- api/src/prompts/check_dup_facts.yaml | 11 ++ api/src/prompts/derive_facts.yaml | 10 ++ example/cli/main.py | 18 ++- example/cli/poetry.lock | 2 +- sdk/poetry.lock | 41 +++++- sdk/pyproject.toml | 2 +- 9 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 api/src/prompts/check_dup_facts.yaml create mode 100644 api/src/prompts/derive_facts.yaml diff --git a/.gitignore b/.gitignore index 3c42912..d8fa784 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,5 @@ cython_debug/ #.idea/ .DS_Store + +supabase/ diff --git a/api/fly.toml b/api/fly.toml index aa53938..104eaa8 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -14,6 +14,7 @@ kill_timeout = "5s" [processes] api = "python -m uvicorn src.main:app --host 0.0.0.0 --port 8000" + deriver = "python -m src.harvester" [http_service] internal_port = 8000 diff --git a/api/src/harvester.py b/api/src/harvester.py index a03aa05..7be78bc 100644 --- a/api/src/harvester.py +++ b/api/src/harvester.py @@ -1,23 +1,203 @@ +import asyncio import os +import uuid +from typing import List from dotenv import load_dotenv +from langchain_core.output_parsers import NumberedListOutputParser +from langchain_core.prompts import ( + ChatPromptTemplate, + SystemMessagePromptTemplate, + load_prompt, +) +from langchain_openai import ChatOpenAI from realtime.connection import Socket +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from . import crud, models, schemas +from .db import SessionLocal load_dotenv() SUPABASE_ID = os.getenv("SUPABASE_ID") -API_KEY = os.getenv("SUPABASE_API_KEY") +SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY") + +llm = ChatOpenAI(model_name="gpt-4") +output_parser = NumberedListOutputParser() + +SYSTEM_DERIVE_FACTS = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/derive_facts.yaml") +) +SYSTEM_CHECK_DUPS = load_prompt( + os.path.join(os.path.dirname(__file__), "prompts/check_dup_facts.yaml") +) + +system_check_dups: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_CHECK_DUPS +) + +system_derive_facts: SystemMessagePromptTemplate = SystemMessagePromptTemplate( + prompt=SYSTEM_DERIVE_FACTS +) + + +async def callback(payload): + # print(payload["record"]["is_user"]) + # print(type(payload["record"]["is_user"])) + if payload["record"]["is_user"]: # Check if the message is from a user + session_id = payload["record"]["session_id"] + message_id = payload["record"]["id"] + content = payload["record"]["content"] + + # Example of querying for a user_id based on session_id, adjust according to your schema + session: models.Session + user_id: uuid.UUID + app_id: uuid.UUID + async with SessionLocal() as db: + stmt = ( + select(models.Session) + .join(models.Session.messages) + .where(models.Message.id == message_id) + .where(models.Session.id == session_id) + .options(selectinload(models.Session.user)) + ) + result = await db.execute(stmt) + session = result.scalars().one() + user = session.user + user_id = user.id + app_id = user.app_id + collection: models.Collection + async with SessionLocal() as db: + collection = await crud.get_collection_by_name( + db, app_id, user_id, "honcho" + ) + if collection is None: + collection_create = schemas.CollectionCreate(name="honcho", metadata={}) + collection = await crud.create_collection( + db, + collection=collection_create, + app_id=app_id, + user_id=user_id, + ) + collection_id = collection.id + await process_user_message( + content, app_id, user_id, session_id, collection_id, message_id + ) + return + + +async def process_user_message( + content: str, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + collection_id: uuid.UUID, + message_id: uuid.UUID, +): + # TODO get messages for the session + async with SessionLocal() as db: + messages_stmt = await crud.get_messages( + db=db, app_id=app_id, user_id=user_id, session_id=session_id, reverse=True + ) + messages_stmt = messages_stmt.limit(10) + response = await db.execute(messages_stmt) + messages = response.scalars().all() + messages = messages[::-1] + contents = [m.content for m in messages] + # print(contents) + + facts = await derive_facts(messages, content) + print("===================") + print(f"DERIVED FACTS: {facts}") + print("===================") + new_facts = await check_dups(app_id, user_id, collection_id, facts) + + print("===================") + print(f"CHECKED FOR DUPLICATES: {new_facts}") + print("===================") + + for fact in new_facts: + create_document = schemas.DocumentCreate(content=fact) + async with SessionLocal() as db: + doc = await crud.create_document( + db, + document=create_document, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + ) + print(f"Returned Document: {doc}") + # doc = crud.create_document(content=fact) + # for fact in new_facts: + # session.create_metamessage( + # message=user_message, metamessage_type="fact", content=fact + # ) + # print(f"Created fact: {fact}") + + +async def derive_facts(chat_history, input: str) -> List[str]: + """Derive facts from the user input""" + + fact_derivation = ChatPromptTemplate.from_messages([system_derive_facts]) + chain = fact_derivation | llm + response = await chain.ainvoke( + { + "chat_history": [ + ( + "user: " + message.content + if message.is_user + else "ai: " + message.content + ) + for message in chat_history + ], + "user_input": input, + } + ) + facts = output_parser.parse(response.content) + + return facts + +async def check_dups( + app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, facts: List[str] +): + """Check that we're not storing duplicate facts""" -def derive_facts(payload): - print("Derive Facts: ", payload) + check_duplication = ChatPromptTemplate.from_messages([system_check_dups]) + query = " ".join(facts) + result = None + async with SessionLocal() as db: + result = await crud.query_documents( + db=db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + query=query, + top_k=10, + ) + # result = collection.query(query=query, top_k=10) + existing_facts = [document.content for document in result] + print("===================") + print(f"Existing Facts {existing_facts}") + print("===================") + if len(existing_facts) == 0: + return facts + chain = check_duplication | llm + response = await chain.ainvoke({"existing_facts": existing_facts, "facts": facts}) + new_facts = output_parser.parse(response.content) + print("===================") + print(f"New Facts {facts}") + print("===================") + return new_facts if __name__ == "__main__": - URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={API_KEY}&vsn=1.0.0" + URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={SUPABASE_API_KEY}&vsn=1.0.0" + # URL = f"ws://127.0.0.1:54321/realtime/v1/websocket?apikey={SUPABASE_API_KEY}" # For local Supabase s = Socket(URL) s.connect() channel = s.set_channel("realtime:public:messages") - channel.join().on("INSERT", derive_facts) + channel.join().on("INSERT", lambda payload: asyncio.create_task(callback(payload))) s.listen() diff --git a/api/src/prompts/check_dup_facts.yaml b/api/src/prompts/check_dup_facts.yaml new file mode 100644 index 0000000..673f9ae --- /dev/null +++ b/api/src/prompts/check_dup_facts.yaml @@ -0,0 +1,11 @@ +_type: prompt +input_variables: + ["existing_facts", "facts"] +template: > + Your job is to compare the following two lists and keep only unique items: + + Old: ```{existing_facts}``` + + New: ```{facts}``` + + Remove redundant information from the new list and output the remaining facts as a numbered list. If there's nothing to remove (i.e. the statements are sufficiently different), print "None". diff --git a/api/src/prompts/derive_facts.yaml b/api/src/prompts/derive_facts.yaml new file mode 100644 index 0000000..4e06a0f --- /dev/null +++ b/api/src/prompts/derive_facts.yaml @@ -0,0 +1,10 @@ +_type: prompt +input_variables: + ["chat_history", "user_input"] +template: > + You are tasked with deriving discrete facts about the user based on their input. The goal is to only extract absolute facts from the message, do not make inferences beyond the text provided. + + chat history: ```{chat_history}``` + user input: ```{user_input}``` + + Output the facts as a numbered list. diff --git a/example/cli/main.py b/example/cli/main.py index a44f0a6..56d048d 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -11,10 +11,10 @@ app_name = str(uuid4()) -# honcho = Honcho( -# app_name=app_name, base_url="http://localhost:8000" -# ) # uncomment to use local -honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev +honcho = Honcho( + app_name=app_name, base_url="http://localhost:8000" +) # uncomment to use local +# honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev honcho.initialize() responses = ["Fake LLM Response :)"] @@ -28,6 +28,16 @@ session = user.create_session() +# def langchain_message_converter(messages: List): +# new_messages = [] +# for message in messages: +# if message.is_user: +# new_messages.append(HumanMessage(content=message.content)) +# else: +# new_messages.append(AIMessage(content=message.content)) +# return new_messages + + def chat(): while True: user_input = input("User: ") diff --git a/example/cli/poetry.lock b/example/cli/poetry.lock index 546d3c0..ae3934c 100644 --- a/example/cli/poetry.lock +++ b/example/cli/poetry.lock @@ -469,7 +469,7 @@ version = "0.0.4" description = "Python Client SDK for Honcho" category = "main" optional = false -python-versions = "^3.10" +python-versions = "^3.9" files = [] develop = true diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 989bc10..61c46e2 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -389,6 +389,26 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "importlib-metadata" +version = "7.0.2" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, + {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -651,6 +671,7 @@ babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.14" @@ -828,7 +849,23 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "zipp" +version = "3.18.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.0-py3-none-any.whl", hash = "sha256:c1bb803ed69d2cce2373152797064f7e79bc43f0a3748eb494096a867e0ebf79"}, + {file = "zipp-3.18.0.tar.gz", hash = "sha256:df8d042b02765029a09b157efd8e820451045890acc30f8e37dd2f94a060221f"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "10e19253c5f33dbed0b26e0fc333ac804fcc459e5a0bf8f2e32277d7efcd264b" +python-versions = "^3.9" +content-hash = "bf46c7a976758553f5f41e0d73766388e5b93ccf383d7870106286c5c570cedf" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 8830d24..72353f3 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" packages = [{include = "honcho"}] [tool.poetry.dependencies] -python = "^3.8.1" +python = "^3.9" httpx = "^0.26.0" [tool.poetry.group.test.dependencies] From 37634b5317c5ed20858f61d0e3b4b95dc6c06442 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:26:07 -0700 Subject: [PATCH 63/85] 0.0.5 Docs and README updates --- README.md | 2 +- api/CHANGELOG.md | 16 ++++++++++++++++ api/pyproject.toml | 2 +- sdk/CHANGELOG.md | 14 ++++++++++++++ sdk/README.md | 3 ++- sdk/pyproject.toml | 2 +- 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2e1669a..0845a28 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Honcho -![Static Badge](https://img.shields.io/badge/Version-0.0.4-blue) +![Static Badge](https://img.shields.io/badge/Version-0.0.5-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 9b5f847..2385bc2 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.5] — 2024-03-14 + +### Added + +* Metadata to all data primitives (Users, Sessions, Messages, etc.) +* Ability to filter paginated GET requests by JSON filter based on metadata +* Optional Sentry error monitoring +* Optional Opentelemetry logging +* Dialectic API to interact with honcho agent and get insights about users +* Automatic Fact Derivation Script for automatically generating simple memory + +### Changed + +* API Server now uses async methods to make use of benefits of FastAPI + + ## [0.0.4] — 2024-02-22 ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index 3fc9cbc..de8f327 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho" -version = "0.0.4" +version = "0.0.5" description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ace34de..cb210a2 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.5] — 2024-03-14 + +### Added + +* Metadata to all data primitives (Users, Sessions, Messages, etc.) +* Ability to filter paginated GET requests by JSON filter based on metadata +* Dialectic API to interact with honcho agent and get insights about users +* Code Coverage Tests +* Autogenerated Sphinx Documentation for Honcho Client SDK + +### Fixed + +* URL encoding all GET requests in honcho client + ## [0.0.4] — 2024-02-22 ### Added diff --git a/sdk/README.md b/sdk/README.md index ee1ca2c..61f64dd 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -5,7 +5,8 @@ applications. Read about the motivation of this project [here](https://blog.plasticlabs.ai). -Read the full documentation of this project [here](https://docs.honcho.dev) +Read the full documentation of this project [here](https://docs.honcho.dev) and +find the SDK reference [here](https://api.python.honcho.dev) ## Installation diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 72353f3..523bb42 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.4" +version = "0.0.5" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0" From 09b3e318f371e82088f99eda815d4aae3ca8ca51 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:41:09 -0700 Subject: [PATCH 64/85] Cloudflare Sphinx --- sdk/CHANGELOG.md | 1 + sdk/docs/Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index cb210a2..112e65b 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Dialectic API to interact with honcho agent and get insights about users * Code Coverage Tests * Autogenerated Sphinx Documentation for Honcho Client SDK +* Built-in Langchain message converter ### Fixed diff --git a/sdk/docs/Makefile b/sdk/docs/Makefile index d4bb2cb..672ce2e 100644 --- a/sdk/docs/Makefile +++ b/sdk/docs/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build +SPHINXBUILD ?= poetry run sphinx-build SOURCEDIR = . BUILDDIR = _build From 510a6593897d784450d9b3f68dda329221862dcd Mon Sep 17 00:00:00 2001 From: vintro <77507980+vintrocode@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:22:12 -0700 Subject: [PATCH 65/85] update example to use right function (#36) --- example/discord/honcho-fact-memory/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/discord/honcho-fact-memory/bot.py b/example/discord/honcho-fact-memory/bot.py index aca2650..910afc6 100644 --- a/example/discord/honcho-fact-memory/bot.py +++ b/example/discord/honcho-fact-memory/bot.py @@ -44,7 +44,7 @@ async def on_message(message): return user_id = f"discord_{str(message.author.id)}" - user = honcho.get_or_create(user_id) + user = honcho.get_or_create_user(user_id) location_id = str(message.channel.id) sessions = list(user.get_sessions_generator(location_id)) From dce2f44631bed70fa9204d83147102d3fd8d0d9d Mon Sep 17 00:00:00 2001 From: Ayush Paul Date: Sat, 16 Mar 2024 22:42:54 -0400 Subject: [PATCH 66/85] =?UTF-8?q?=F0=9F=9A=80=20feat:=20add=20support=20fo?= =?UTF-8?q?r=20running=20API=20using=20docker-compose=20with=20configurabl?= =?UTF-8?q?e=20environment=20variables=20and=20update=20docker-compose.yml?= =?UTF-8?q?=20for=20API=20and=20database=20services.=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> --- .gitignore | 3 ++- README.md | 36 +++++++++++++++---------------- api/docker-compose.yml.example | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 api/docker-compose.yml.example diff --git a/.gitignore b/.gitignore index d8fa784..558b175 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ api/**/*.db - +api/data +api/docker-compose.yml # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index 0845a28..b0539c3 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,19 @@ directories. ### API +#### Docker + +The API can be run using docker-compose. The `docker-compose.yml.example` file can be copied to `docker-compose.yml` and the environment variables can be set in the `.env` file. + +```bash +cd honcho/api +cp docker-compose.yml.example docker-compose.yml +[ update the file with openai key and other wanted environment variables ] +docker compose up -d +``` + +#### Manually + The API can be run either by installing the necessary dependencies and then specifying the appropriate environment variables. @@ -48,7 +61,7 @@ poetry install # install dependencies 2. Copy the `.env.template` file and specify the type of database and connection_uri. For testing sqlite is fine. The below example uses an - in-memory sqlite database. + in-memory sqlite database. > Honcho has been tested with Postgresql and PGVector @@ -72,26 +85,11 @@ poetry shell # Activate virtual environment if not already enabled python -m uvicorn src.main:app --reload ``` -#### Docker - -Alternatively there is also a `Dockerfile` included to run the API server from a -docker container. - -The `.env` file is not loaded into the docker container and should still be -configured from outside. - -```bash -cd honcho/api -docker build -t honcho-api . -docker run --env-file .env -p 8000:8000 honcho-api:latest -``` - #### Deploy on Fly The API can also be deployed on fly.io. Follow the [Fly.io Docs](https://fly.io/docs/getting-started/) to setup your environment and the -`flyctl`. - +`flyctl`. Once `flyctl` is set up use the following commands to launch the application: @@ -134,12 +132,12 @@ See more information [here](https://python-poetry.org/docs/cli/#add) This project is completely open source and welcomes any and all open source contributions. The workflow for contributing is to make a fork of the repository. You can claim an issue in the issues tab or start a new thread to -indicate a feature or bug fix you are working on. +indicate a feature or bug fix you are working on. Once you have finished your contribution make a PR pointed at the `staging` branch, and it will be reviewed by a project manager. Feel free to join us in our [discord](http://discord.gg/plasticlabs) to discuss your changes or get -help. +help. Once your changes are accepted and merged into staging they will undergo a period of live testing before entering the upstream into `main` diff --git a/api/docker-compose.yml.example b/api/docker-compose.yml.example new file mode 100644 index 0000000..60fc24d --- /dev/null +++ b/api/docker-compose.yml.example @@ -0,0 +1,39 @@ +version: "3.8" +services: + api: + build: + context: . + dockerfile: Dockerfile + ports: + - 8000:8000 + volumes: + - .:/app + environment: + - DATABASE_TYPE=postgres + - CONNECTION_URI=postgresql://testuser:testpwd@database:5432/honcho + - OPENAI_API_KEY=[YOUR_OPENAI_API_KEY] + - OPENTELEMETRY_ENABLED=false + - SENTRY_ENABLED=false + - SENTRY_DSN= + - OTEL_SERVICE_NAME=honcho + - OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true + - OTEL_PYTHON_LOG_CORRELATION=true + - OTEL_PYTHON_LOG_LEVEL= + - OTEL_EXPORTER_OTLP_PROTOCOL= + - OTEL_EXPORTER_OTLP_ENDPOINT= + - OTEL_EXPORTER_OTLP_HEADERS= + - OTEL_RESOURCE_ATTRIBUTES= + - DEBUG_LOG_OTEL_TO_PROVIDER=false + - DEBUG_LOG_OTEL_TO_CONSOLE=true + database: + image: ankane/pgvector + restart: always + environment: + - POSTGRES_DB=honcho + - POSTGRES_USER=testuser + - POSTGRES_PASSWORD=testpwd + - POSTGRES_HOST_AUTH_METHOD=trust + - PGDATA=/var/lib/postgresql/data/pgdata + volumes: + - ./local/init.sql:/docker-entrypoint-initdb.d/init.sql + - ./data:/var/lib/postgresql/data/ From d4cd018f833e4b188220ecf75b6c450a6a9c90b4 Mon Sep 17 00:00:00 2001 From: hyusap Date: Sat, 16 Mar 2024 18:11:57 -0400 Subject: [PATCH 67/85] add interrogate --- .github/workflows/run_coverage.yml | 3 + sdk/poetry.lock | 147 ++++++++++++++++++++--------- sdk/pyproject.toml | 1 + 3 files changed, 104 insertions(+), 47 deletions(-) diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index a0cf695..eb265a2 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -36,6 +36,9 @@ jobs: poetry install poetry run coverage run -m pytest poetry run coverage report --format=markdown > coverage.md + echo -e "\n---\n# Docstring Coverage\n\`\`\`" >> coverage.md + poetry run interrogate -v honcho >> coverage.md + echo -e "\`\`\`" >> coverage.md cd .. - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 61c46e2..9d561c4 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -16,7 +15,6 @@ files = [ name = "anyio" version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -35,11 +33,29 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "babel" version = "2.14.0" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -54,7 +70,6 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "beautifulsoup4" version = "4.12.3" description = "Screen-scraping library" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -76,7 +91,6 @@ lxml = ["lxml"] name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -88,7 +102,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -184,11 +197,24 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[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." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -200,7 +226,6 @@ files = [ name = "coverage" version = "7.4.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -265,7 +290,6 @@ toml = ["tomli"] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -277,7 +301,6 @@ files = [ name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -292,7 +315,6 @@ test = ["pytest (>=6)"] name = "furo" version = "2024.1.29" description = "A clean customisable Sphinx documentation theme." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -310,7 +332,6 @@ sphinx-basic-ng = "*" name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -322,7 +343,6 @@ files = [ name = "httpcore" version = "1.0.4" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -337,14 +357,13 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -355,21 +374,20 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = ">=1.0.0,<2.0.0" +httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -381,7 +399,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -393,7 +410,6 @@ files = [ name = "importlib-metadata" version = "7.0.2" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -413,7 +429,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -421,11 +436,35 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "interrogate" +version = "1.5.0" +description = "Interrogate a codebase for docstring coverage." +optional = false +python-versions = ">=3.6" +files = [ + {file = "interrogate-1.5.0-py3-none-any.whl", hash = "sha256:a4ccc5cbd727c74acc98dee6f5e79ef264c0bcfa66b68d4e123069b2af89091a"}, + {file = "interrogate-1.5.0.tar.gz", hash = "sha256:b6f325f0aa84ac3ac6779d8708264d366102226c5af7d69058cecffcff7a6d6c"}, +] + +[package.dependencies] +attrs = "*" +click = ">=7.1" +colorama = "*" +py = "*" +tabulate = "*" +toml = "*" + +[package.extras] +dev = ["cairosvg", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "sphinx", "sphinx-autobuild", "wheel"] +docs = ["sphinx", "sphinx-autobuild"] +png = ["cairosvg"] +tests = ["pytest", "pytest-cov", "pytest-mock"] + [[package]] name = "jinja2" version = "3.1.3" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -443,7 +482,6 @@ i18n = ["Babel (>=2.7)"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -513,7 +551,6 @@ files = [ name = "packaging" version = "24.0" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -525,7 +562,6 @@ files = [ name = "pluggy" version = "1.4.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -537,11 +573,21 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + [[package]] name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -557,7 +603,6 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -580,7 +625,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.23.5.post1" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -599,7 +643,6 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -621,7 +664,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -633,7 +675,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -645,7 +686,6 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -657,7 +697,6 @@ files = [ name = "sphinx" version = "7.2.6" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -693,7 +732,6 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -711,7 +749,6 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinxcontrib-applehelp" version = "1.0.8" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -728,7 +765,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.6" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -745,7 +781,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.5" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -762,7 +797,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -777,7 +811,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.7" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -794,7 +827,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.10" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -807,11 +839,35 @@ lint = ["docutils-stubs", "flake8", "mypy"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -823,7 +879,6 @@ files = [ name = "typing-extensions" version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -835,7 +890,6 @@ files = [ name = "urllib3" version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -853,7 +907,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.18.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -868,4 +921,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "bf46c7a976758553f5f41e0d73766388e5b93ccf383d7870106286c5c570cedf" +content-hash = "3265d992168555f7d9d60f6f03c18dc515b32de3b975290158b086d82247fefc" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 523bb42..ee8c3e9 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -15,6 +15,7 @@ httpx = "^0.26.0" pytest = "^7.4.4" pytest-asyncio = "^0.23.4" coverage = "^7.4.3" +interrogate = "^1.5.0" [tool.poetry.group.docs.dependencies] sphinx = "^7.2.6" From 7af03f13cc4fe355e32fc190e07adb6bdc204e10 Mon Sep 17 00:00:00 2001 From: hyusap Date: Sat, 16 Mar 2024 19:37:35 -0400 Subject: [PATCH 68/85] routerify everything --- api/src/dependencies.py | 15 + api/src/main.py | 1059 +------------------------------ api/src/routers/apps.py | 99 +++ api/src/routers/collections.py | 160 +++++ api/src/routers/documents.py | 175 +++++ api/src/routers/messages.py | 138 ++++ api/src/routers/metamessages.py | 173 +++++ api/src/routers/sessions.py | 193 ++++++ api/src/routers/users.py | 128 ++++ 9 files changed, 1099 insertions(+), 1041 deletions(-) create mode 100644 api/src/dependencies.py create mode 100644 api/src/routers/apps.py create mode 100644 api/src/routers/collections.py create mode 100644 api/src/routers/documents.py create mode 100644 api/src/routers/messages.py create mode 100644 api/src/routers/metamessages.py create mode 100644 api/src/routers/sessions.py create mode 100644 api/src/routers/users.py diff --git a/api/src/dependencies.py b/api/src/dependencies.py new file mode 100644 index 0000000..d328700 --- /dev/null +++ b/api/src/dependencies.py @@ -0,0 +1,15 @@ +from fastapi import Depends +from .db import SessionLocal +from sqlalchemy.ext.asyncio import AsyncSession + + +async def get_db(): + """FastAPI Dependency Generator for Database""" + db: AsyncSession = SessionLocal() + try: + yield db + finally: + await db.close() + + +db: AsyncSession = Depends(get_db) diff --git a/api/src/main.py b/api/src/main.py index e7f20b1..fec8f98 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -54,11 +54,20 @@ from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException from . import agent, crud, schemas -from .db import SessionLocal, engine, scaffold_db +from .db import engine, scaffold_db +from src.dependencies import db +from src.routers import ( + apps, + users, + sessions, + messages, + metamessages, + collections, + documents, +) # Otel Setup @@ -220,15 +229,6 @@ async def lifespan(app: FastAPI): add_pagination(app) -async def get_db(): - """FastAPI Dependency Generator for Database""" - db: AsyncSession = SessionLocal() - try: - yield db - finally: - await db.close() - - @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request, exc): current_span = trace.get_current_span() @@ -245,1033 +245,10 @@ async def http_exception_handler(request, exc): ) -######################################################## -# App Routes -######################################################## -@app.get("/apps/{app_id}", response_model=schemas.App) -async def get_app( - request: Request, - app_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - """Get an App by ID - - Args: - app_id (uuid.UUID): The ID of the app - - Returns: - schemas.App: App object - - """ - app = await crud.get_app(db, app_id=app_id) - if app is None: - raise HTTPException(status_code=404, detail="App not found") - return app - - -@app.get("/apps/name/{name}", response_model=schemas.App) -async def get_app_by_name( - request: Request, - name: str, - db: AsyncSession = Depends(get_db), -): - """Get an App by Name - - Args: - app_name (str): The name of the app - - Returns: - schemas.App: App object - - """ - app = await crud.get_app_by_name(db, name=name) - if app is None: - raise HTTPException(status_code=404, detail="App not found") - return app - - -@app.post("/apps", response_model=schemas.App) -async def create_app( - request: Request, - app: schemas.AppCreate, - db: AsyncSession = Depends(get_db), -): - """Create an App - - Args: - app (schemas.AppCreate): The App object containing any metadata - - Returns: - schemas.App: Created App object - - """ - - return await crud.create_app(db, app=app) - - -@app.get("/apps/get_or_create/{name}", response_model=schemas.App) -async def get_or_create_app( - request: Request, - name: str, - db: AsyncSession = Depends(get_db), -): - """Get or Create an App - - Args: - app_name (str): The name of the app - - Returns: - schemas.App: App object - - """ - app = await crud.get_app_by_name(db, name=name) - if app is None: - app = await crud.create_app(db, app=schemas.AppCreate(name=name)) - return app - - -@app.put("/apps/{app_id}", response_model=schemas.App) -async def update_app( - request: Request, - app_id: uuid.UUID, - app: schemas.AppUpdate, - db: AsyncSession = Depends(get_db), -): - """Update an App - - Args: - app_id (uuid.UUID): The ID of the app to update - app (schemas.AppUpdate): The App object containing any new metadata - - Returns: - schemas.App: The App object of the updated App - - """ - honcho_app = await crud.update_app(db, app_id=app_id, app=app) - if honcho_app is None: - raise HTTPException(status_code=404, detail="App not found") - return honcho_app - - -######################################################## -# User Routes -######################################################## - - -@app.post("/apps/{app_id}/users", response_model=schemas.User) -async def create_user( - request: Request, - app_id: uuid.UUID, - user: schemas.UserCreate, - db: AsyncSession = Depends(get_db), -): - """Create a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user (schemas.UserCreate): The User object containing any metadata - - Returns: - schemas.User: Created User object - - """ - return await crud.create_user(db, app_id=app_id, user=user) - - -@app.get("/apps/{app_id}/users", response_model=Page[schemas.User]) -async def get_users( - request: Request, - app_id: uuid.UUID, - reverse: bool = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - """Get All Users for an App - - Args: - app_id (uuid.UUID): The ID of the app representing the client - application using honcho - - Returns: - list[schemas.User]: List of User objects - - """ - data = None - if filter is not None: - data = json.loads(filter) - - return await paginate( - db, await crud.get_users(db, app_id=app_id, reverse=reverse, filter=data) - ) - - -@app.get("/apps/{app_id}/users/{name}", response_model=schemas.User) -async def get_user_by_name( - request: Request, - app_id: uuid.UUID, - name: str, - db: AsyncSession = Depends(get_db), -): - """Get a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - - Returns: - schemas.User: User object - - """ - return await crud.get_user_by_name(db, app_id=app_id, name=name) - - -@app.get("/apps/{app_id}/users/get_or_create/{name}", response_model=schemas.User) -async def get_or_create_user( - request: Request, app_id: uuid.UUID, name: str, db: AsyncSession = Depends(get_db) -): - """Get or Create a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - - Returns: - schemas.User: User object - - """ - user = await crud.get_user_by_name(db, app_id=app_id, name=name) - if user is None: - user = await crud.create_user( - db, app_id=app_id, user=schemas.UserCreate(name=name) - ) - return user - - -@app.put("/apps/{app_id}/users/{user_id}", response_model=schemas.User) -async def update_user( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - user: schemas.UserUpdate, - db: AsyncSession = Depends(get_db), -): - """Update a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - user (schemas.UserCreate): The User object containing any metadata - - Returns: - schemas.User: Updated User object - - """ - return await crud.update_user(db, app_id=app_id, user_id=user_id, user=user) - - -######################################################## -# Session Routes -######################################################## - - -@router.get("/sessions", response_model=Page[schemas.Session]) -async def get_sessions( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - location_id: Optional[str] = None, - is_active: Optional[bool] = False, - reverse: Optional[bool] = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - """Get All Sessions for a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - location_id (str, optional): Optional Location ID representing the location of a - session - - Returns: - list[schemas.Session]: List of Session objects - - """ - - data = None - if filter is not None: - data = json.loads(filter) - - return await paginate( - db, - await crud.get_sessions( - db, - app_id=app_id, - user_id=user_id, - location_id=location_id, - reverse=reverse, - is_active=is_active, - filter=data, - ), - ) - - -@router.post("/sessions", response_model=schemas.Session) -async def create_session( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session: schemas.SessionCreate, - db: AsyncSession = Depends(get_db), -): - """Create a Session for a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client - application using honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - session (schemas.SessionCreate): The Session object containing any - metadata and a location ID - - Returns: - schemas.Session: The Session object of the new Session - - """ - value = await crud.create_session( - db, app_id=app_id, user_id=user_id, session=session - ) - return value - - -@router.put("/sessions/{session_id}", response_model=schemas.Session) -async def update_session( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - session: schemas.SessionUpdate, - db: AsyncSession = Depends(get_db), -): - """Update the metadata of a Session - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - session_id (uuid.UUID): The ID of the Session to update - session (schemas.SessionUpdate): The Session object containing any new metadata - - Returns: - schemas.Session: The Session object of the updated Session - - """ - if session.metadata is None: - raise HTTPException(status_code=400, detail="Session metadata cannot be empty") - try: - return await crud.update_session( - db, app_id=app_id, user_id=user_id, session_id=session_id, session=session - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -@router.delete("/sessions/{session_id}") -async def delete_session( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - """Delete a session by marking it as inactive - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - session_id (uuid.UUID): The ID of the Session to delete - - Returns: - dict: A message indicating that the session was deleted - - Raises: - HTTPException: If the session is not found - - """ - response = await crud.delete_session( - db, app_id=app_id, user_id=user_id, session_id=session_id - ) - if response: - return {"message": "Session deleted successfully"} - else: - raise HTTPException(status_code=404, detail="Session not found") - - -@router.get("/sessions/{session_id}", response_model=schemas.Session) -async def get_session( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - """Get a specific session for a user by ID - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - session_id (uuid.UUID): The ID of the Session to retrieve - - Returns: - schemas.Session: The Session object of the requested Session - - Raises: - HTTPException: If the session is not found - """ - honcho_session = await crud.get_session( - db, app_id=app_id, session_id=session_id, user_id=user_id - ) - if honcho_session is None: - raise HTTPException(status_code=404, detail="Session not found") - return honcho_session - - -######################################################## -# Message Routes -######################################################## - - -@router.post("/sessions/{session_id}/messages", response_model=schemas.Message) -async def create_message_for_session( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - message: schemas.MessageCreate, - db: AsyncSession = Depends(get_db), -): - """Adds a message to a session - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to add the message to - message (schemas.MessageCreate): The Message object to add containing the - message content and type - - Returns: - schemas.Message: The Message object of the added message - - Raises: - HTTPException: If the session is not found - - """ - try: - return await crud.create_message( - db, message=message, app_id=app_id, user_id=user_id, session_id=session_id - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -@router.get("/sessions/{session_id}/messages", response_model=Page[schemas.Message]) -async def get_messages( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - reverse: Optional[bool] = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - """Get all messages for a session - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve - reverse (bool): Whether to reverse the order of the messages - - Returns: - list[schemas.Message]: List of Message objects - - Raises: - HTTPException: If the session is not found - - """ - try: - data = None - if filter is not None: - data = json.loads(filter) - return await paginate( - db, - await crud.get_messages( - db, - app_id=app_id, - user_id=user_id, - session_id=session_id, - filter=data, - reverse=reverse, - ), - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -@router.get( - "sessions/{session_id}/messages/{message_id}", response_model=schemas.Message -) -async def get_message( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - message_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - """ """ - honcho_message = await crud.get_message( - db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id - ) - if honcho_message is None: - raise HTTPException(status_code=404, detail="Session not found") - return honcho_message - - -@router.put( - "sessions/{session_id}/messages/{message_id}", response_model=schemas.Message -) -async def update_message( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - message_id: uuid.UUID, - message: schemas.MessageUpdate, - db: AsyncSession = Depends(get_db), -): - """Update's the metadata of a message""" - if message.metadata is None: - raise HTTPException(status_code=400, detail="Message metadata cannot be empty") - try: - return await crud.update_message( - db, - message=message, - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id, - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -######################################################## -# metamessage routes -######################################################## - - -@router.post("/sessions/{session_id}/metamessages", response_model=schemas.Metamessage) -async def create_metamessage( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - metamessage: schemas.MetamessageCreate, - db: AsyncSession = Depends(get_db), -): - """Adds a message to a session - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to add the message to - message (schemas.MessageCreate): The Message object to add containing the - message content and type - - Returns: - schemas.Message: The Message object of the added message - - Raises: - HTTPException: If the session is not found - - """ - try: - return await crud.create_metamessage( - db, - metamessage=metamessage, - app_id=app_id, - user_id=user_id, - session_id=session_id, - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -@router.get( - "/sessions/{session_id}/metamessages", response_model=Page[schemas.Metamessage] -) -async def get_metamessages( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - message_id: Optional[uuid.UUID] = None, - metamessage_type: Optional[str] = None, - reverse: Optional[bool] = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - """Get all messages for a session - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve - reverse (bool): Whether to reverse the order of the metamessages - - Returns: - list[schemas.Message]: List of Message objects - - Raises: - HTTPException: If the session is not found - - """ - try: - data = None - if filter is not None: - data = json.loads(filter) - return await paginate( - db, - await crud.get_metamessages( - db, - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id, - metamessage_type=metamessage_type, - filter=data, - reverse=reverse, - ), - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -@router.get( - "/sessions/{session_id}/metamessages/{metamessage_id}", - response_model=schemas.Metamessage, -) -async def get_metamessage( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - message_id: uuid.UUID, - metamessage_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - """Get a specific Metamessage by ID - - Args: - app_id (uuid.UUID): The ID of the app representing the client application using - honcho - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to retrieve - - Returns: - schemas.Session: The Session object of the requested Session - - Raises: - HTTPException: If the session is not found - """ - honcho_metamessage = await crud.get_metamessage( - db, - app_id=app_id, - session_id=session_id, - user_id=user_id, - message_id=message_id, - metamessage_id=metamessage_id, - ) - if honcho_metamessage is None: - raise HTTPException(status_code=404, detail="Session not found") - return honcho_metamessage - - -@router.put( - "sessions/{session_id}/metamessages/{metamessage_id}", - response_model=schemas.Metamessage, -) -async def update_metamessage( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - metamessage_id: uuid.UUID, - metamessage: schemas.MetamessageUpdate, - db: AsyncSession = Depends(get_db), -): - """Update's the metadata of a metamessage""" - if metamessage.metadata is None: - raise HTTPException( - status_code=400, detail="Metamessage metadata cannot be empty" - ) - try: - return await crud.update_metamessage( - db, - metamessage=metamessage, - app_id=app_id, - user_id=user_id, - session_id=session_id, - metamessage_id=metamessage_id, - ) - except ValueError: - raise HTTPException(status_code=404, detail="Session not found") from None - - -######################################################## -# collection routes -######################################################## - - -@router.get("/collections", response_model=Page[schemas.Collection]) -async def get_collections( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - reverse: Optional[bool] = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - """Get All Collections for a User - - Args: - app_id (uuid.UUID): The ID of the app representing the client - application using honcho - user_id (uuid.UUID): The User ID representing the user, managed by the user - - Returns: - list[schemas.Collection]: List of Collection objects - - """ - data = None - if filter is not None: - data = json.loads(filter) - return await paginate( - db, - await crud.get_collections( - db, app_id=app_id, user_id=user_id, filter=data, reverse=reverse - ), - ) - - -# @router.get("/collections/id/{collection_id}", response_model=schemas.Collection) -# def get_collection_by_id( -# request: Request, -# app_id: uuid.UUID, -# user_id: uuid.UUID, -# collection_id: uuid.UUID, -# db: AsyncSession = Depends(get_db), -# ) -> schemas.Collection: -# honcho_collection = crud.get_collection_by_id( -# db, app_id=app_id, user_id=user_id, collection_id=collection_id -# ) -# if honcho_collection is None: -# raise HTTPException( -# status_code=404, detail="collection not found or does not belong to user" -# ) -# return honcho_collection - - -@router.get("/collections/{name}", response_model=schemas.Collection) -async def get_collection_by_name( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - name: str, - db: AsyncSession = Depends(get_db), -) -> schemas.Collection: - honcho_collection = await crud.get_collection_by_name( - db, app_id=app_id, user_id=user_id, name=name - ) - if honcho_collection is None: - raise HTTPException( - status_code=404, detail="collection not found or does not belong to user" - ) - return honcho_collection - - -@router.post("/collections", response_model=schemas.Collection) -async def create_collection( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection: schemas.CollectionCreate, - db: AsyncSession = Depends(get_db), -): - if collection.name == "honcho": - raise HTTPException( - status_code=406, - detail="error invalid collection configuration - honcho is a reserved name", - ) - try: - return await crud.create_collection( - db, collection=collection, app_id=app_id, user_id=user_id - ) - except ValueError: - raise HTTPException( - status_code=406, - detail="Error invalid collection configuration - name may already exist", - ) from None - - -@router.put("/collections/{collection_id}", response_model=schemas.Collection) -async def update_collection( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - collection: schemas.CollectionUpdate, - db: AsyncSession = Depends(get_db), -): - if collection.name is None: - raise HTTPException( - status_code=400, detail="invalid request - name cannot be None" - ) - if collection.name == "honcho": - raise HTTPException( - status_code=406, - detail="error invalid collection configuration - honcho is a reserved name", - ) - - try: - honcho_collection = await crud.update_collection( - db, - collection=collection, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - ) - except ValueError: - raise HTTPException( - status_code=406, - detail="Error invalid collection configuration - name may already exist", - ) from None - return honcho_collection - - -@router.delete("/collections/{collection_id}") -async def delete_collection( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - response = await crud.delete_collection( - db, app_id=app_id, user_id=user_id, collection_id=collection_id - ) - if response: - return {"message": "Collection deleted successfully"} - else: - raise HTTPException( - status_code=404, detail="collection not found or does not belong to user" - ) - - -######################################################## -# Document routes -######################################################## - - -@router.get( - "/collections/{collection_id}/documents", response_model=Page[schemas.Document] -) -async def get_documents( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - reverse: Optional[bool] = False, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - try: - data = None - if filter is not None: - data = json.loads(filter) - return await paginate( - db, - await crud.get_documents( - db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - filter=data, - reverse=reverse, - ), - ) - except ( - ValueError - ): # TODO can probably remove this exception ok to return empty here - raise HTTPException( - status_code=404, detail="collection not found or does not belong to user" - ) from None - - -router.get( - "/collections/{collection_id}/documents/{document_id}", - response_model=schemas.Document, -) - - -async def get_document( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - document_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - honcho_document = await crud.get_document( - db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - if honcho_document is None: - raise HTTPException( - status_code=404, detail="document not found or does not belong to user" - ) - return honcho_document - - -@router.get( - "/collections/{collection_id}/query", response_model=Sequence[schemas.Document] -) -async def query_documents( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - query: str, - top_k: int = 5, - filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), -): - if top_k is not None and top_k > 50: - top_k = 50 # TODO see if we need to paginate this - data = None - if filter is not None: - data = json.loads(filter) - return await crud.query_documents( - db=db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - query=query, - filter=data, - top_k=top_k, - ) - - -@router.post("/collections/{collection_id}/documents", response_model=schemas.Document) -async def create_document( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - document: schemas.DocumentCreate, - db: AsyncSession = Depends(get_db), -): - try: - return await crud.create_document( - db, - document=document, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - ) - except ValueError: - raise HTTPException( - status_code=404, detail="collection not found or does not belong to user" - ) from None - - -@router.put( - "/collections/{collection_id}/documents/{document_id}", - response_model=schemas.Document, -) -async def update_document( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - document_id: uuid.UUID, - document: schemas.DocumentUpdate, - db: AsyncSession = Depends(get_db), -): - if document.content is None and document.metadata is None: - raise HTTPException( - status_code=400, detail="content and metadata cannot both be None" - ) - return await crud.update_document( - db, - document=document, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - - -@router.delete("/collections/{collection_id}/documents/{document_id}") -async def delete_document( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - collection_id: uuid.UUID, - document_id: uuid.UUID, - db: AsyncSession = Depends(get_db), -): - response = await crud.delete_document( - db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - if response: - return {"message": "Document deleted successfully"} - else: - raise HTTPException( - status_code=404, detail="document not found or does not belong to user" - ) - - -@router.get("/sessions/{session_id}/chat", response_model=schemas.AgentChat) -async def get_chat( - request: Request, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - query: str, - db: AsyncSession = Depends(get_db), -): - return await agent.chat( - app_id=app_id, user_id=user_id, session_id=session_id, query=query, db=db - ) - - -app.include_router(router) +app.include_router(apps.router) +app.include_router(users.router) +app.include_router(sessions.router) +app.include_router(messages.router) +app.include_router(metamessages.router) +app.include_router(collections.router) +app.include_router(documents.router) diff --git a/api/src/routers/apps.py b/api/src/routers/apps.py new file mode 100644 index 0000000..5d98adb --- /dev/null +++ b/api/src/routers/apps.py @@ -0,0 +1,99 @@ +import uuid +from fastapi import APIRouter, HTTPException, Request + + +from src import crud, schemas +from src.dependencies import db +from sqlalchemy.ext.asyncio import AsyncSession + +router = APIRouter( + prefix="/apps", + tags=["apps"], +) + + +@router.get("/{app_id}", response_model=schemas.App) +async def get_app(request: Request, app_id: uuid.UUID, db=db): + """Get an App by ID + + Args: + app_id (uuid.UUID): The ID of the app + + Returns: + schemas.App: App object + + """ + app = await crud.get_app(db, app_id=app_id) + if app is None: + raise HTTPException(status_code=404, detail="App not found") + return app + + +@router.get("/name/{name}", response_model=schemas.App) +async def get_app_by_name(request: Request, name: str, db=db): + """Get an App by Name + + Args: + app_name (str): The name of the app + + Returns: + schemas.App: App object + + """ + app = await crud.get_app_by_name(db, name=name) + if app is None: + raise HTTPException(status_code=404, detail="App not found") + return app + + +@router.post("/apps", response_model=schemas.App) +async def create_app(request: Request, app: schemas.AppCreate, db=db): + """Create an App + + Args: + app (schemas.AppCreate): The App object containing any metadata + + Returns: + schemas.App: Created App object + + """ + + return await crud.create_app(db, app=app) + + +@router.get("/get_or_create/{name}", response_model=schemas.App) +async def get_or_create_app(request: Request, name: str, db=db): + """Get or Create an App + + Args: + app_name (str): The name of the app + + Returns: + schemas.App: App object + + """ + print("name", name) + app = await crud.get_app_by_name(db, name=name) + if app is None: + app = await crud.create_app(db, app=schemas.AppCreate(name=name)) + return app + + +@router.put("/{app_id}", response_model=schemas.App) +async def update_app( + request: Request, app_id: uuid.UUID, app: schemas.AppUpdate, db=db +): + """Update an App + + Args: + app_id (uuid.UUID): The ID of the app to update + app (schemas.AppUpdate): The App object containing any new metadata + + Returns: + schemas.App: The App object of the updated App + + """ + honcho_app = await crud.update_app(db, app_id=app_id, app=app) + if honcho_app is None: + raise HTTPException(status_code=404, detail="App not found") + return honcho_app diff --git a/api/src/routers/collections.py b/api/src/routers/collections.py new file mode 100644 index 0000000..1f9e788 --- /dev/null +++ b/api/src/routers/collections.py @@ -0,0 +1,160 @@ +import json +from typing import Optional +import uuid +from fastapi import APIRouter, HTTPException, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db + + +router = APIRouter( + prefix="/apps/{app_id}/users/{user_id}/collections", + tags=["collections"], +) + + +@router.get("", response_model=Page[schemas.Collection]) +async def get_collections( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + reverse: Optional[bool] = False, + filter: Optional[str] = None, + db=db, +): + """Get All Collections for a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client + application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + + Returns: + list[schemas.Collection]: List of Collection objects + + """ + data = None + if filter is not None: + data = json.loads(filter) + return await paginate( + db, + await crud.get_collections( + db, app_id=app_id, user_id=user_id, filter=data, reverse=reverse + ), + ) + + +# @router.get("/id/{collection_id}", response_model=schemas.Collection) +# def get_collection_by_id( +# request: Request, +# app_id: uuid.UUID, +# user_id: uuid.UUID, +# collection_id: uuid.UUID, +# db=db, +# ) -> schemas.Collection: +# honcho_collection = crud.get_collection_by_id( +# db, app_id=app_id, user_id=user_id, collection_id=collection_id +# ) +# if honcho_collection is None: +# raise HTTPException( +# status_code=404, detail="collection not found or does not belong to user" +# ) +# return honcho_collection + + +@router.get("/{name}", response_model=schemas.Collection) +async def get_collection_by_name( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + name: str, + db=db, +) -> schemas.Collection: + honcho_collection = await crud.get_collection_by_name( + db, app_id=app_id, user_id=user_id, name=name + ) + if honcho_collection is None: + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) + return honcho_collection + + +@router.post("", response_model=schemas.Collection) +async def create_collection( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection: schemas.CollectionCreate, + db=db, +): + if collection.name == "honcho": + raise HTTPException( + status_code=406, + detail="error invalid collection configuration - honcho is a reserved name", + ) + try: + return await crud.create_collection( + db, collection=collection, app_id=app_id, user_id=user_id + ) + except ValueError: + raise HTTPException( + status_code=406, + detail="Error invalid collection configuration - name may already exist", + ) from None + + +@router.put("/{collection_id}", response_model=schemas.Collection) +async def update_collection( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + collection: schemas.CollectionUpdate, + db=db, +): + if collection.name is None: + raise HTTPException( + status_code=400, detail="invalid request - name cannot be None" + ) + if collection.name == "honcho": + raise HTTPException( + status_code=406, + detail="error invalid collection configuration - honcho is a reserved name", + ) + + try: + honcho_collection = await crud.update_collection( + db, + collection=collection, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + ) + except ValueError: + raise HTTPException( + status_code=406, + detail="Error invalid collection configuration - name may already exist", + ) from None + return honcho_collection + + +@router.delete("/{collection_id}") +async def delete_collection( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + db=db, +): + response = await crud.delete_collection( + db, app_id=app_id, user_id=user_id, collection_id=collection_id + ) + if response: + return {"message": "Collection deleted successfully"} + else: + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) diff --git a/api/src/routers/documents.py b/api/src/routers/documents.py new file mode 100644 index 0000000..cb2b8f7 --- /dev/null +++ b/api/src/routers/documents.py @@ -0,0 +1,175 @@ +import json +from typing import Optional, Sequence +import uuid +from fastapi import APIRouter, HTTPException, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db + + +router = APIRouter( + prefix="/apps/{app_id}/users/{user_id}/collections/{collection_id}", + tags=["documents"], +) + + +@router.get("/documents", response_model=Page[schemas.Document]) +async def get_documents( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + reverse: Optional[bool] = False, + filter: Optional[str] = None, + db=db, +): + try: + data = None + if filter is not None: + data = json.loads(filter) + return await paginate( + db, + await crud.get_documents( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + filter=data, + reverse=reverse, + ), + ) + except ( + ValueError + ): # TODO can probably remove this exception ok to return empty here + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) from None + + +@router.get( + "/documents/{document_id}", + response_model=schemas.Document, +) +async def get_document( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + document_id: uuid.UUID, + db=db, +): + honcho_document = await crud.get_document( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) + if honcho_document is None: + raise HTTPException( + status_code=404, detail="document not found or does not belong to user" + ) + return honcho_document + + +@router.get("/query", response_model=Sequence[schemas.Document]) +async def query_documents( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + query: str, + top_k: int = 5, + filter: Optional[str] = None, + db=db, +): + if top_k is not None and top_k > 50: + top_k = 50 # TODO see if we need to paginate this + data = None + if filter is not None: + data = json.loads(filter) + return await crud.query_documents( + db=db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + query=query, + filter=data, + top_k=top_k, + ) + + +@router.post("/documents", response_model=schemas.Document) +async def create_document( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + document: schemas.DocumentCreate, + db=db, +): + try: + return await crud.create_document( + db, + document=document, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + ) + except ValueError: + raise HTTPException( + status_code=404, detail="collection not found or does not belong to user" + ) from None + + +@router.put( + "/documents/{document_id}", + response_model=schemas.Document, +) +async def update_document( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + document_id: uuid.UUID, + document: schemas.DocumentUpdate, + db=db, +): + if document.content is None and document.metadata is None: + raise HTTPException( + status_code=400, detail="content and metadata cannot both be None" + ) + return await crud.update_document( + db, + document=document, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) + + +@router.delete("/documents/{document_id}") +async def delete_document( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + collection_id: uuid.UUID, + document_id: uuid.UUID, + db=db, +): + response = await crud.delete_document( + db, + app_id=app_id, + user_id=user_id, + collection_id=collection_id, + document_id=document_id, + ) + if response: + return {"message": "Document deleted successfully"} + else: + raise HTTPException( + status_code=404, detail="document not found or does not belong to user" + ) diff --git a/api/src/routers/messages.py b/api/src/routers/messages.py new file mode 100644 index 0000000..e901dc5 --- /dev/null +++ b/api/src/routers/messages.py @@ -0,0 +1,138 @@ +import json +from typing import Optional +import uuid +from fastapi import APIRouter, HTTPException, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db + + +router = APIRouter( + prefix="/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages", + tags=["messages"], +) + + +@router.post("", response_model=schemas.Message) +async def create_message_for_session( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message: schemas.MessageCreate, + db=db, +): + """Adds a message to a session + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to add the message to + message (schemas.MessageCreate): The Message object to add containing the + message content and type + + Returns: + schemas.Message: The Message object of the added message + + Raises: + HTTPException: If the session is not found + + """ + try: + return await crud.create_message( + db, message=message, app_id=app_id, user_id=user_id, session_id=session_id + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + +@router.get("", response_model=Page[schemas.Message]) +async def get_messages( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + reverse: Optional[bool] = False, + filter: Optional[str] = None, + db=db, +): + """Get all messages for a session + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + reverse (bool): Whether to reverse the order of the messages + + Returns: + list[schemas.Message]: List of Message objects + + Raises: + HTTPException: If the session is not found + + """ + try: + data = None + if filter is not None: + data = json.loads(filter) + return await paginate( + db, + await crud.get_messages( + db, + app_id=app_id, + user_id=user_id, + session_id=session_id, + filter=data, + reverse=reverse, + ), + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + +@router.get("/{message_id}", response_model=schemas.Message) +async def get_message( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, + db=db, +): + """ """ + honcho_message = await crud.get_message( + db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id + ) + if honcho_message is None: + raise HTTPException(status_code=404, detail="Session not found") + return honcho_message + + +@router.put("/{message_id}", response_model=schemas.Message) +async def update_message( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, + message: schemas.MessageUpdate, + db=db, +): + """Update's the metadata of a message""" + if message.metadata is None: + raise HTTPException(status_code=400, detail="Message metadata cannot be empty") + try: + return await crud.update_message( + db, + message=message, + app_id=app_id, + user_id=user_id, + session_id=session_id, + message_id=message_id, + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None diff --git a/api/src/routers/metamessages.py b/api/src/routers/metamessages.py new file mode 100644 index 0000000..bf83329 --- /dev/null +++ b/api/src/routers/metamessages.py @@ -0,0 +1,173 @@ +import json +from typing import Optional +import uuid +from fastapi import APIRouter, HTTPException, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db + + +router = APIRouter( + prefix="/apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages", + tags=["messages"], +) + + +@router.post("", response_model=schemas.Metamessage) +async def create_metamessage( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + metamessage: schemas.MetamessageCreate, + db=db, +): + """Adds a message to a session + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to add the message to + message (schemas.MessageCreate): The Message object to add containing the + message content and type + + Returns: + schemas.Message: The Message object of the added message + + Raises: + HTTPException: If the session is not found + + """ + try: + return await crud.create_metamessage( + db, + metamessage=metamessage, + app_id=app_id, + user_id=user_id, + session_id=session_id, + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + +@router.get("", response_model=Page[schemas.Metamessage]) +async def get_metamessages( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: Optional[uuid.UUID] = None, + metamessage_type: Optional[str] = None, + reverse: Optional[bool] = False, + filter: Optional[str] = None, + db=db, +): + """Get all messages for a session + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + reverse (bool): Whether to reverse the order of the metamessages + + Returns: + list[schemas.Message]: List of Message objects + + Raises: + HTTPException: If the session is not found + + """ + try: + data = None + if filter is not None: + data = json.loads(filter) + return await paginate( + db, + await crud.get_metamessages( + db, + app_id=app_id, + user_id=user_id, + session_id=session_id, + message_id=message_id, + metamessage_type=metamessage_type, + filter=data, + reverse=reverse, + ), + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + +@router.get( + "/{metamessage_id}", + response_model=schemas.Metamessage, +) +async def get_metamessage( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + message_id: uuid.UUID, + metamessage_id: uuid.UUID, + db=db, +): + """Get a specific Metamessage by ID + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + session_id (int): The ID of the Session to retrieve + + Returns: + schemas.Session: The Session object of the requested Session + + Raises: + HTTPException: If the session is not found + """ + honcho_metamessage = await crud.get_metamessage( + db, + app_id=app_id, + session_id=session_id, + user_id=user_id, + message_id=message_id, + metamessage_id=metamessage_id, + ) + if honcho_metamessage is None: + raise HTTPException(status_code=404, detail="Session not found") + return honcho_metamessage + + +@router.put( + "/{metamessage_id}", + response_model=schemas.Metamessage, +) +async def update_metamessage( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + metamessage_id: uuid.UUID, + metamessage: schemas.MetamessageUpdate, + db=db, +): + """Update's the metadata of a metamessage""" + if metamessage.metadata is None: + raise HTTPException( + status_code=400, detail="Metamessage metadata cannot be empty" + ) + try: + return await crud.update_metamessage( + db, + metamessage=metamessage, + app_id=app_id, + user_id=user_id, + session_id=session_id, + metamessage_id=metamessage_id, + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None diff --git a/api/src/routers/sessions.py b/api/src/routers/sessions.py new file mode 100644 index 0000000..50a65f8 --- /dev/null +++ b/api/src/routers/sessions.py @@ -0,0 +1,193 @@ +import json +from typing import Optional +import uuid +from fastapi import APIRouter, HTTPException, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import agent, crud, schemas +from src.dependencies import db + + +router = APIRouter( + prefix="/apps/{app_id}/users/{user_id}/sessions", + tags=["sessions"], +) + + +@router.get("", response_model=Page[schemas.Session]) +async def get_sessions( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + location_id: Optional[str] = None, + is_active: Optional[bool] = False, + reverse: Optional[bool] = False, + filter: Optional[str] = None, + db=db, +): + """Get All Sessions for a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + location_id (str, optional): Optional Location ID representing the location of a + session + + Returns: + list[schemas.Session]: List of Session objects + + """ + + data = None + if filter is not None: + data = json.loads(filter) + + return await paginate( + db, + await crud.get_sessions( + db, + app_id=app_id, + user_id=user_id, + location_id=location_id, + reverse=reverse, + is_active=is_active, + filter=data, + ), + ) + + +@router.post("", response_model=schemas.Session) +async def create_session( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session: schemas.SessionCreate, + db=db, +): + """Create a Session for a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client + application using honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session (schemas.SessionCreate): The Session object containing any + metadata and a location ID + + Returns: + schemas.Session: The Session object of the new Session + + """ + value = await crud.create_session( + db, app_id=app_id, user_id=user_id, session=session + ) + return value + + +@router.put("/{session_id}", response_model=schemas.Session) +async def update_session( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + session: schemas.SessionUpdate, + db=db, +): + """Update the metadata of a Session + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to update + session (schemas.SessionUpdate): The Session object containing any new metadata + + Returns: + schemas.Session: The Session object of the updated Session + + """ + if session.metadata is None: + raise HTTPException(status_code=400, detail="Session metadata cannot be empty") + try: + return await crud.update_session( + db, app_id=app_id, user_id=user_id, session_id=session_id, session=session + ) + except ValueError: + raise HTTPException(status_code=404, detail="Session not found") from None + + +@router.delete("/{session_id}") +async def delete_session( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + db=db, +): + """Delete a session by marking it as inactive + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to delete + + Returns: + dict: A message indicating that the session was deleted + + Raises: + HTTPException: If the session is not found + + """ + response = await crud.delete_session( + db, app_id=app_id, user_id=user_id, session_id=session_id + ) + if response: + return {"message": "Session deleted successfully"} + else: + raise HTTPException(status_code=404, detail="Session not found") + + +@router.get("/{session_id}", response_model=schemas.Session) +async def get_session( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + db=db, +): + """Get a specific session for a user by ID + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (uuid.UUID): The User ID representing the user, managed by the user + session_id (uuid.UUID): The ID of the Session to retrieve + + Returns: + schemas.Session: The Session object of the requested Session + + Raises: + HTTPException: If the session is not found + """ + honcho_session = await crud.get_session( + db, app_id=app_id, session_id=session_id, user_id=user_id + ) + if honcho_session is None: + raise HTTPException(status_code=404, detail="Session not found") + return honcho_session + + +@router.get("/{session_id}/chat", response_model=schemas.AgentChat) +async def get_chat( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + session_id: uuid.UUID, + query: str, + db=db, +): + return await agent.chat( + app_id=app_id, user_id=user_id, session_id=session_id, query=query, db=db + ) diff --git a/api/src/routers/users.py b/api/src/routers/users.py new file mode 100644 index 0000000..c79c234 --- /dev/null +++ b/api/src/routers/users.py @@ -0,0 +1,128 @@ +import json +from typing import Optional +import uuid +from fastapi import APIRouter, Request +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db + +router = APIRouter( + prefix="/apps/{app_id}/users", + tags=["users"], +) + + +@router.post("", response_model=schemas.User) +async def create_user( + request: Request, + app_id: uuid.UUID, + user: schemas.UserCreate, + db=db, +): + """Create a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user (schemas.UserCreate): The User object containing any metadata + + Returns: + schemas.User: Created User object + + """ + print("running create_user") + return await crud.create_user(db, app_id=app_id, user=user) + + +@router.get("", response_model=Page[schemas.User]) +async def get_users( + request: Request, + app_id: uuid.UUID, + reverse: bool = False, + filter: Optional[str] = None, + db=db, +): + """Get All Users for an App + + Args: + app_id (uuid.UUID): The ID of the app representing the client + application using honcho + + Returns: + list[schemas.User]: List of User objects + + """ + data = None + if filter is not None: + data = json.loads(filter) + + return await paginate( + db, await crud.get_users(db, app_id=app_id, reverse=reverse, filter=data) + ) + + +@router.get("/{name}", response_model=schemas.User) +async def get_user_by_name( + request: Request, + app_id: uuid.UUID, + name: str, + db=db, +): + """Get a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + + Returns: + schemas.User: User object + + """ + return await crud.get_user_by_name(db, app_id=app_id, name=name) + + +@router.get("/get_or_create/{name}", response_model=schemas.User) +async def get_or_create_user(request: Request, app_id: uuid.UUID, name: str, db=db): + """Get or Create a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + + Returns: + schemas.User: User object + + """ + user = await crud.get_user_by_name(db, app_id=app_id, name=name) + if user is None: + user = await crud.create_user( + db, app_id=app_id, user=schemas.UserCreate(name=name) + ) + return user + + +@router.put("/{user_id}", response_model=schemas.User) +async def update_user( + request: Request, + app_id: uuid.UUID, + user_id: uuid.UUID, + user: schemas.UserUpdate, + db=db, +): + """Update a User + + Args: + app_id (uuid.UUID): The ID of the app representing the client application using + honcho + user_id (str): The User ID representing the user, managed by the user + user (schemas.UserCreate): The User object containing any metadata + + Returns: + schemas.User: Updated User object + + """ + return await crud.update_user(db, app_id=app_id, user_id=user_id, user=user) From 04a6764d7f554a8c24912c7cad711bcd55a281b2 Mon Sep 17 00:00:00 2001 From: hyusap Date: Sun, 17 Mar 2024 17:31:51 -0400 Subject: [PATCH 69/85] full docstring coverage --- scripts/syncronizer.py | 1 + sdk/honcho/__init__.py | 2 ++ sdk/honcho/cache.py | 7 +++++++ sdk/honcho/client.py | 15 ++++++++++++++- sdk/honcho/ext/__init__.py | 1 + sdk/honcho/ext/langchain.py | 10 ++++++++++ sdk/honcho/schemas.py | 13 +++++++++++++ sdk/honcho/sync_client.py | 15 ++++++++++++++- 8 files changed, 62 insertions(+), 2 deletions(-) diff --git a/scripts/syncronizer.py b/scripts/syncronizer.py index 0740bb4..830f02c 100755 --- a/scripts/syncronizer.py +++ b/scripts/syncronizer.py @@ -13,6 +13,7 @@ sync_code = re.sub(r"async\s", "", source_code) sync_code = re.sub(r"await\s", "", sync_code) sync_code = re.sub(r"Async", "", sync_code) +sync_code = re.sub(r"asynchronous", "synchronous", sync_code) # Write the modified code to the destination file destination_file_path = os.path.join(this_dir, "../sdk/honcho/sync_client.py") diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index 6ab9451..61da0ff 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -1,3 +1,5 @@ +"""Honcho is a Python client for the Honcho API.""" + from .client import ( AsyncHoncho, AsyncUser, diff --git a/sdk/honcho/cache.py b/sdk/honcho/cache.py index f9488a0..f8e6b5e 100644 --- a/sdk/honcho/cache.py +++ b/sdk/honcho/cache.py @@ -1,11 +1,18 @@ +""" +This module provides an LRU (Least Recently Used) cache implementation as part of the Honcho SDK's caching mechanisms. +""" + from collections import OrderedDict + class LRUCache: """ An implementation of a basic LRUcache that utilizes the built in OrderedDict data structure. """ + def __init__(self, capacity: int): + """Initialize the cache""" self.capacity = capacity self.cache = OrderedDict() diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index b35ec1e..2e6ed7b 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,3 +1,7 @@ +""" +This module provides asynchronous client functionality for interacting with the Honcho API. +""" + from __future__ import annotations import datetime @@ -62,6 +66,11 @@ def __init__( ] async def next(self): + """Get the next page of results + + Returns: + AsyncGetUserPage | None: Next Page of Results or None if there are no more users to retreive from a query + """ if self.page >= self.pages: return None return await self.honcho.get_users( @@ -176,6 +185,8 @@ async def next(self): class AsyncGetMetamessagePage(AsyncGetPage): + """Paginated Results for Get Metamessage Requests""" + def __init__( self, response: dict, @@ -335,6 +346,7 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict async def initialize(self): + """Initialize the Honcho client from the server""" res = await self.client.get( f"{self.server_url}/apps/get_or_create/{self.app_name}" ) @@ -345,7 +357,7 @@ async def initialize(self): @property def base_url(self): - """Shorcut for common API prefix. made a property to prevent tampering""" + """Shortcut for common API prefix. made a property to prevent tampering""" return f"{self.server_url}/apps/{self.app_id}" async def update(self, metadata: dict): @@ -1156,6 +1168,7 @@ async def close(self): self._is_active = False async def chat(self, query) -> str: + """Ask Honcho for information about the user session""" url = f"{self.base_url}/chat" params = {"query": query} response = await self.user.honcho.client.get(url, params=params) diff --git a/sdk/honcho/ext/__init__.py b/sdk/honcho/ext/__init__.py index e69de29..bd84b90 100644 --- a/sdk/honcho/ext/__init__.py +++ b/sdk/honcho/ext/__init__.py @@ -0,0 +1 @@ +"""Extensions/utilities for the Honcho Ecosystem""" diff --git a/sdk/honcho/ext/langchain.py b/sdk/honcho/ext/langchain.py index c84a0be..765b05b 100644 --- a/sdk/honcho/ext/langchain.py +++ b/sdk/honcho/ext/langchain.py @@ -1,3 +1,7 @@ +""" +Utilities to integrate Honcho with Langchain projects +""" + import functools import importlib from typing import List @@ -6,8 +10,12 @@ def requires_langchain(func): + """A utility to check if langchain is installed before running a function""" + @functools.wraps(func) def wrapper(*args, **kwargs): + """Check if langchain is installed before running a function""" + if importlib.util.find_spec("langchain") is None: raise ImportError("Langchain must be installed to use this feature") # raise RuntimeError("langchain is not installed") @@ -18,6 +26,8 @@ def wrapper(*args, **kwargs): @requires_langchain def langchain_message_converter(messages: List[Message]): + """Converts Honcho messages to Langchain messages""" + from langchain_core.messages import AIMessage, HumanMessage new_messages = [] diff --git a/sdk/honcho/schemas.py b/sdk/honcho/schemas.py index 1a0a2fe..3eaead7 100644 --- a/sdk/honcho/schemas.py +++ b/sdk/honcho/schemas.py @@ -1,8 +1,14 @@ +""" +This module defines the schema classes for various entities such as Message, Metamessage, and Document. +""" + import uuid import datetime class Message: + """Class representing a Message""" + def __init__( self, session_id: uuid.UUID, @@ -21,10 +27,13 @@ def __init__( self.created_at = created_at def __str__(self): + """String representation of Message object""" return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" class Metamessage: + """Class representing a Metamessage""" + def __init__( self, id: uuid.UUID, @@ -43,10 +52,13 @@ def __init__( self.created_at = created_at def __str__(self): + """String representation of Metamessage object""" return f"Metamessage(id={self.id}, message_id={self.message_id}, metamessage_type={self.metamessage_type}, content={self.content})" class Document: + """Class representing a Document""" + def __init__( self, id: uuid.UUID, @@ -63,4 +75,5 @@ def __init__( self.created_at = created_at def __str__(self) -> str: + """String representation of Document object""" return f"Document(id={self.id}, metadata={self.metadata}, content={self.content}, created_at={self.created_at})" diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 5322812..a587a77 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -1,3 +1,7 @@ +""" +This module provides synchronous client functionality for interacting with the Honcho API. +""" + from __future__ import annotations import datetime @@ -62,6 +66,11 @@ def __init__( ] def next(self): + """Get the next page of results + + Returns: + GetUserPage | None: Next Page of Results or None if there are no more users to retreive from a query + """ if self.page >= self.pages: return None return self.honcho.get_users( @@ -176,6 +185,8 @@ def next(self): class GetMetamessagePage(GetPage): + """Paginated Results for Get Metamessage Requests""" + def __init__( self, response: dict, @@ -335,6 +346,7 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict def initialize(self): + """Initialize the Honcho client from the server""" res = self.client.get( f"{self.server_url}/apps/get_or_create/{self.app_name}" ) @@ -345,7 +357,7 @@ def initialize(self): @property def base_url(self): - """Shorcut for common API prefix. made a property to prevent tampering""" + """Shortcut for common API prefix. made a property to prevent tampering""" return f"{self.server_url}/apps/{self.app_id}" def update(self, metadata: dict): @@ -1156,6 +1168,7 @@ def close(self): self._is_active = False def chat(self, query) -> str: + """Ask Honcho for information about the user session""" url = f"{self.base_url}/chat" params = {"query": query} response = self.user.honcho.client.get(url, params=params) From 3f1a1f7adbb355fbd3ce085c4b361e9bcb078943 Mon Sep 17 00:00:00 2001 From: hyusap Date: Sun, 17 Mar 2024 17:06:14 -0400 Subject: [PATCH 70/85] remove unused imports and fix env issue --- api/src/db.py | 2 +- api/src/main.py | 22 +++------------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/api/src/db.py b/api/src/db.py index 7da3dd2..5c431d8 100644 --- a/api/src/db.py +++ b/api/src/db.py @@ -5,7 +5,7 @@ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from sqlalchemy.ext.declarative import declarative_base -load_dotenv() +load_dotenv(override=True) connect_args = {} diff --git a/api/src/main.py b/api/src/main.py index fec8f98..45f9cfd 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,32 +1,19 @@ import json import logging import os -import uuid from contextlib import asynccontextmanager -from typing import Optional, Sequence import sentry_sdk from fastapi import ( APIRouter, - Depends, FastAPI, - HTTPException, - Request, ) from fastapi.responses import PlainTextResponse -from fastapi_pagination import Page, add_pagination -from fastapi_pagination.ext.sqlalchemy import paginate +from fastapi_pagination import add_pagination from opentelemetry import trace from opentelemetry._logs import ( - SeverityNumber, - get_logger, - get_logger_provider, set_logger_provider, - std_to_otel, ) - -# from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter -# from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.http._log_exporter import ( OTLPLogExporter, ) @@ -53,12 +40,9 @@ from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address -from sqlalchemy.ext.asyncio import AsyncSession from starlette.exceptions import HTTPException as StarletteHTTPException -from . import agent, crud, schemas from .db import engine, scaffold_db -from src.dependencies import db from src.routers import ( apps, users, @@ -108,11 +92,11 @@ def otel_trace_init(): otlp_span_exporter = OTLPSpanExporter( endpoint=otel_endpoint_url, headers=otel_http_headers ) - trace.get_tracer_provider().add_span_processor( + trace.get_tracer_provider().add_span_processor( # type: ignore BatchSpanProcessor(otlp_span_exporter) ) if DEBUG_LOG_OTEL_TO_CONSOLE: - trace.get_tracer_provider().add_span_processor( + trace.get_tracer_provider().add_span_processor( # type: ignore SimpleSpanProcessor(ConsoleSpanExporter()) ) From 2c1c1fb07919d7dffd7bc0c1fd0923497ae6e668 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:55:32 -0700 Subject: [PATCH 71/85] Update docker-compose connection uri and remove auto-stop to deriver process --- README.md | 5 +- api/docker-compose.yml.example | 4 +- api/fly.toml | 10 + .../discord/honcho-fact-memory/poetry.lock | 586 +++++++++--------- .../discord/honcho-fact-memory/pyproject.toml | 3 +- 5 files changed, 317 insertions(+), 291 deletions(-) diff --git a/README.md b/README.md index b0539c3..9fb1338 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,10 @@ alternatively if you are using poetry run: poetry add honcho-ai ``` -checkout the [example folder](./example/) for examples of how to use the sdk +checkout the [SDK Reference](https://api.python.honcho.dev) for a detailed +look at the different methods and how to use them. + +Also, check out the[example folder](./example/) for examples of how to use the sdk #### Use Locally diff --git a/api/docker-compose.yml.example b/api/docker-compose.yml.example index 60fc24d..5be0752 100644 --- a/api/docker-compose.yml.example +++ b/api/docker-compose.yml.example @@ -10,7 +10,7 @@ services: - .:/app environment: - DATABASE_TYPE=postgres - - CONNECTION_URI=postgresql://testuser:testpwd@database:5432/honcho + - CONNECTION_URI=postgresql+psycopg://testuser:testpwd@database:5432/honcho - OPENAI_API_KEY=[YOUR_OPENAI_API_KEY] - OPENTELEMETRY_ENABLED=false - SENTRY_ENABLED=false @@ -28,6 +28,8 @@ services: database: image: ankane/pgvector restart: always + ports: + - 5432:5432 environment: - POSTGRES_DB=honcho - POSTGRES_USER=testuser diff --git a/api/fly.toml b/api/fly.toml index 104eaa8..2012518 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -16,6 +16,16 @@ kill_timeout = "5s" api = "python -m uvicorn src.main:app --host 0.0.0.0 --port 8000" deriver = "python -m src.harvester" +[[services]] + auto_stop_machines = false + auto_start_machines = true + min_machines_running = 1 + processes = ["deriver"] + protocol = "tcp" + [services.concurrency] + hard_limit = 250 + soft_limit = 200 + [http_service] internal_port = 8000 auto_stop_machines = false diff --git a/example/discord/honcho-fact-memory/poetry.lock b/example/discord/honcho-fact-memory/poetry.lock index 46acc45..e81d8d9 100644 --- a/example/discord/honcho-fact-memory/poetry.lock +++ b/example/discord/honcho-fact-memory/poetry.lock @@ -1,117 +1,106 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.3" description = "Async http client/server framework (asyncio)" +category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [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"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -126,6 +115,7 @@ frozenlist = ">=1.1.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -135,13 +125,14 @@ files = [ [[package]] name = "anyio" -version = "4.2.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] @@ -153,21 +144,11 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] -[[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.2.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -187,6 +168,7 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -198,6 +180,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -297,6 +280,7 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -308,6 +292,7 @@ files = [ name = "dataclasses-json" version = "0.6.4" description = "Easily serialize dataclasses to and from JSON." +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -323,6 +308,7 @@ typing-inspect = ">=0.4.0,<1" name = "distro" version = "1.9.0" description = "Distro - an OS platform information API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -334,6 +320,7 @@ files = [ name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -420,6 +407,7 @@ files = [ name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -491,6 +479,7 @@ test = ["objgraph", "psutil"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -500,13 +489,14 @@ files = [ [[package]] name = "honcho-ai" -version = "0.0.3" +version = "0.0.5" description = "Python Client SDK for Honcho" +category = "main" optional = false -python-versions = ">=3.10,<4.0" +python-versions = ">=3.9,<4.0" files = [ - {file = "honcho_ai-0.0.3-py3-none-any.whl", hash = "sha256:a817ec62c4fd8dad1d629927511ce98a3f626f4bc55474187b80010e208e61ba"}, - {file = "honcho_ai-0.0.3.tar.gz", hash = "sha256:ca52bb8c5036bfdbeee0c71ca754c580c672b28a4824240123b783f8679ca18e"}, + {file = "honcho_ai-0.0.5-py3-none-any.whl", hash = "sha256:3278aac9dd80cde10fa3216cc38e84eae90acc0b5c0fac986abb40973a8fe156"}, + {file = "honcho_ai-0.0.5.tar.gz", hash = "sha256:7283ae2ee3baa5f0429c9ea2eb9f1e74b3fb164d5a7448ee7872ba9847500ae6"}, ] [package.dependencies] @@ -514,13 +504,14 @@ httpx = ">=0.26.0,<0.27.0" [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -530,13 +521,14 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -547,20 +539,21 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = "==1.*" +httpcore = ">=1.0.0,<2.0.0" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -572,6 +565,7 @@ files = [ name = "jsonpatch" version = "1.33" description = "Apply JSON-Patches (RFC 6902)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -586,6 +580,7 @@ jsonpointer = ">=1.9" name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -597,6 +592,7 @@ files = [ name = "langchain-community" version = "0.0.20" description = "Community contributed LangChain integrations." +category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -623,6 +619,7 @@ extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15. name = "langchain-core" version = "0.1.23" description = "Building applications with LLMs through composability" +category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -647,6 +644,7 @@ extended-testing = ["jinja2 (>=3,<4)"] name = "langchain-openai" version = "0.0.6" description = "An integration package connecting OpenAI and LangChain" +category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -664,6 +662,7 @@ tiktoken = ">=0.5.2,<1" name = "langsmith" version = "0.0.87" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -677,28 +676,29 @@ requests = ">=2,<3" [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.21.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "multidict" version = "6.0.5" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -798,6 +798,7 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -809,6 +810,7 @@ files = [ name = "numpy" version = "1.26.4" description = "Fundamental package for array computing in Python" +category = "main" optional = false python-versions = ">=3.9" files = [ @@ -852,13 +854,14 @@ files = [ [[package]] name = "openai" -version = "1.12.0" +version = "1.14.1" description = "The official Python library for the openai API" +category = "main" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, - {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, + {file = "openai-1.14.1-py3-none-any.whl", hash = "sha256:f9322b0bf3b82bbd06930fad535369a023f35a3a96d3ef0b827644a15d7aae97"}, + {file = "openai-1.14.1.tar.gz", hash = "sha256:1fab5dd623cdc0c7c6e7da5d8d11fa6900f94191c2dfb6510d7eac33195fa175"}, ] [package.dependencies] @@ -877,6 +880,7 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -886,37 +890,39 @@ files = [ [[package]] name = "py-cord" -version = "2.4.1" +version = "2.5.0" description = "A Python wrapper for the Discord API" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "py-cord-2.4.1.tar.gz", hash = "sha256:0266c9d9a9d2397622a0e5ead09826690e688ba3cf14c470167b81e6cd2d8a56"}, - {file = "py_cord-2.4.1-py3-none-any.whl", hash = "sha256:862a372c364cd263e2c8e696c64887f969c02cbdf0fdd6b09f0283e9dd67a290"}, + {file = "py-cord-2.5.0.tar.gz", hash = "sha256:faf08af5da5eac2ed3d1c8a43d8307d5a1e3f01602def283330c9d2cde0b1162"}, + {file = "py_cord-2.5.0-py3-none-any.whl", hash = "sha256:9e5fc79feec5a48f53aa4c066b57dd75fe67d29021b042d12f378a513d308bbc"}, ] [package.dependencies] -aiohttp = ">=3.6.0,<3.9.0" +aiohttp = ">=3.6.0,<4.0" [package.extras] -docs = ["furo", "myst-parser (==0.18.1)", "sphinx (==5.3.0)", "sphinx-autodoc-typehints (==1.22)", "sphinx-copybutton (==0.5.1)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "sphinxext-opengraph (==0.8.1)"] -speed = ["aiohttp[speedups]", "orjson (>=3.5.4)"] +docs = ["furo (==2023.3.23)", "myst-parser (==1.0.0)", "sphinx (==5.3.0)", "sphinx-autodoc-typehints (==1.23.0)", "sphinx-copybutton (==0.5.2)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "sphinxext-opengraph (==0.9.1)"] +speed = ["aiohttp[speedups]", "msgspec (>=0.18.6,<0.19.0)"] voice = ["PyNaCl (>=1.3.0,<1.6)"] [[package]] name = "pydantic" -version = "2.6.1" +version = "2.6.4" description = "Data validation using Python type hints" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -924,90 +930,91 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.2" +version = "2.16.3" description = "" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] @@ -1017,6 +1024,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1031,6 +1039,7 @@ cli = ["click (>=5.0)"] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1039,7 +1048,6 @@ files = [ {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"}, @@ -1047,15 +1055,8 @@ files = [ {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"}, @@ -1072,7 +1073,6 @@ files = [ {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"}, @@ -1080,7 +1080,6 @@ files = [ {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"}, @@ -1090,6 +1089,7 @@ files = [ name = "regex" version = "2023.12.25" description = "Alternative regular expression module, to replace re." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1192,6 +1192,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1211,71 +1212,73 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" +category = "main" 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"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.28" description = "Database Abstraction Library" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -1311,6 +1314,7 @@ sqlcipher = ["sqlcipher3_binary"] name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1325,6 +1329,7 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "tiktoken" version = "0.6.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1377,6 +1382,7 @@ blobfile = ["blobfile (>=2)"] name = "tqdm" version = "4.66.2" description = "Fast, Extensible Progress Meter" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1395,19 +1401,21 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "typing-inspect" version = "0.9.0" description = "Runtime inspection utilities for typing module." +category = "main" optional = false python-versions = "*" files = [ @@ -1421,13 +1429,14 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] @@ -1440,6 +1449,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.4" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1542,4 +1552,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ec01ccebabbdd1053e6dc7778ff629842adeff8e97200636460028a1338d656f" +content-hash = "3f4ec2cb731a6b5b3cd11481853b12565c08a6b48f9602977a1bececec308720" diff --git a/example/discord/honcho-fact-memory/pyproject.toml b/example/discord/honcho-fact-memory/pyproject.toml index f4c093e..d9d2aa7 100644 --- a/example/discord/honcho-fact-memory/pyproject.toml +++ b/example/discord/honcho-fact-memory/pyproject.toml @@ -13,7 +13,8 @@ openai = "^1.12.0" py-cord = "^2.4.1" python-dotenv = "^1.0.1" langchain-openai = "^0.0.6" -honcho-ai = "0.0.3" +honcho-ai = "0.0.5" +aiohttp = "^3.9.3" [build-system] From ad5a16fdce050ea1b16ad27b485f66fd76b38f98 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:43:33 -0700 Subject: [PATCH 72/85] Docstrings and langchain message converter in reverse --- sdk/honcho/client.py | 188 ++++++++++++++++++++++++--------- sdk/honcho/ext/langchain.py | 26 ++++- sdk/honcho/sync_client.py | 204 ++++++++++++++++++++++++++---------- sdk/pyproject.toml | 25 +++-- 4 files changed, 318 insertions(+), 125 deletions(-) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 2e6ed7b..4dd96ce 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -49,6 +49,7 @@ def __init__( Args: response (dict): Response from API with pagination information honcho (AsyncHoncho): Honcho Client + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -99,7 +100,10 @@ def __init__( response (dict): Response from API with pagination information user (AsyncUser): Honcho User associated with the session reverse (bool): Whether to reverse the order of the results or not - location_id (str): ID of the location associated with the session + filter (dict, optional): Key value filter to apply to the results + location_id (str, optional): ID of the location associated with the session + is_active (bool): boolean filter for restricint results to only active + sessions """ super().__init__(response) self.user = user @@ -152,6 +156,7 @@ def __init__( Args: response (dict): Response from API with pagination information session (AsyncSession): Session the returned messages are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -202,9 +207,10 @@ def __init__( response (dict): Response from API with pagination information session (AsyncSession): Session the returned messages are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results - message_id (Optional[str]): ID of the message associated with the - metamessage_type (Optional[str]): Type of the metamessage + message_id (str, optional): ID of the message associated with the + metamessage_type (str, optional): Type of the metamessage """ super().__init__(response) self.session = session @@ -255,6 +261,7 @@ def __init__( response (dict): Response from API with pagination information collection (AsyncCollection): Collection the returned documents are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -300,6 +307,7 @@ def __init__( Args: response (dict): Response from API with pagination information user (AsyncUser): Honcho Client + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -338,7 +346,13 @@ class AsyncHoncho: """Honcho API Client Object""" def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): - """Constructor for Client""" + """Constructor for Client + + Args: + app_name (str): Name of the application + base_url (str): Base URL for the instance of the Honcho API defaults to + https://demo.honcho.dev + """ self.server_url: str = base_url # Base URL for the instance of the Honcho API self.client: httpx.AsyncClient = httpx.AsyncClient() self.app_name: str = app_name # Representing name of the client application @@ -346,7 +360,7 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict async def initialize(self): - """Initialize the Honcho client from the server""" + """Run initialization tasks for the Honcho client""" res = await self.client.get( f"{self.server_url}/apps/get_or_create/{self.app_name}" ) @@ -355,6 +369,10 @@ async def initialize(self): self.app_id: uuid.UUID = data["id"] self.metadata: dict = data["metadata"] + async def init(self): + """Synonym for initialize""" + await self.initialize() + @property def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" @@ -452,9 +470,11 @@ async def get_users( """Get Paginated list of users Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return. Defaults to 50 + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to False Returns: AsyncGetUserPage: Paginated list of users @@ -483,7 +503,9 @@ async def get_users_generator( all users in an app Args: - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to False Yields: AsyncUser: The User object @@ -519,7 +541,7 @@ async def get_users_generator( class AsyncUser: - """Represents a single user in an app""" + """Represents a single user in an app and associated methods""" def __init__( self, @@ -528,7 +550,18 @@ def __init__( metadata: dict, created_at: datetime.datetime, ): - """Constructor for User""" + """Constructor for User + + Args: + honcho (AsyncHoncho): The honcho object + id (uuid.UUID): The id of the user + metadata (dict): The metadata for the user + created_at (datetime.datetime): The time the user was created + + Returns: + AsyncUser: The created User object + + """ # self.base_url: str = honcho.base_url self.honcho: AsyncHoncho = honcho self.id: uuid.UUID = id @@ -542,7 +575,7 @@ def base_url(self): def __str__(self): """String representation of User""" - return f"AsyncUser(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + return f"AsyncUser(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" async def update(self, metadata: dict): """Updates a user's metadata @@ -600,11 +633,13 @@ async def get_sessions( Args: location_id (str, optional): Optional Location ID representing the - location of a session - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return - reverse (bool): Whether to reverse the order of the results - is_active (bool): Whether to only return active sessions + location of a session. Defaults to None + filter (dict, optional): The key value filter to apply. Defaults to None + page (int, optional): The page of results to return. Defaults to 1 + page_size (int, optional): The number of results to return. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False + is_active (bool): Whether to only return active sessions. Defaults to False Returns: AsyncGetSessionPage: Page or results for get_sessions query @@ -644,8 +679,10 @@ async def get_sessions_generator( Args: location_id (str, optional): Optional Location ID representing the location of a session - reverse (bool): Whether to reverse the order of the results - is_active (bool): Whether to only return active sessions + reverse (bool): Whether to reverse the order of the results. Defaults to + False + is_active (bool): Whether to only return active sessions. Defaults to False + filter (dict, optional): The key value filter to apply. Defaults to None Yields: AsyncSession: The Session object of the requested Session @@ -673,8 +710,8 @@ async def create_session( Args: location_id (str, optional): Optional Location ID representing the - location of a session - metadata (dict, optional): Optional session metadata + location of a session. Defaults to "default" + metadata (dict, optional): Optional session metadata. Defaults to None Returns: AsyncSession: The Session object of the new Session @@ -705,6 +742,7 @@ async def create_collection( Args: name (str): unique name for the collection for the user + metadata (dict, optional): Optional metadata. Defaults to None Returns: AsyncCollection: The Collection object of the new Collection @@ -757,15 +795,16 @@ async def get_collections( """Return collections associated with a user paginated Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return reverse (bool): Whether to reverse the order of the results Returns: AsyncGetCollectionPage: Page or results for get_collections query """ - # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/collections" params = { "page": page, @@ -787,7 +826,9 @@ async def get_collections_generator( all sessions for a user in an app Args: - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False Yields: AsyncCollection: The Session object of the requested Session @@ -821,7 +862,20 @@ def __init__( is_active: bool, created_at: datetime.datetime, ): - """Constructor for Session""" + """Constructor for Session + + Args: + user (AsyncUser): The user object + id (uuid.UUID): The id of the session + location_id (str): The id of the location associated with the session + metadata (dict): The metadata associated with the session + is_active (bool): Whether the session is active + created_at (datetime.datetime): When the session was created + + Returns: + AsyncSession: The Session object + + """ self.user: AsyncUser = user self.id: uuid.UUID = id self.location_id: str = location_id @@ -836,7 +890,7 @@ def base_url(self): def __str__(self): """String representation of Session""" - return f"AsyncSession(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" # noqa: E501 + return f"AsyncSession(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" @property def is_active(self): @@ -851,6 +905,7 @@ async def create_message( Args: is_user (bool): Whether the message is from the user content (str): The content of the message + metadata (dict, optional): The metadata of the message. Defaults to None Returns: Message: The Message object of the added message @@ -907,9 +962,11 @@ async def get_messages( """Get all messages for a session Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return per page - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return per page. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False Returns: AsyncGetMessagePage: Page of Message objects @@ -935,6 +992,11 @@ async def get_messages_generator( """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + Args: + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False + Yields: Message: The Message object of the next Message @@ -965,6 +1027,7 @@ async def create_metamessage( message (Message): A message to associate the metamessage with metamessage_type (str): The type of the metamessage arbitrary identifier content (str): The content of the metamessage + metadata (dict, optional): The metadata of the metamessage. Defaults to None Returns: Metamessage: The Metamessage object of the added metamessage @@ -997,7 +1060,7 @@ async def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific metamessage Args: - message_id (uuid.UUID): The ID of the Message to retrieve + metamessage_id (uuid.UUID): The ID of the Metamessage to retrieve Returns: Message: The Message object @@ -1028,17 +1091,21 @@ async def get_metamessages( """Get all messages for a session Args: - metamessage_type (str, optional): The type of the metamessage - message (Message, optional): The message to associate the metamessage with - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return per page - reverse (bool): Whether to reverse the order of the results + metamessage_type (str, optional): The type of the metamessage. Defaults to + None + message (Message, optional): The message to associate the metamessage with. + Defaults to None + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return per page. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False Returns: list[dict]: List of Message objects """ - # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/metamessages" params = { "page": page, @@ -1073,8 +1140,12 @@ async def get_metamessages_generator( through all metamessages for a session in an app Args: - metamessage_type (str, optional): Optional Metamessage type to filter by - message (Message, optional): Optional Message to filter by + metamessage_type (str, optional): Optional Metamessage type to filter by. + Defaults to None + message (Message, optional): Optional Message to filter by. Defaults to None + filter (dict, optional): Optional key value filter. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False Yields: Metamessage: The next Metamessage object of the requested query @@ -1143,7 +1214,8 @@ async def update_metamessage( Args: metamessage (Metamessage): The metamessage to update - metadata (dict): The new metadata for the metamessage + metamessage_type (str, optional): The new type of the metamessage + metadata (dict, optional): The new metadata for the metamessage Returns: boolean: Whether the metamessage was successfully updated @@ -1161,14 +1233,23 @@ async def update_metamessage( return success async def close(self): - """Closes a session by marking it as inactive""" + """Closes a session by marking it as inactive. An inactive session can no longer + create new messages""" url = f"{self.base_url}" response = await self.user.honcho.client.delete(url) response.raise_for_status() self._is_active = False async def chat(self, query) -> str: - """Ask Honcho for information about the user session""" + """Chat with the dialectic Honcho Agent that asynchonously reasons about users + + Args: + query (str): The query to send to the agent + + Returns: + str: The response from the agent + + """ url = f"{self.base_url}/chat" params = {"query": query} response = await self.user.honcho.client.get(url, params=params) @@ -1202,13 +1283,14 @@ def base_url(self): def __str__(self): """String representation of Collection""" - return f"AsyncCollection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 + return f"AsyncCollection(id={self.id}, name={self.name}, created_at={self.created_at})" async def update(self, name: Optional[str] = None, metadata: Optional[dict] = None): - """Update the name of the collection + """Update the name of the collection. Atleast one argument is required Args: - name (str): The new name of the document + name (str, optional): The new name of the document + metadata (dict, optional): The new metadata of the document Returns: boolean: Whether the session was successfully updated @@ -1237,7 +1319,7 @@ async def create_document(self, content: str, metadata: Optional[dict] = None): Args: content (str): The content of the document - metadata (dict): The metadata of the document + metadata (dict, optional): The metadata of the document Returns: Document: The Document object of the added document @@ -1290,14 +1372,17 @@ async def get_documents( """Get all documents for a collection Args: + filter (dict, optional): The key value filter to apply page (int, optional): The page of results to return page_size (int, optional): The number of results to return per page + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to None Returns: AsyncGetDocumentPage: Page of Document objects """ - # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/documents" params = { "page": page, @@ -1318,6 +1403,11 @@ async def get_documents_generator( """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + Args: + filter (dict, optional): The key value filter to apply + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to None + Yields: Document: The Document object of the next Document @@ -1371,8 +1461,8 @@ async def update_document( Args: document (Document): The Document to update - metadata (dict): The metadata of the document - content (str): The content of the document + content (str, optional): The content of the document + metadata (dict, optional): The metadata of the document Returns: Document: The newly updated Document diff --git a/sdk/honcho/ext/langchain.py b/sdk/honcho/ext/langchain.py index 765b05b..844387e 100644 --- a/sdk/honcho/ext/langchain.py +++ b/sdk/honcho/ext/langchain.py @@ -4,8 +4,9 @@ import functools import importlib -from typing import List +from typing import List, Union +from honcho import AsyncSession, Session from honcho.schemas import Message @@ -16,7 +17,7 @@ def requires_langchain(func): def wrapper(*args, **kwargs): """Check if langchain is installed before running a function""" - if importlib.util.find_spec("langchain") is None: + if importlib.util.find_spec("langchain") is None: # type: ignore raise ImportError("Langchain must be installed to use this feature") # raise RuntimeError("langchain is not installed") return func(*args, **kwargs) @@ -25,10 +26,9 @@ def wrapper(*args, **kwargs): @requires_langchain -def langchain_message_converter(messages: List[Message]): +def _messages_to_langchain(messages: List[Message]): """Converts Honcho messages to Langchain messages""" - - from langchain_core.messages import AIMessage, HumanMessage + from langchain_core.messages import AIMessage, HumanMessage # type: ignore new_messages = [] for message in messages: @@ -37,3 +37,19 @@ def langchain_message_converter(messages: List[Message]): else: new_messages.append(AIMessage(content=message.content)) return new_messages + + +@requires_langchain +def _langchain_to_messages(messages, session: Union[Session, AsyncSession]): + """Converts Langchain messages to Langchain messages""" + from langchain_core.messages import HumanMessage # type: ignore + + for message in messages: + if isinstance(message, HumanMessage): + session.create_message( + is_user=True, content=message.content, metadata=message.metadata + ) + else: + session.create_message( + is_user=False, content=message.content, metadata=message.metadata + ) diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index a587a77..f8ab7d0 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -49,6 +49,7 @@ def __init__( Args: response (dict): Response from API with pagination information honcho (Honcho): Honcho Client + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -99,7 +100,10 @@ def __init__( response (dict): Response from API with pagination information user (User): Honcho User associated with the session reverse (bool): Whether to reverse the order of the results or not - location_id (str): ID of the location associated with the session + filter (dict, optional): Key value filter to apply to the results + location_id (str, optional): ID of the location associated with the session + is_active (bool): boolean filter for restricint results to only active + sessions """ super().__init__(response) self.user = user @@ -152,6 +156,7 @@ def __init__( Args: response (dict): Response from API with pagination information session (Session): Session the returned messages are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -202,9 +207,10 @@ def __init__( response (dict): Response from API with pagination information session (Session): Session the returned messages are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results - message_id (Optional[str]): ID of the message associated with the - metamessage_type (Optional[str]): Type of the metamessage + message_id (str, optional): ID of the message associated with the + metamessage_type (str, optional): Type of the metamessage """ super().__init__(response) self.session = session @@ -255,6 +261,7 @@ def __init__( response (dict): Response from API with pagination information collection (Collection): Collection the returned documents are associated with + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -300,6 +307,7 @@ def __init__( Args: response (dict): Response from API with pagination information user (User): Honcho Client + filter (dict, optional): Key value filter to apply to the results reverse (bool): Whether to reverse the order of the results or not """ super().__init__(response) @@ -338,7 +346,13 @@ class Honcho: """Honcho API Client Object""" def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): - """Constructor for Client""" + """Constructor for Client + + Args: + app_name (str): Name of the application + base_url (str): Base URL for the instance of the Honcho API defaults to + https://demo.honcho.dev + """ self.server_url: str = base_url # Base URL for the instance of the Honcho API self.client: httpx.Client = httpx.Client() self.app_name: str = app_name # Representing name of the client application @@ -346,15 +360,17 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): self.metadata: dict def initialize(self): - """Initialize the Honcho client from the server""" - res = self.client.get( - f"{self.server_url}/apps/get_or_create/{self.app_name}" - ) + """Run initialization tasks for the Honcho client""" + res = self.client.get(f"{self.server_url}/apps/get_or_create/{self.app_name}") res.raise_for_status() data = res.json() self.app_id: uuid.UUID = data["id"] self.metadata: dict = data["metadata"] + def init(self): + """Synonym for initialize""" + self.initialize() + @property def base_url(self): """Shortcut for common API prefix. made a property to prevent tampering""" @@ -390,9 +406,7 @@ def create_user(self, name: str, metadata: Optional[dict] = None): if metadata is None: metadata = {} url = f"{self.base_url}/users" - response = self.client.post( - url, json={"name": name, "metadata": metadata} - ) + response = self.client.post(url, json={"name": name, "metadata": metadata}) response.raise_for_status() data = response.json() return User( @@ -452,9 +466,11 @@ def get_users( """Get Paginated list of users Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return. Defaults to 50 + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to False Returns: GetUserPage: Paginated list of users @@ -483,7 +499,9 @@ def get_users_generator( all users in an app Args: - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to False Yields: User: The User object @@ -519,7 +537,7 @@ def get_users_generator( class User: - """Represents a single user in an app""" + """Represents a single user in an app and associated methods""" def __init__( self, @@ -528,7 +546,18 @@ def __init__( metadata: dict, created_at: datetime.datetime, ): - """Constructor for User""" + """Constructor for User + + Args: + honcho (Honcho): The honcho object + id (uuid.UUID): The id of the user + metadata (dict): The metadata for the user + created_at (datetime.datetime): The time the user was created + + Returns: + User: The created User object + + """ # self.base_url: str = honcho.base_url self.honcho: Honcho = honcho self.id: uuid.UUID = id @@ -542,7 +571,9 @@ def base_url(self): def __str__(self): """String representation of User""" - return f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" # noqa: E501 + return ( + f"User(id={self.id}, app_id={self.honcho.app_id}, metadata={self.metadata})" + ) def update(self, metadata: dict): """Updates a user's metadata @@ -600,11 +631,13 @@ def get_sessions( Args: location_id (str, optional): Optional Location ID representing the - location of a session - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return - reverse (bool): Whether to reverse the order of the results - is_active (bool): Whether to only return active sessions + location of a session. Defaults to None + filter (dict, optional): The key value filter to apply. Defaults to None + page (int, optional): The page of results to return. Defaults to 1 + page_size (int, optional): The number of results to return. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False + is_active (bool): Whether to only return active sessions. Defaults to False Returns: GetSessionPage: Page or results for get_sessions query @@ -644,8 +677,10 @@ def get_sessions_generator( Args: location_id (str, optional): Optional Location ID representing the location of a session - reverse (bool): Whether to reverse the order of the results - is_active (bool): Whether to only return active sessions + reverse (bool): Whether to reverse the order of the results. Defaults to + False + is_active (bool): Whether to only return active sessions. Defaults to False + filter (dict, optional): The key value filter to apply. Defaults to None Yields: Session: The Session object of the requested Session @@ -673,8 +708,8 @@ def create_session( Args: location_id (str, optional): Optional Location ID representing the - location of a session - metadata (dict, optional): Optional session metadata + location of a session. Defaults to "default" + metadata (dict, optional): Optional session metadata. Defaults to None Returns: Session: The Session object of the new Session @@ -705,6 +740,7 @@ def create_collection( Args: name (str): unique name for the collection for the user + metadata (dict, optional): Optional metadata. Defaults to None Returns: Collection: The Collection object of the new Collection @@ -757,15 +793,16 @@ def get_collections( """Return collections associated with a user paginated Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return reverse (bool): Whether to reverse the order of the results Returns: GetCollectionPage: Page or results for get_collections query """ - # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/collections?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/collections" params = { "page": page, @@ -787,7 +824,9 @@ def get_collections_generator( all sessions for a user in an app Args: - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False Yields: Collection: The Session object of the requested Session @@ -795,9 +834,7 @@ def get_collections_generator( """ page = 1 page_size = 50 - get_collection_response = self.get_collections( - filter, page, page_size, reverse - ) + get_collection_response = self.get_collections(filter, page, page_size, reverse) while True: for collection in get_collection_response.items: yield collection @@ -821,7 +858,20 @@ def __init__( is_active: bool, created_at: datetime.datetime, ): - """Constructor for Session""" + """Constructor for Session + + Args: + user (User): The user object + id (uuid.UUID): The id of the session + location_id (str): The id of the location associated with the session + metadata (dict): The metadata associated with the session + is_active (bool): Whether the session is active + created_at (datetime.datetime): When the session was created + + Returns: + Session: The Session object + + """ self.user: User = user self.id: uuid.UUID = id self.location_id: str = location_id @@ -836,7 +886,7 @@ def base_url(self): def __str__(self): """String representation of Session""" - return f"Session(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" # noqa: E501 + return f"Session(id={self.id}, location_id={self.location_id}, metadata={self.metadata}, is_active={self.is_active})" @property def is_active(self): @@ -851,6 +901,7 @@ def create_message( Args: is_user (bool): Whether the message is from the user content (str): The content of the message + metadata (dict, optional): The metadata of the message. Defaults to None Returns: Message: The Message object of the added message @@ -907,9 +958,11 @@ def get_messages( """Get all messages for a session Args: - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return per page - reverse (bool): Whether to reverse the order of the results + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return per page. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False Returns: GetMessagePage: Page of Message objects @@ -935,6 +988,11 @@ def get_messages_generator( """Shortcut Generator for get_messages. Generator to iterate through all messages for a session in an app + Args: + filter (dict, optional): The key value filter to apply. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False + Yields: Message: The Message object of the next Message @@ -965,6 +1023,7 @@ def create_metamessage( message (Message): A message to associate the metamessage with metamessage_type (str): The type of the metamessage arbitrary identifier content (str): The content of the metamessage + metadata (dict, optional): The metadata of the metamessage. Defaults to None Returns: Metamessage: The Metamessage object of the added metamessage @@ -997,7 +1056,7 @@ def get_metamessage(self, metamessage_id: uuid.UUID) -> Metamessage: """Get a specific metamessage Args: - message_id (uuid.UUID): The ID of the Message to retrieve + metamessage_id (uuid.UUID): The ID of the Metamessage to retrieve Returns: Message: The Message object @@ -1028,17 +1087,21 @@ def get_metamessages( """Get all messages for a session Args: - metamessage_type (str, optional): The type of the metamessage - message (Message, optional): The message to associate the metamessage with - page (int, optional): The page of results to return - page_size (int, optional): The number of results to return per page - reverse (bool): Whether to reverse the order of the results + metamessage_type (str, optional): The type of the metamessage. Defaults to + None + message (Message, optional): The message to associate the metamessage with. + Defaults to None + filter (dict, optional): The key value filter to apply. Defaults to None + page (int): The page of results to return. Defaults to 1 + page_size (int): The number of results to return per page. Defaults to 50 + reverse (bool): Whether to reverse the order of the results. Defaults to + False Returns: list[dict]: List of Message objects """ - # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/metamessages?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/metamessages" params = { "page": page, @@ -1073,8 +1136,12 @@ def get_metamessages_generator( through all metamessages for a session in an app Args: - metamessage_type (str, optional): Optional Metamessage type to filter by - message (Message, optional): Optional Message to filter by + metamessage_type (str, optional): Optional Metamessage type to filter by. + Defaults to None + message (Message, optional): Optional Message to filter by. Defaults to None + filter (dict, optional): Optional key value filter. Defaults to None + reverse (bool): Whether to reverse the order of the results. Defaults to + False Yields: Metamessage: The next Metamessage object of the requested query @@ -1143,7 +1210,8 @@ def update_metamessage( Args: metamessage (Metamessage): The metamessage to update - metadata (dict): The new metadata for the metamessage + metamessage_type (str, optional): The new type of the metamessage + metadata (dict, optional): The new metadata for the metamessage Returns: boolean: Whether the metamessage was successfully updated @@ -1161,14 +1229,23 @@ def update_metamessage( return success def close(self): - """Closes a session by marking it as inactive""" + """Closes a session by marking it as inactive. An inactive session can no longer + create new messages""" url = f"{self.base_url}" response = self.user.honcho.client.delete(url) response.raise_for_status() self._is_active = False def chat(self, query) -> str: - """Ask Honcho for information about the user session""" + """Chat with the dialectic Honcho Agent that asynchonously reasons about users + + Args: + query (str): The query to send to the agent + + Returns: + str: The response from the agent + + """ url = f"{self.base_url}/chat" params = {"query": query} response = self.user.honcho.client.get(url, params=params) @@ -1202,13 +1279,16 @@ def base_url(self): def __str__(self): """String representation of Collection""" - return f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" # noqa: E501 + return ( + f"Collection(id={self.id}, name={self.name}, created_at={self.created_at})" + ) def update(self, name: Optional[str] = None, metadata: Optional[dict] = None): - """Update the name of the collection + """Update the name of the collection. Atleast one argument is required Args: - name (str): The new name of the document + name (str, optional): The new name of the document + metadata (dict, optional): The new metadata of the document Returns: boolean: Whether the session was successfully updated @@ -1237,7 +1317,7 @@ def create_document(self, content: str, metadata: Optional[dict] = None): Args: content (str): The content of the document - metadata (dict): The metadata of the document + metadata (dict, optional): The metadata of the document Returns: Document: The Document object of the added document @@ -1290,14 +1370,17 @@ def get_documents( """Get all documents for a collection Args: + filter (dict, optional): The key value filter to apply page (int, optional): The page of results to return page_size (int, optional): The number of results to return per page + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to None Returns: GetDocumentPage: Page of Document objects """ - # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" # noqa: E501 + # url = f"{self.base_url}/documents?page={page}&size={page_size}&reverse={reverse}" url = f"{self.base_url}/documents" params = { "page": page, @@ -1318,6 +1401,11 @@ def get_documents_generator( """Shortcut Generator for get_documents. Generator to iterate through all documents for a collection in an app + Args: + filter (dict, optional): The key value filter to apply + reverse (bool): Whether to pull from the beginning or end of the list — + latest or earliest. Defaults to None + Yields: Document: The Document object of the next Document @@ -1371,8 +1459,8 @@ def update_document( Args: document (Document): The Document to update - metadata (dict): The metadata of the document - content (str): The content of the document + content (str, optional): The content of the document + metadata (dict, optional): The metadata of the document Returns: Document: The newly updated Document diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index ee8c3e9..601f4c0 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -24,20 +24,19 @@ furo = "^2024.1.29" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example select = [ - # pycodestyle - "E", - # Pyflakes - "F", - # pyupgrade - "UP", - # flake8-bugbear - "B", - # flake8-simplify - "SIM", - # isort - "I", + "E", # pycodestyle + "F", # Pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "SIM", # flake8-simplify + "S", # flake8-bandit + "I", # isort + "RUF", # ruff +] +ignore = [ + "UP007", # https://docs.astral.sh/ruff/rules/non-pep604-annotation/ + "E501", # line too long ] -ignore = ["UP007"] [build-system] From 98a39341054b4bc90a9a75b381997483a3066c7e Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:37:23 -0700 Subject: [PATCH 73/85] Sentry, OTEL, langchain both directions, fly.toml for deriver --- README.md | 19 ++++++++++-- api/fly.toml | 2 +- api/src/harvester.py | 9 ++++++ api/src/main.py | 13 ++++---- example/cli/main.py | 14 ++------- example/discord/honcho-dspy-personas/bot.py | 4 +-- example/discord/honcho-fact-memory/bot.py | 4 +-- example/discord/simple-roast-bot/main.py | 4 +-- sdk/honcho/ext/langchain.py | 33 +++++++++++++++++---- 9 files changed, 69 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 9fb1338..30a5034 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,30 @@ -# Honcho +# 🫡 Honcho ![Static Badge](https://img.shields.io/badge/Version-0.0.5-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) [![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fplastic_labs)](https://twitter.com/plastic_labs) -A User context management solution for building AI Agents and LLM powered -applications. +Honcho is a platform for making AI agents and LLM powered applications that are personalized +to their end users. Read about the motivation of this project [here](https://blog.plasticlabs.ai). Read the user documenation [here](https://docs.honcho.dev) +## Table of Contents + +- [Project Structure](#project-structure) +- [Usage](#usage) + - [API](#api) + - [Docker](#docker) + - [Manually](#manually) + - [Deploying on Fly.io](#deploy-on-fly) + - [Client SDK](#client-sdk) + - [Use Locally](#use-locally) +- [Contributing](#contributing) +- [License](#license) + ## Project Structure The Honcho repo is a monorepo containing the server/API that manages database diff --git a/api/fly.toml b/api/fly.toml index 2012518..8c9f343 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -41,4 +41,4 @@ kill_timeout = "5s" cpu_kind = "shared" cpus = 1 memory_mb = 512 - processes = ["api"] + processes = ["api", "deriver"] diff --git a/api/src/harvester.py b/api/src/harvester.py index 7be78bc..6785f29 100644 --- a/api/src/harvester.py +++ b/api/src/harvester.py @@ -3,6 +3,7 @@ import uuid from typing import List +import sentry_sdk from dotenv import load_dotenv from langchain_core.output_parsers import NumberedListOutputParser from langchain_core.prompts import ( @@ -20,6 +21,14 @@ load_dotenv() +SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", "False").lower() == "true" +if SENTRY_ENABLED: + sentry_sdk.init( + dsn=os.getenv("SENTRY_DSN"), + enable_tracing=True, + ) + + SUPABASE_ID = os.getenv("SUPABASE_ID") SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY") diff --git a/api/src/main.py b/api/src/main.py index 45f9cfd..d69d7f2 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -42,17 +42,18 @@ from slowapi.util import get_remote_address from starlette.exceptions import HTTPException as StarletteHTTPException -from .db import engine, scaffold_db from src.routers import ( apps, - users, - sessions, - messages, - metamessages, collections, documents, + messages, + metamessages, + sessions, + users, ) +from .db import engine, scaffold_db + # Otel Setup DEBUG_LOG_OTEL_TO_PROVIDER = ( @@ -171,7 +172,7 @@ def otel_logging_init(): otel_trace_init() otel_logging_init() - SQLAlchemyInstrumentor().instrument(engine=engine) + SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine) # Sentry Setup diff --git a/example/cli/main.py b/example/cli/main.py index 56d048d..2e59878 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -7,7 +7,7 @@ from langchain_community.chat_models.fake import FakeListChatModel from honcho import Honcho -from honcho.ext.langchain import langchain_message_converter +from honcho.ext.langchain import _messages_to_langchain app_name = str(uuid4()) @@ -28,16 +28,6 @@ session = user.create_session() -# def langchain_message_converter(messages: List): -# new_messages = [] -# for message in messages: -# if message.is_user: -# new_messages.append(HumanMessage(content=message.content)) -# else: -# new_messages.append(AIMessage(content=message.content)) -# return new_messages - - def chat(): while True: user_input = input("User: ") @@ -46,7 +36,7 @@ def chat(): break user_message = HumanMessage(content=user_input) history = list(session.get_messages_generator()) - langchain_history = langchain_message_converter(history) + langchain_history = _messages_to_langchain(history) prompt = ChatPromptTemplate.from_messages( [system, *langchain_history, user_message] ) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index ea91ec7..2e0ebfd 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -2,7 +2,7 @@ from uuid import uuid1 import discord from honcho import Honcho -from honcho.ext.langchain import langchain_message_converter +from honcho.ext.langchain import _messages_to_langchain from graph import chat from dspy import Example @@ -61,7 +61,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator())[:5] - chat_history = langchain_message_converter(history) + chat_history = _messages_to_langchain(history) inp = message.content user_message = session.create_message(is_user=True, content=inp) diff --git a/example/discord/honcho-fact-memory/bot.py b/example/discord/honcho-fact-memory/bot.py index 910afc6..aba5099 100644 --- a/example/discord/honcho-fact-memory/bot.py +++ b/example/discord/honcho-fact-memory/bot.py @@ -2,7 +2,7 @@ from uuid import uuid1 import discord from honcho import Honcho -from honcho.ext.langchain import langchain_message_converter +from honcho.ext.langchain import _messages_to_langchain from chain import LMChain @@ -59,7 +59,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator()) - chat_history = langchain_message_converter(history) + chat_history = _messages_to_langchain(history) inp = message.content user_message = session.create_message(is_user=True, content=inp) diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py index 2a7a4b2..f6e18a0 100644 --- a/example/discord/simple-roast-bot/main.py +++ b/example/discord/simple-roast-bot/main.py @@ -11,7 +11,7 @@ from langchain_core.messages import AIMessage, HumanMessage from honcho import Honcho -from honcho.ext.langchain import langchain_message_converter +from honcho.ext.langchain import _messages_to_langchain load_dotenv() @@ -66,7 +66,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator()) - chat_history = langchain_message_converter(history) + chat_history = _messages_to_langchain(history) inp = message.content session.create_message(is_user=True, content=inp) diff --git a/sdk/honcho/ext/langchain.py b/sdk/honcho/ext/langchain.py index 844387e..a27a9fb 100644 --- a/sdk/honcho/ext/langchain.py +++ b/sdk/honcho/ext/langchain.py @@ -27,7 +27,15 @@ def wrapper(*args, **kwargs): @requires_langchain def _messages_to_langchain(messages: List[Message]): - """Converts Honcho messages to Langchain messages""" + """Converts Honcho messages to Langchain messages + + Args: + messages (List[Message]): The list of messages to convert + + Returns: + List: The list of converted LangChain messages + + """ from langchain_core.messages import AIMessage, HumanMessage # type: ignore new_messages = [] @@ -40,16 +48,31 @@ def _messages_to_langchain(messages: List[Message]): @requires_langchain -def _langchain_to_messages(messages, session: Union[Session, AsyncSession]): - """Converts Langchain messages to Langchain messages""" +def _langchain_to_messages( + messages, session: Union[Session, AsyncSession] +) -> List[Message]: + """Converts Langchain messages to Honcho messages and adds to appropriate session + + Args: + messages: The LangChain messages to convert + session: The session to add the messages to + + Returns: + List[Message]: The list of converted messages + + """ from langchain_core.messages import HumanMessage # type: ignore + messages = [] for message in messages: if isinstance(message, HumanMessage): - session.create_message( + message = session.create_message( is_user=True, content=message.content, metadata=message.metadata ) + messages.append(message) else: - session.create_message( + message = session.create_message( is_user=False, content=message.content, metadata=message.metadata ) + messages.append(message) + return messages From e090e809381844dcd09f5117400c3e1f9396d6f6 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:41:46 -0700 Subject: [PATCH 74/85] Rename to deriver --- api/fly.toml | 2 +- api/src/{harvester.py => deriver.py} | 3 +-- api/src/routers/sessions.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) rename api/src/{harvester.py => deriver.py} (98%) diff --git a/api/fly.toml b/api/fly.toml index 8c9f343..cf47e59 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -14,7 +14,7 @@ kill_timeout = "5s" [processes] api = "python -m uvicorn src.main:app --host 0.0.0.0 --port 8000" - deriver = "python -m src.harvester" + deriver = "python -m src.deriver" [[services]] auto_stop_machines = false diff --git a/api/src/harvester.py b/api/src/deriver.py similarity index 98% rename from api/src/harvester.py rename to api/src/deriver.py index 6785f29..6fbb760 100644 --- a/api/src/harvester.py +++ b/api/src/deriver.py @@ -104,7 +104,6 @@ async def process_user_message( collection_id: uuid.UUID, message_id: uuid.UUID, ): - # TODO get messages for the session async with SessionLocal() as db: messages_stmt = await crud.get_messages( db=db, app_id=app_id, user_id=user_id, session_id=session_id, reverse=True @@ -113,7 +112,7 @@ async def process_user_message( response = await db.execute(messages_stmt) messages = response.scalars().all() messages = messages[::-1] - contents = [m.content for m in messages] + # contents = [m.content for m in messages] # print(contents) facts = await derive_facts(messages, content) diff --git a/api/src/routers/sessions.py b/api/src/routers/sessions.py index 50a65f8..af17e72 100644 --- a/api/src/routers/sessions.py +++ b/api/src/routers/sessions.py @@ -1,6 +1,7 @@ import json -from typing import Optional import uuid +from typing import Optional + from fastapi import APIRouter, HTTPException, Request from fastapi_pagination import Page from fastapi_pagination.ext.sqlalchemy import paginate @@ -8,7 +9,6 @@ from src import agent, crud, schemas from src.dependencies import db - router = APIRouter( prefix="/apps/{app_id}/users/{user_id}/sessions", tags=["sessions"], From 3026e4146014425e15c2388679b137b1465be630 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:11:56 -0700 Subject: [PATCH 75/85] Fix favicon and remove metadata from schema --- api/src/schemas.py | 30 +++++++++++++++--------------- sdk/docs/_static/favicon.svg | 28 ++++++++++++++++++++++++++++ sdk/docs/conf.py | 2 ++ sdk/tests/test_sync.py | 12 ++++-------- 4 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 sdk/docs/_static/favicon.svg diff --git a/api/src/schemas.py b/api/src/schemas.py index e99e181..1f222d9 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -1,7 +1,7 @@ import datetime import uuid -from pydantic import BaseModel, validator +from pydantic import BaseModel, Field, validator class AppBase(BaseModel): @@ -21,7 +21,7 @@ class AppUpdate(AppBase): class App(AppBase): id: uuid.UUID name: str - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime @@ -33,7 +33,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class UserBase(BaseModel): @@ -54,7 +54,7 @@ class User(UserBase): id: uuid.UUID app_id: uuid.UUID created_at: datetime.datetime - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict @validator("metadata", pre=True, allow_reuse=True) @@ -65,7 +65,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class MessageBase(BaseModel): @@ -87,7 +87,7 @@ class Message(MessageBase): is_user: bool session_id: uuid.UUID id: uuid.UUID - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime @@ -99,7 +99,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class SessionBase(BaseModel): @@ -121,7 +121,7 @@ class Session(SessionBase): is_active: bool user_id: uuid.UUID location_id: str - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime @@ -133,7 +133,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class MetamessageBase(BaseModel): @@ -158,7 +158,7 @@ class Metamessage(MetamessageBase): content: str id: uuid.UUID message_id: uuid.UUID - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime @@ -170,7 +170,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class CollectionBase(BaseModel): @@ -191,7 +191,7 @@ class Collection(CollectionBase): id: uuid.UUID name: str user_id: uuid.UUID - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime @@ -203,7 +203,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class DocumentBase(BaseModel): @@ -222,7 +222,7 @@ class DocumentUpdate(DocumentBase): class Document(DocumentBase): id: uuid.UUID content: str - h_metadata: dict + h_metadata: dict = Field(exclude=True) metadata: dict created_at: datetime.datetime collection_id: uuid.UUID @@ -235,7 +235,7 @@ def fetch_h_metadata(cls, value, values): class Config: from_attributes = True - schema_extra = {"exclude": ["h_metadata"]} + json_schema_extra = {"exclude": ["h_metadata"]} class AgentChat(BaseModel): diff --git a/sdk/docs/_static/favicon.svg b/sdk/docs/_static/favicon.svg new file mode 100644 index 0000000..6bcb833 --- /dev/null +++ b/sdk/docs/_static/favicon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/docs/conf.py b/sdk/docs/conf.py index 3798508..ecdee16 100644 --- a/sdk/docs/conf.py +++ b/sdk/docs/conf.py @@ -39,3 +39,5 @@ html_theme = "furo" html_static_path = ["_static"] + +html_favicon = "_static/favicon.svg" diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index 0077ff0..d6fa208 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -3,14 +3,14 @@ import pytest from honcho import ( + Document, GetDocumentPage, GetMessagePage, GetMetamessagePage, GetSessionPage, - Session, - Document, Message, Metamessage, + Session, ) from honcho import Honcho as Honcho @@ -251,9 +251,7 @@ def test_paginated_messages(): created_session.create_message(is_user=False, content="Hi") page_size = 7 - get_message_response = created_session.get_messages( - page=1, page_size=page_size - ) + get_message_response = created_session.get_messages(page=1, page_size=page_size) assert get_message_response is not None assert isinstance(get_message_response, GetMessagePage) @@ -448,9 +446,7 @@ def test_collection_query(): collection = user.create_collection(col_name) # Add documents - doc1 = collection.create_document( - content="The user loves puppies", metadata={} - ) + doc1 = collection.create_document(content="The user loves puppies", metadata={}) doc2 = collection.create_document(content="The user owns a dog", metadata={}) doc3 = collection.create_document(content="The user is a doctor", metadata={}) From 9a8641041f19e82a0d46ddcabbc106e29f67df49 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:02:21 -0700 Subject: [PATCH 76/85] 0.0.6 Notes --- README.md | 2 +- api/CHANGELOG.md | 16 ++++++++++++++++ api/pyproject.toml | 2 +- sdk/CHANGELOG.md | 9 +++++++++ sdk/pyproject.toml | 2 +- 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 30a5034..0c020cd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # 🫡 Honcho -![Static Badge](https://img.shields.io/badge/Version-0.0.5-blue) +![Static Badge](https://img.shields.io/badge/Version-0.0.6-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 2385bc2..275e9e3 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.6] — 2024-03-21 + +### Added + +* Full docker-compose for API and Database + +### Fixed + +* API Response schema removed unnecessary fields +* OTEL logging to properly work with async database engine + +### Changed + +* Refactored API server into multiple route files + + ## [0.0.5] — 2024-03-14 ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index de8f327..17c6c60 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho" -version = "0.0.5" +version = "0.0.6" description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 112e65b..397baf0 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.6] — 2024-03-21 + +### Added + +* Full docstring coverage +* Code coverage tests +* Add LangChain to Honcho message converter in both directions +* Synonym `init` function that acts the same as `initialize` + ## [0.0.5] — 2024-03-14 ### Added diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 601f4c0..a4eb01c 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.5" +version = "0.0.6" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0" From da2d3e96938114294924a5c5963de32259108232 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:39:20 -0700 Subject: [PATCH 77/85] Changelog edit --- api/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 275e9e3..aa75c81 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * API Response schema removed unnecessary fields * OTEL logging to properly work with async database engine +* `fly.toml` default settings for deriver set `auto_stop=false` ### Changed From dfcb45b5a51189085d963b1cce33ad536df9e8a1 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:18:09 -0700 Subject: [PATCH 78/85] Route bug fix --- api/src/routers/apps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/routers/apps.py b/api/src/routers/apps.py index 5d98adb..d90b7e9 100644 --- a/api/src/routers/apps.py +++ b/api/src/routers/apps.py @@ -1,10 +1,10 @@ import uuid -from fastapi import APIRouter, HTTPException, Request +from fastapi import APIRouter, HTTPException, Request +from sqlalchemy.ext.asyncio import AsyncSession from src import crud, schemas from src.dependencies import db -from sqlalchemy.ext.asyncio import AsyncSession router = APIRouter( prefix="/apps", @@ -46,7 +46,7 @@ async def get_app_by_name(request: Request, name: str, db=db): return app -@router.post("/apps", response_model=schemas.App) +@router.post("/", response_model=schemas.App) async def create_app(request: Request, app: schemas.AppCreate, db=db): """Create an App From 5fdfdbcb6a1ddd24aba93eec63244535f74aa685 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:21:14 -0700 Subject: [PATCH 79/85] Route bug fix again --- api/src/routers/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routers/apps.py b/api/src/routers/apps.py index d90b7e9..648c28b 100644 --- a/api/src/routers/apps.py +++ b/api/src/routers/apps.py @@ -46,7 +46,7 @@ async def get_app_by_name(request: Request, name: str, db=db): return app -@router.post("/", response_model=schemas.App) +@router.post("", response_model=schemas.App) async def create_app(request: Request, app: schemas.AppCreate, db=db): """Create an App From f74125e75de9d5c018ee31a0a6b56c58f5e38232 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:22:34 -0700 Subject: [PATCH 80/85] Langchain Utilities Refactor (#48) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [0.0.6] - 3-21-2024 Bug Fixes (#47) * 🧪 asyncify tests * ✨ asyncify client * Basic Test for Page based pagination * add sync buildstep and client * add vscode DX * Added Testing for generators and updated examples * feat: example updates * readme exists now * Stylistic changes and generic message * Metamessages with other refactoring - untested * Work with unit tests * Fix Examples * MEME-78 Update Changelogs * Docstrings to client * 🧪 autogenerate sync tests * test one * add db type * sync client * add status badge * add coverage * add file * give perms * properly output coverage * split test and coverage * rename action * 🧪 autogenerate sync tests (#16) * Vector Support (#18) * Scaffold for PGVector support * Buggy crud with logic skeleton on api * Crud logic and schema definition for pgvector * Populate all routes and refactor to name Collection * vince's progress * AsyncCollection progress * Local PGVector Docker Container * client methods for sdk except document delete and update * Vector Support Passing All Test Cases * Docs Updates --------- Co-authored-by: vintro * Add reverse parameters for paginated routes * Address dependabot * Formatting * initial commit on honcho dspy personas * working, hit token limit and can't test dspy optimization * initial version working, need to test optimization * optimizers working, but appending any example * ready for user object (tbomk) * Revert "add test actions and coverage" * Refactor to add User and App Tables * User Object passing test cases * Update examples * DSPy Todo and documentation updates * Add is_active filtering * Add is_active filtering to the generator * Fix update user metadata * working, but weird compiler error * fixed str error in optimizer * ship * sentry * Open Telemetry * optional logging with environment variables * add actions again? (#29) * add postgres * add openai key * readd coverage * desyncify and add detailed coverage * ⚙️ chore: update start script in VS Code to include poetry install --no-root before running uvicorn (#33) * Refactored code but need to tweak asyncpg * Working Async API using Psycopg3 * Update Workflow Connection URI * Update Workflow Connection URI in coverage test as well * Skeleton for Dialectic API * Fixes DEV-217 URL Encoding * Add Built-in Langchain Utility function * Sphinx Docs MVP * Metadata filtering for all fixes dev-261 * Basic Dialectic Endpoint fixes dev-253 * Working Fact Deriver * 0.0.5 Docs and README updates * Cloudflare Sphinx * update example to use right function (#36) * 🚀 feat: add support for running API using docker-compose with configurable environment variables and update docker-compose.yml for API and database services. (#34) Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> * add interrogate * routerify everything * full docstring coverage * remove unused imports and fix env issue * Update docker-compose connection uri and remove auto-stop to deriver process * Docstrings and langchain message converter in reverse * Sentry, OTEL, langchain both directions, fly.toml for deriver * Rename to deriver * Fix favicon and remove metadata from schema * 0.0.6 Notes * Changelog edit * Route bug fix * Route bug fix again --------- Co-authored-by: hyusap Co-authored-by: vintro Co-authored-by: vintro <77507980+vintrocode@users.noreply.github.com> * Port Docs * Update Examples with new langchain utilites * Rename decorator * Synchronize * Fix API SessionLocal --------- Co-authored-by: hyusap Co-authored-by: vintro Co-authored-by: vintro <77507980+vintrocode@users.noreply.github.com> --- .gitignore | 2 + api/src/db.py | 4 +- api/src/dependencies.py | 3 +- docs/README.md | 43 + docs/_snippets/overview-shields.mdx | 63 + docs/about/contributing.mdx | 18 + docs/about/license.mdx | 671 ++ .../endpoint/apps/create-app.mdx | 3 + .../endpoint/apps/get-app-by-name.mdx | 3 + docs/api-reference/endpoint/apps/get-app.mdx | 3 + .../endpoint/apps/get-or-create-app.mdx | 3 + .../endpoint/apps/update-app.mdx | 3 + .../collections/create-collection.mdx | 3 + .../collections/delete-collection.mdx | 3 + .../collections/get-collection-by-name.mdx | 3 + .../endpoint/collections/get-collections.mdx | 3 + .../collections/update-collection.mdx | 3 + .../endpoint/documents/create-document.mdx | 3 + .../endpoint/documents/delete-document.mdx | 3 + .../endpoint/documents/get-document.mdx | 3 + .../endpoint/documents/get-documents.mdx | 3 + .../endpoint/documents/query-documents.mdx | 3 + .../endpoint/documents/update-document.mdx | 3 + .../messages/create-message-for-session.mdx | 3 + .../endpoint/messages/create-metamessage.mdx | 3 + .../endpoint/messages/get-message.mdx | 3 + .../endpoint/messages/get-messages.mdx | 3 + .../endpoint/messages/get-metamessage.mdx | 3 + .../endpoint/messages/get-metamessages.mdx | 3 + .../endpoint/messages/update-message.mdx | 3 + .../endpoint/messages/update-metamessage.mdx | 3 + .../endpoint/sessions/create-session.mdx | 3 + .../endpoint/sessions/delete-session.mdx | 3 + .../endpoint/sessions/get-chat.mdx | 3 + .../endpoint/sessions/get-session.mdx | 3 + .../endpoint/sessions/get-sessions.mdx | 3 + .../endpoint/sessions/update-session.mdx | 3 + .../endpoint/users/create-user.mdx | 3 + .../endpoint/users/get-or-create-user.mdx | 3 + .../endpoint/users/get-user-by-name.mdx | 3 + .../endpoint/users/get-users.mdx | 3 + .../endpoint/users/update-user.mdx | 3 + docs/api-reference/introduction.mdx | 17 + docs/api-reference/openapi.json | 3617 ++++++++ docs/favicon.svg | 28 + docs/getting-started/architecture.mdx | 83 + docs/getting-started/deploying.mdx | 19 + docs/getting-started/development.mdx | 98 + docs/getting-started/introduction.mdx | 37 + docs/getting-started/quickstart.mdx | 72 + docs/getting-started/self-hosting.mdx | 64 + docs/guides/dialectic-endpoint.mdx | 54 + docs/guides/discord.mdx | 113 + docs/guides/langchain.mdx | 75 + docs/guides/overview.mdx | 6 + docs/guides/simple-memory.mdx | 184 + docs/images/checks-passed.png | Bin 0 -> 160724 bytes docs/images/db_schema.png | Bin 0 -> 67286 bytes docs/images/hero-dark.svg | 161 + docs/images/hero-light.svg | 155 + docs/logo/dark.svg | 55 + docs/logo/honcho-dark.svg | 37 + docs/logo/honcho-light.svg | 30 + docs/logo/light.svg | 51 + docs/mint.json | 177 + docs/package-lock.json | 7910 +++++++++++++++++ docs/package.json | 16 + example/cli/main.py | 4 +- example/discord/honcho-dspy-personas/bot.py | 4 +- example/discord/honcho-fact-memory/bot.py | 4 +- example/discord/simple-roast-bot/main.py | 5 +- sdk/honcho/ext/langchain.py | 10 +- 72 files changed, 13978 insertions(+), 17 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/_snippets/overview-shields.mdx create mode 100644 docs/about/contributing.mdx create mode 100644 docs/about/license.mdx create mode 100644 docs/api-reference/endpoint/apps/create-app.mdx create mode 100644 docs/api-reference/endpoint/apps/get-app-by-name.mdx create mode 100644 docs/api-reference/endpoint/apps/get-app.mdx create mode 100644 docs/api-reference/endpoint/apps/get-or-create-app.mdx create mode 100644 docs/api-reference/endpoint/apps/update-app.mdx create mode 100644 docs/api-reference/endpoint/collections/create-collection.mdx create mode 100644 docs/api-reference/endpoint/collections/delete-collection.mdx create mode 100644 docs/api-reference/endpoint/collections/get-collection-by-name.mdx create mode 100644 docs/api-reference/endpoint/collections/get-collections.mdx create mode 100644 docs/api-reference/endpoint/collections/update-collection.mdx create mode 100644 docs/api-reference/endpoint/documents/create-document.mdx create mode 100644 docs/api-reference/endpoint/documents/delete-document.mdx create mode 100644 docs/api-reference/endpoint/documents/get-document.mdx create mode 100644 docs/api-reference/endpoint/documents/get-documents.mdx create mode 100644 docs/api-reference/endpoint/documents/query-documents.mdx create mode 100644 docs/api-reference/endpoint/documents/update-document.mdx create mode 100644 docs/api-reference/endpoint/messages/create-message-for-session.mdx create mode 100644 docs/api-reference/endpoint/messages/create-metamessage.mdx create mode 100644 docs/api-reference/endpoint/messages/get-message.mdx create mode 100644 docs/api-reference/endpoint/messages/get-messages.mdx create mode 100644 docs/api-reference/endpoint/messages/get-metamessage.mdx create mode 100644 docs/api-reference/endpoint/messages/get-metamessages.mdx create mode 100644 docs/api-reference/endpoint/messages/update-message.mdx create mode 100644 docs/api-reference/endpoint/messages/update-metamessage.mdx create mode 100644 docs/api-reference/endpoint/sessions/create-session.mdx create mode 100644 docs/api-reference/endpoint/sessions/delete-session.mdx create mode 100644 docs/api-reference/endpoint/sessions/get-chat.mdx create mode 100644 docs/api-reference/endpoint/sessions/get-session.mdx create mode 100644 docs/api-reference/endpoint/sessions/get-sessions.mdx create mode 100644 docs/api-reference/endpoint/sessions/update-session.mdx create mode 100644 docs/api-reference/endpoint/users/create-user.mdx create mode 100644 docs/api-reference/endpoint/users/get-or-create-user.mdx create mode 100644 docs/api-reference/endpoint/users/get-user-by-name.mdx create mode 100644 docs/api-reference/endpoint/users/get-users.mdx create mode 100644 docs/api-reference/endpoint/users/update-user.mdx create mode 100644 docs/api-reference/introduction.mdx create mode 100644 docs/api-reference/openapi.json create mode 100644 docs/favicon.svg create mode 100644 docs/getting-started/architecture.mdx create mode 100644 docs/getting-started/deploying.mdx create mode 100644 docs/getting-started/development.mdx create mode 100644 docs/getting-started/introduction.mdx create mode 100644 docs/getting-started/quickstart.mdx create mode 100644 docs/getting-started/self-hosting.mdx create mode 100644 docs/guides/dialectic-endpoint.mdx create mode 100644 docs/guides/discord.mdx create mode 100644 docs/guides/langchain.mdx create mode 100644 docs/guides/overview.mdx create mode 100644 docs/guides/simple-memory.mdx create mode 100644 docs/images/checks-passed.png create mode 100644 docs/images/db_schema.png create mode 100644 docs/images/hero-dark.svg create mode 100644 docs/images/hero-light.svg create mode 100644 docs/logo/dark.svg create mode 100644 docs/logo/honcho-dark.svg create mode 100644 docs/logo/honcho-light.svg create mode 100644 docs/logo/light.svg create mode 100644 docs/mint.json create mode 100644 docs/package-lock.json create mode 100644 docs/package.json diff --git a/.gitignore b/.gitignore index 558b175..d98ca3c 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,5 @@ cython_debug/ .DS_Store supabase/ + +docs/node_modules diff --git a/api/src/db.py b/api/src/db.py index 5c431d8..de43eda 100644 --- a/api/src/db.py +++ b/api/src/db.py @@ -23,8 +23,8 @@ def scaffold_db(): - """Use a Sync Engine for scaffolding the database. DDL operations are unavailable - with Async Engines + """use a sync engine for scaffolding the database. ddl operations are unavailable + with async engines """ engine = create_engine(os.environ["CONNECTION_URI"], echo=True) Base.metadata.create_all(bind=engine) diff --git a/api/src/dependencies.py b/api/src/dependencies.py index d328700..1379f1e 100644 --- a/api/src/dependencies.py +++ b/api/src/dependencies.py @@ -1,7 +1,8 @@ from fastapi import Depends -from .db import SessionLocal from sqlalchemy.ext.asyncio import AsyncSession +from .db import SessionLocal + async def get_db(): """FastAPI Dependency Generator for Database""" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c011650 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,43 @@ +# honcho-docs + +## Setting Up `honcho-docs` Locally + +1. Clone the repository: +``` +git clone git@github.com:plastic-labs/honcho-docs.git +``` + +2. Navigate into the `honcho-docs` folder: +``` +cd honcho-docs/ +``` +The docs folder contains the markdown files that make up the documentation. The majority of the files are in the pages directory. Some notable files in this folder include: + +`index.mdx`: The main documentation file. +`_app.js`: This file is used to customize the default Next.js application shell. +`theme.config.jsx`: This file is for configuring the Nextra theme for the documentation. + +3. Verify that you have Node.js and npm installed in your system. You can check by running: +``` +node --version +npm --version +``` + +4. If not installed, download Node.js and npm from the respective official websites. + +5. Once you have Node.js and npm running, proceed to install `pnpm` - another package manager that helps to manage project dependencies: +``` +npm install -g pnpm +``` + +6. Install the project dependencies using yarn: +``` +pnpm i +``` + +7. After the successful installation of the project dependencies, start the local server: +``` +pnpm dev +``` + +Now, you should be able to view the docs on your local environment by visiting `http://localhost:3000`. You can explore the different markdown files and make changes as you see fit. \ No newline at end of file diff --git a/docs/_snippets/overview-shields.mdx b/docs/_snippets/overview-shields.mdx new file mode 100644 index 0000000..3344994 --- /dev/null +++ b/docs/_snippets/overview-shields.mdx @@ -0,0 +1,63 @@ + \ No newline at end of file diff --git a/docs/about/contributing.mdx b/docs/about/contributing.mdx new file mode 100644 index 0000000..3264e2d --- /dev/null +++ b/docs/about/contributing.mdx @@ -0,0 +1,18 @@ +--- +title: 'Contributing' +description: 'Guidelines for contributing to the Honcho Project' +icon: 'handshake-angle' +--- + +This project is completely open source and welcomes any and all open source +contributions. The workflow for contributing is to make a fork of the +repository. You can claim an issue in the issues tab or start a new thread to +indicate a feature or bug fix you are working on. + +Once you have finished your contribution make a PR pointed at the `staging` +branch, and it will be reviewed by a project manager. Feel free to join us in +our [discord](http://discord.gg/plasticlabs) to discuss your changes or get +help. + +Once your changes are accepted and merged into staging they will undergo a +period of live testing before entering the upstream into `main` diff --git a/docs/about/license.mdx b/docs/about/license.mdx new file mode 100644 index 0000000..a53e2d7 --- /dev/null +++ b/docs/about/license.mdx @@ -0,0 +1,671 @@ +--- +title: 'License' +icon: 'scroll' +--- + +Honcho is licensed under the AGPL-3.0 License. This is copied below for convenience and also present in the +[GitHub Repository](https://github.com/plastic-labs/honcho) + +``` + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. +``` \ No newline at end of file diff --git a/docs/api-reference/endpoint/apps/create-app.mdx b/docs/api-reference/endpoint/apps/create-app.mdx new file mode 100644 index 0000000..0a2ec53 --- /dev/null +++ b/docs/api-reference/endpoint/apps/create-app.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/apps/get-app-by-name.mdx b/docs/api-reference/endpoint/apps/get-app-by-name.mdx new file mode 100644 index 0000000..e738d85 --- /dev/null +++ b/docs/api-reference/endpoint/apps/get-app-by-name.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/name/{name} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/apps/get-app.mdx b/docs/api-reference/endpoint/apps/get-app.mdx new file mode 100644 index 0000000..955aa86 --- /dev/null +++ b/docs/api-reference/endpoint/apps/get-app.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/apps/get-or-create-app.mdx b/docs/api-reference/endpoint/apps/get-or-create-app.mdx new file mode 100644 index 0000000..c871fcc --- /dev/null +++ b/docs/api-reference/endpoint/apps/get-or-create-app.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/get_or_create/{name} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/apps/update-app.mdx b/docs/api-reference/endpoint/apps/update-app.mdx new file mode 100644 index 0000000..e12a95f --- /dev/null +++ b/docs/api-reference/endpoint/apps/update-app.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/collections/create-collection.mdx b/docs/api-reference/endpoint/collections/create-collection.mdx new file mode 100644 index 0000000..351b11b --- /dev/null +++ b/docs/api-reference/endpoint/collections/create-collection.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users/{user_id}/collections +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/collections/delete-collection.mdx b/docs/api-reference/endpoint/collections/delete-collection.mdx new file mode 100644 index 0000000..ebbc68f --- /dev/null +++ b/docs/api-reference/endpoint/collections/delete-collection.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /apps/{app_id}/users/{user_id}/collections/{collection_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/collections/get-collection-by-name.mdx b/docs/api-reference/endpoint/collections/get-collection-by-name.mdx new file mode 100644 index 0000000..22c3196 --- /dev/null +++ b/docs/api-reference/endpoint/collections/get-collection-by-name.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/collections/{name} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/collections/get-collections.mdx b/docs/api-reference/endpoint/collections/get-collections.mdx new file mode 100644 index 0000000..2f2e3b4 --- /dev/null +++ b/docs/api-reference/endpoint/collections/get-collections.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/collections +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/collections/update-collection.mdx b/docs/api-reference/endpoint/collections/update-collection.mdx new file mode 100644 index 0000000..ad16c14 --- /dev/null +++ b/docs/api-reference/endpoint/collections/update-collection.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id}/collections/{collection_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/create-document.mdx b/docs/api-reference/endpoint/documents/create-document.mdx new file mode 100644 index 0000000..829c416 --- /dev/null +++ b/docs/api-reference/endpoint/documents/create-document.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users/{user_id}/collections/{collection_id}/documents +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/delete-document.mdx b/docs/api-reference/endpoint/documents/delete-document.mdx new file mode 100644 index 0000000..c6c4f23 --- /dev/null +++ b/docs/api-reference/endpoint/documents/delete-document.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/get-document.mdx b/docs/api-reference/endpoint/documents/get-document.mdx new file mode 100644 index 0000000..5f60bdd --- /dev/null +++ b/docs/api-reference/endpoint/documents/get-document.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/get-documents.mdx b/docs/api-reference/endpoint/documents/get-documents.mdx new file mode 100644 index 0000000..55331c2 --- /dev/null +++ b/docs/api-reference/endpoint/documents/get-documents.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/collections/{collection_id}/documents +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/query-documents.mdx b/docs/api-reference/endpoint/documents/query-documents.mdx new file mode 100644 index 0000000..a9f35ba --- /dev/null +++ b/docs/api-reference/endpoint/documents/query-documents.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/collections/{collection_id}/query +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/documents/update-document.mdx b/docs/api-reference/endpoint/documents/update-document.mdx new file mode 100644 index 0000000..5b0cb13 --- /dev/null +++ b/docs/api-reference/endpoint/documents/update-document.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/create-message-for-session.mdx b/docs/api-reference/endpoint/messages/create-message-for-session.mdx new file mode 100644 index 0000000..6747759 --- /dev/null +++ b/docs/api-reference/endpoint/messages/create-message-for-session.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users/{user_id}/sessions/{session_id}/messages +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/create-metamessage.mdx b/docs/api-reference/endpoint/messages/create-metamessage.mdx new file mode 100644 index 0000000..47b4ff3 --- /dev/null +++ b/docs/api-reference/endpoint/messages/create-metamessage.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/get-message.mdx b/docs/api-reference/endpoint/messages/get-message.mdx new file mode 100644 index 0000000..e614aeb --- /dev/null +++ b/docs/api-reference/endpoint/messages/get-message.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/{message_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/get-messages.mdx b/docs/api-reference/endpoint/messages/get-messages.mdx new file mode 100644 index 0000000..d3602fa --- /dev/null +++ b/docs/api-reference/endpoint/messages/get-messages.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id}/messages +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/get-metamessage.mdx b/docs/api-reference/endpoint/messages/get-metamessage.mdx new file mode 100644 index 0000000..ec0a871 --- /dev/null +++ b/docs/api-reference/endpoint/messages/get-metamessage.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages/{metamessage_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/get-metamessages.mdx b/docs/api-reference/endpoint/messages/get-metamessages.mdx new file mode 100644 index 0000000..73ba6a6 --- /dev/null +++ b/docs/api-reference/endpoint/messages/get-metamessages.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/update-message.mdx b/docs/api-reference/endpoint/messages/update-message.mdx new file mode 100644 index 0000000..0697517 --- /dev/null +++ b/docs/api-reference/endpoint/messages/update-message.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/{message_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/messages/update-metamessage.mdx b/docs/api-reference/endpoint/messages/update-metamessage.mdx new file mode 100644 index 0000000..507100d --- /dev/null +++ b/docs/api-reference/endpoint/messages/update-metamessage.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages/{metamessage_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/create-session.mdx b/docs/api-reference/endpoint/sessions/create-session.mdx new file mode 100644 index 0000000..f785e35 --- /dev/null +++ b/docs/api-reference/endpoint/sessions/create-session.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users/{user_id}/sessions +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/delete-session.mdx b/docs/api-reference/endpoint/sessions/delete-session.mdx new file mode 100644 index 0000000..3bacb40 --- /dev/null +++ b/docs/api-reference/endpoint/sessions/delete-session.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /apps/{app_id}/users/{user_id}/sessions/{session_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/get-chat.mdx b/docs/api-reference/endpoint/sessions/get-chat.mdx new file mode 100644 index 0000000..566b50e --- /dev/null +++ b/docs/api-reference/endpoint/sessions/get-chat.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id}/chat +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/get-session.mdx b/docs/api-reference/endpoint/sessions/get-session.mdx new file mode 100644 index 0000000..762384a --- /dev/null +++ b/docs/api-reference/endpoint/sessions/get-session.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions/{session_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/get-sessions.mdx b/docs/api-reference/endpoint/sessions/get-sessions.mdx new file mode 100644 index 0000000..cb6e61e --- /dev/null +++ b/docs/api-reference/endpoint/sessions/get-sessions.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{user_id}/sessions +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/sessions/update-session.mdx b/docs/api-reference/endpoint/sessions/update-session.mdx new file mode 100644 index 0000000..25c75c1 --- /dev/null +++ b/docs/api-reference/endpoint/sessions/update-session.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id}/sessions/{session_id} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/users/create-user.mdx b/docs/api-reference/endpoint/users/create-user.mdx new file mode 100644 index 0000000..24b5314 --- /dev/null +++ b/docs/api-reference/endpoint/users/create-user.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /apps/{app_id}/users +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/users/get-or-create-user.mdx b/docs/api-reference/endpoint/users/get-or-create-user.mdx new file mode 100644 index 0000000..6741344 --- /dev/null +++ b/docs/api-reference/endpoint/users/get-or-create-user.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/get_or_create/{name} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/users/get-user-by-name.mdx b/docs/api-reference/endpoint/users/get-user-by-name.mdx new file mode 100644 index 0000000..4fcf37b --- /dev/null +++ b/docs/api-reference/endpoint/users/get-user-by-name.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users/{name} +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/users/get-users.mdx b/docs/api-reference/endpoint/users/get-users.mdx new file mode 100644 index 0000000..c276638 --- /dev/null +++ b/docs/api-reference/endpoint/users/get-users.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /apps/{app_id}/users +--- \ No newline at end of file diff --git a/docs/api-reference/endpoint/users/update-user.mdx b/docs/api-reference/endpoint/users/update-user.mdx new file mode 100644 index 0000000..7c8790e --- /dev/null +++ b/docs/api-reference/endpoint/users/update-user.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /apps/{app_id}/users/{user_id} +--- \ No newline at end of file diff --git a/docs/api-reference/introduction.mdx b/docs/api-reference/introduction.mdx new file mode 100644 index 0000000..e9f1648 --- /dev/null +++ b/docs/api-reference/introduction.mdx @@ -0,0 +1,17 @@ +--- +title: 'Introduction' +--- + + + If you're using the Python SDK you shouldn't need to interface directly with the + API. The SDK reference may be more helpful + + +This section of the documentation goes over all of the different API endpoints available in the Honcho +Server. They largely map to CRUD operations for each of the core primitives. For information about the core +primitives consult [Architecture](/getting-started/Architecture) + + + This part of the documentation is autogenerated for the most up to date and accurate API spec view the + [Redoc](https://demo.honcho.dev/redoc) or [Swagger](https://demo.honcho.dev/docs) directly. + diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json new file mode 100644 index 0000000..891d306 --- /dev/null +++ b/docs/api-reference/openapi.json @@ -0,0 +1,3617 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/apps/{app_id}": { + "get": { + "summary": "Get App", + "description": "Get an App by ID\n\nArgs:\n app_id (uuid.UUID): The ID of the app\n\nReturns:\n schemas.App: App object", + "operationId": "get_app_apps__app_id__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "put": { + "summary": "Update App", + "description": "Update an App\n\nArgs:\n app_id (uuid.UUID): The ID of the app to update\n app (schemas.AppUpdate): The App object containing any new metadata\n\nReturns:\n schemas.App: The App object of the updated App", + "operationId": "update_app_apps__app_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/name/{name}": { + "get": { + "summary": "Get App By Name", + "description": "Get an App by Name\n\nArgs:\n app_name (str): The name of the app\n\nReturns:\n schemas.App: App object", + "operationId": "get_app_by_name_apps_name__name__get", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps": { + "post": { + "summary": "Create App", + "description": "Create an App\n\nArgs:\n app (schemas.AppCreate): The App object containing any metadata\n\nReturns:\n schemas.App: Created App object", + "operationId": "create_app_apps_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppCreate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/get_or_create/{name}": { + "get": { + "summary": "Get Or Create App", + "description": "Get or Create an App\n\nArgs:\n app_name (str): The name of the app\n\nReturns:\n schemas.App: App object", + "operationId": "get_or_create_app_apps_get_or_create__name__get", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/App" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users": { + "post": { + "summary": "Create User", + "description": "Create a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user (schemas.UserCreate): The User object containing any metadata\n\nReturns:\n schemas.User: Created User object", + "operationId": "create_user_apps__app_id__users_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "summary": "Get Users", + "description": "Get All Users for an App\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client\n application using honcho\n\nReturns:\n list[schemas.User]: List of User objects", + "operationId": "get_users_apps__app_id__users_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_User_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{name}": { + "get": { + "summary": "Get User By Name", + "description": "Get a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n\nReturns:\n schemas.User: User object", + "operationId": "get_user_by_name_apps__app_id__users__name__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/get_or_create/{name}": { + "get": { + "summary": "Get Or Create User", + "description": "Get or Create a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n\nReturns:\n schemas.User: User object", + "operationId": "get_or_create_user_apps__app_id__users_get_or_create__name__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}": { + "put": { + "summary": "Update User", + "description": "Update a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n user (schemas.UserCreate): The User object containing any metadata\n\nReturns:\n schemas.User: Updated User object", + "operationId": "update_user_apps__app_id__users__user_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions": { + "get": { + "summary": "Get Sessions", + "description": "Get All Sessions for a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n location_id (str, optional): Optional Location ID representing the location of a\n session\n\nReturns:\n list[schemas.Session]: List of Session objects", + "operationId": "get_sessions_apps__app_id__users__user_id__sessions_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "location_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Location Id" + } + }, + { + "name": "is_active", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Is Active" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_Session_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "summary": "Create Session", + "description": "Create a Session for a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client\n application using honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n session (schemas.SessionCreate): The Session object containing any\n metadata and a location ID\n\nReturns:\n schemas.Session: The Session object of the new Session", + "operationId": "create_session_apps__app_id__users__user_id__sessions_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions/{session_id}": { + "put": { + "summary": "Update Session", + "description": "Update the metadata of a Session\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n session_id (uuid.UUID): The ID of the Session to update\n session (schemas.SessionUpdate): The Session object containing any new metadata\n\nReturns:\n schemas.Session: The Session object of the updated Session", + "operationId": "update_session_apps__app_id__users__user_id__sessions__session_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "summary": "Delete Session", + "description": "Delete a session by marking it as inactive\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n session_id (uuid.UUID): The ID of the Session to delete\n\nReturns:\n dict: A message indicating that the session was deleted\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "delete_session_apps__app_id__users__user_id__sessions__session_id__delete", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "summary": "Get Session", + "description": "Get a specific session for a user by ID\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n session_id (uuid.UUID): The ID of the Session to retrieve\n\nReturns:\n schemas.Session: The Session object of the requested Session\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "get_session_apps__app_id__users__user_id__sessions__session_id__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages": { + "post": { + "summary": "Create Message For Session", + "description": "Adds a message to a session\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n session_id (int): The ID of the Session to add the message to\n message (schemas.MessageCreate): The Message object to add containing the\n message content and type\n\nReturns:\n schemas.Message: The Message object of the added message\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "create_message_for_session_apps__app_id__users__user_id__sessions__session_id__messages_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "summary": "Get Messages", + "description": "Get all messages for a session\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n session_id (int): The ID of the Session to retrieve\n reverse (bool): Whether to reverse the order of the messages\n\nReturns:\n list[schemas.Message]: List of Message objects\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "get_messages_apps__app_id__users__user_id__sessions__session_id__messages_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_Message_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}sessions/{session_id}/messages/{message_id}": { + "get": { + "summary": "Get Message", + "operationId": "get_message_apps__app_id__users__user_id_sessions__session_id__messages__message_id__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Message Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "put": { + "summary": "Update Message", + "description": "Update's the metadata of a message", + "operationId": "update_message_apps__app_id__users__user_id_sessions__session_id__messages__message_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Message Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages": { + "post": { + "summary": "Create Metamessage", + "description": "Adds a message to a session\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n session_id (int): The ID of the Session to add the message to\n message (schemas.MessageCreate): The Message object to add containing the\n message content and type\n\nReturns:\n schemas.Message: The Message object of the added message\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "create_metamessage_apps__app_id__users__user_id__sessions__session_id__metamessages_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MetamessageCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Metamessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "summary": "Get Metamessages", + "description": "Get all messages for a session\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n session_id (int): The ID of the Session to retrieve\n reverse (bool): Whether to reverse the order of the metamessages\n\nReturns:\n list[schemas.Message]: List of Message objects\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "get_metamessages_apps__app_id__users__user_id__sessions__session_id__metamessages_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "message_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Message Id" + } + }, + { + "name": "metamessage_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Metamessage Type" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_Metamessage_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions/{session_id}/metamessages/{metamessage_id}": { + "get": { + "summary": "Get Metamessage", + "description": "Get a specific Metamessage by ID\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client application using\n honcho\n user_id (str): The User ID representing the user, managed by the user\n session_id (int): The ID of the Session to retrieve\n\nReturns:\n schemas.Session: The Session object of the requested Session\n\nRaises:\n HTTPException: If the session is not found", + "operationId": "get_metamessage_apps__app_id__users__user_id__sessions__session_id__metamessages__metamessage_id__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "metamessage_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Metamessage Id" + } + }, + { + "name": "message_id", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Message Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Metamessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}sessions/{session_id}/metamessages/{metamessage_id}": { + "put": { + "summary": "Update Metamessage", + "description": "Update's the metadata of a metamessage", + "operationId": "update_metamessage_apps__app_id__users__user_id_sessions__session_id__metamessages__metamessage_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "metamessage_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Metamessage Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MetamessageUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Metamessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections": { + "get": { + "summary": "Get Collections", + "description": "Get All Collections for a User\n\nArgs:\n app_id (uuid.UUID): The ID of the app representing the client\n application using honcho\n user_id (uuid.UUID): The User ID representing the user, managed by the user\n\nReturns:\n list[schemas.Collection]: List of Collection objects", + "operationId": "get_collections_apps__app_id__users__user_id__collections_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_Collection_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "summary": "Create Collection", + "operationId": "create_collection_apps__app_id__users__user_id__collections_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Collection" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections/{name}": { + "get": { + "summary": "Get Collection By Name", + "operationId": "get_collection_by_name_apps__app_id__users__user_id__collections__name__get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Collection" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections/{collection_id}": { + "put": { + "summary": "Update Collection", + "operationId": "update_collection_apps__app_id__users__user_id__collections__collection_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Collection" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "summary": "Delete Collection", + "operationId": "delete_collection_apps__app_id__users__user_id__collections__collection_id__delete", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents": { + "get": { + "summary": "Get Documents", + "operationId": "get_documents_apps__app_id__users__user_id__collections__collection_id__documents_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "title": "Reverse" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number", + "default": 1, + "title": "Page" + }, + "description": "Page number" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Page size", + "default": 50, + "title": "Size" + }, + "description": "Page size" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_Document_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "summary": "Create Document", + "operationId": "create_document_apps__app_id__users__user_id__collections__collection_id__documents_post", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Document" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections/{collection_id}/query": { + "get": { + "summary": "Query Documents", + "operationId": "query_documents_apps__app_id__users__user_id__collections__collection_id__query_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + }, + { + "name": "query", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Query" + } + }, + { + "name": "top_k", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 5, + "title": "Top K" + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Document" + }, + "title": "Response Query Documents Apps App Id Users User Id Collections Collection Id Query Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id}": { + "put": { + "summary": "Update Document", + "operationId": "update_document_apps__app_id__users__user_id__collections__collection_id__documents__document_id__put", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + }, + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Document Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Document" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "summary": "Delete Document", + "operationId": "delete_document_apps__app_id__users__user_id__collections__collection_id__documents__document_id__delete", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "collection_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + }, + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Document Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/apps/{app_id}/users/{user_id}/sessions/{session_id}/chat": { + "get": { + "summary": "Get Chat", + "operationId": "get_chat_apps__app_id__users__user_id__sessions__session_id__chat_get", + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "App Id" + } + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "User Id" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "query", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Query" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentChat" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AgentChat": { + "properties": { + "content": { + "type": "string", + "title": "Content" + } + }, + "type": "object", + "required": [ + "content" + ], + "title": "AgentChat" + }, + "App": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "name", + "h_metadata", + "metadata", + "created_at" + ], + "title": "App" + }, + "AppCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "AppCreate" + }, + "AppUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "title": "AppUpdate" + }, + "Collection": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "user_id": { + "type": "string", + "format": "uuid", + "title": "User Id" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "name", + "user_id", + "h_metadata", + "metadata", + "created_at" + ], + "title": "Collection" + }, + "CollectionCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "CollectionCreate" + }, + "CollectionUpdate": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "CollectionUpdate" + }, + "Document": { + "properties": { + "content": { + "type": "string", + "title": "Content" + }, + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "collection_id": { + "type": "string", + "format": "uuid", + "title": "Collection Id" + } + }, + "type": "object", + "required": [ + "content", + "id", + "h_metadata", + "metadata", + "created_at", + "collection_id" + ], + "title": "Document" + }, + "DocumentCreate": { + "properties": { + "content": { + "type": "string", + "title": "Content" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "content" + ], + "title": "DocumentCreate" + }, + "DocumentUpdate": { + "properties": { + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Content" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "title": "DocumentUpdate" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "Message": { + "properties": { + "content": { + "type": "string", + "title": "Content" + }, + "is_user": { + "type": "boolean", + "title": "Is User" + }, + "session_id": { + "type": "string", + "format": "uuid", + "title": "Session Id" + }, + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "content", + "is_user", + "session_id", + "id", + "h_metadata", + "metadata", + "created_at" + ], + "title": "Message" + }, + "MessageCreate": { + "properties": { + "content": { + "type": "string", + "title": "Content" + }, + "is_user": { + "type": "boolean", + "title": "Is User" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "content", + "is_user" + ], + "title": "MessageCreate" + }, + "MessageUpdate": { + "properties": { + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "title": "MessageUpdate" + }, + "Metamessage": { + "properties": { + "metamessage_type": { + "type": "string", + "title": "Metamessage Type" + }, + "content": { + "type": "string", + "title": "Content" + }, + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "message_id": { + "type": "string", + "format": "uuid", + "title": "Message Id" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "metamessage_type", + "content", + "id", + "message_id", + "h_metadata", + "metadata", + "created_at" + ], + "title": "Metamessage" + }, + "MetamessageCreate": { + "properties": { + "metamessage_type": { + "type": "string", + "title": "Metamessage Type" + }, + "content": { + "type": "string", + "title": "Content" + }, + "message_id": { + "type": "string", + "format": "uuid", + "title": "Message Id" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "metamessage_type", + "content", + "message_id" + ], + "title": "MetamessageCreate" + }, + "MetamessageUpdate": { + "properties": { + "message_id": { + "type": "string", + "format": "uuid", + "title": "Message Id" + }, + "metamessage_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Metamessage Type" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "message_id" + ], + "title": "MetamessageUpdate" + }, + "Page_Collection_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/Collection" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[Collection]" + }, + "Page_Document_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/Document" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[Document]" + }, + "Page_Message_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/Message" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[Message]" + }, + "Page_Metamessage_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/Metamessage" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[Metamessage]" + }, + "Page_Session_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/Session" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[Session]" + }, + "Page_User_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/User" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "page": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size" + }, + "pages": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Pages" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page", + "size" + ], + "title": "Page[User]" + }, + "Session": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "is_active": { + "type": "boolean", + "title": "Is Active" + }, + "user_id": { + "type": "string", + "format": "uuid", + "title": "User Id" + }, + "location_id": { + "type": "string", + "title": "Location Id" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "is_active", + "user_id", + "location_id", + "h_metadata", + "metadata", + "created_at" + ], + "title": "Session" + }, + "SessionCreate": { + "properties": { + "location_id": { + "type": "string", + "title": "Location Id" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "location_id" + ], + "title": "SessionCreate" + }, + "SessionUpdate": { + "properties": { + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "title": "SessionUpdate" + }, + "User": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "app_id": { + "type": "string", + "format": "uuid", + "title": "App Id" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "h_metadata": { + "type": "object", + "title": "H Metadata" + }, + "metadata": { + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "id", + "app_id", + "created_at", + "h_metadata", + "metadata" + ], + "title": "User" + }, + "UserCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "UserCreate" + }, + "UserUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "metadata": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + } + }, + "type": "object", + "title": "UserUpdate" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } +} \ No newline at end of file diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 0000000..6bcb833 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/getting-started/architecture.mdx b/docs/getting-started/architecture.mdx new file mode 100644 index 0000000..db62250 --- /dev/null +++ b/docs/getting-started/architecture.mdx @@ -0,0 +1,83 @@ +--- +title: 'Architecture' +description: 'Learn the core primitives and the structure of Honcho' +icon: 'building' +--- + +Honcho is a user context management system for AI powered applications. It is +inspired by but not a 1:1 mapping of the OpenAI Assistants API. While we are +building many similar primitives, we are going about it differently. + +Honcho is open source. We believe trust and transparency are vital for +developing AI technology. We're also focused on using and supporting existing +tools rather than developing from scratch. + +One of the main objectives of the Honcho project is to promote community +exploration of *user models*. Language models are highly capable of modeling +human psychology. By building a data management framework that is user-centric, +we aim to address not only practical application development issues (like +scaling, statefulness, etc.) but also kickstart exploration of the design space +of what's possible in terms of building user models. You can read more about +Honcho's origin, inspiration and philosophy on our +[blog](https://blog.plasticlabs.ai). + +## Core Primitives + +Using Honcho has the following flow: +1. Initialize your `Honcho` instance and `App` +2. Create a `User` +3. Create a `Session` for a `User`. +4. Create a `Collection` for a `User` +5. Add `Message`s to a `User`'s `Session`. +6. Add `Metamessage`s to `User`'s `Message`s +7. Add `Document`s to a `User`'s `Collection` + + +![DB Schema](../images/db_schema.png) + +### Users + +The `User` object is the main interface for managing a User's context. With it +you can interface with the `User`'s `Session`s and `Collections`s directly. + +### Sessions + +The `Session` object is useful for organizing your interactions with `User`s. +Different `User`s can have different sessions enabling you to neatly segment user +context. It also accepts a `location_id` parameter which can specifically +denote *where* users' sessions are taking place. + +### Messages + +Sessions are made up of `Message` objects. You can append them to sessions. +This is pretty straightforward. + +### Metamessages + +Plenty of applications have intermediate steps between `User` input and the +response that gets sent. The `Metamessage` object allows you to store those +intermediate steps and link them to the messages they were derived from. + +### Collections + +`Collections` are used to organize information about the `User`. These can be +thought of as stores for more global data about the `User` that spans sessions +while `Metamessages` are local to a session and the message they are linked to. + +### Documents + +`Documents` are the individual facts that are stored in the `Collection`. They +are stored as vector embeddings to allow for a RAG like interface. Using honcho +a developer can query a collection of documents using methods like cosine +similarity search + +## Conclusion + +Too often we hear developers enjoying a certain framework for building +LLM-powered applications only to see their codebase reach a level of complexity +that hits the limits of said framework. It ultimately gets abandoned and +developers implement their own solutions that without a doubt increase overhead +and maintenance. Our goal with Honcho is to provide a simple and flexible +storage framework accompanied by a smooth developer experience to ease pains +building the cumbersome parts of LLM applications. We hope this will allow +developers more freedom to explore exciting, yet-to-be-discovered areas! \ No newline at end of file diff --git a/docs/getting-started/deploying.mdx b/docs/getting-started/deploying.mdx new file mode 100644 index 0000000..4b585ba --- /dev/null +++ b/docs/getting-started/deploying.mdx @@ -0,0 +1,19 @@ +--- +title: 'Deploying Honcho' +sidebarTitle: 'Deploying' +description: 'Deploying the Honcho API Server with Docker' +icon: 'rocket' +--- + +If you're happy with how things are working locally, deploying your Honcho instance is a breeze with [Fly](https://fly.io). Follow the [Fly.io Docs](https://fly.io/docs/getting-started/) to setup your environment and the `flyctl`. + +Once `flyctl` is set up use the the following commands to launch the application: + +```bash +cd honcho/api +flyctl launch --no-deploy # Follow the prompts and edit as you see fit +cat .env | flyctl secrets import # Load in your secrets +flyctl deploy # Deploy with appropriate environment variables +``` + +Then you should have a new URL to initialize your Honcho client with! Consider your user context managed 🪄.` \ No newline at end of file diff --git a/docs/getting-started/development.mdx b/docs/getting-started/development.mdx new file mode 100644 index 0000000..8783008 --- /dev/null +++ b/docs/getting-started/development.mdx @@ -0,0 +1,98 @@ +--- +title: 'Development' +description: 'Learn how to preview changes locally' +--- + + + **Prerequisite** You should have installed Node.js (version 18.10.0 or + higher). + + +Step 1. Install Mintlify on your OS: + + + +```bash npm +npm i -g mintlify +``` + +```bash yarn +yarn global add mintlify +``` + + + +Step 2. Go to the docs are located (where you can find `mint.json`) and run the following command: + +```bash +mintlify dev +``` + +The documentation website is now available at `http://localhost:3000`. + +### Custom Ports + +Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333: + +```bash +mintlify dev --port 3333 +``` + +You will see an error like this if you try to run Mintlify in a port that's already taken: + +```md +Error: listen EADDRINUSE: address already in use :::3000 +``` + +## Mintlify Versions + +Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production. + + + +```bash npm +npm i -g mintlify@latest +``` + +```bash yarn +yarn global upgrade mintlify +``` + + + +## Deployment + + + Unlimited editors available under the [Startup + Plan](https://mintlify.com/pricing) + + +You should see the following if the deploy successfully went through: + + + + + +## Troubleshooting + +Here's how to solve some common problems when working with the CLI. + + + + Update to Node v18. Run `mintlify install` and try again. + + +Go to the `C:/Users/Username/.mintlify/` directory and remove the `mint` +folder. Then Open the Git Bash in this location and run `git clone +https://github.com/mintlify/mint.git`. + +Repeat step 3. + + + + Try navigating to the root of your device and delete the ~/.mintlify folder. + Then run `mintlify dev` again. + + + +Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/command-line) diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx new file mode 100644 index 0000000..9e7878e --- /dev/null +++ b/docs/getting-started/introduction.mdx @@ -0,0 +1,37 @@ +--- +title: 🫡 Welcome to Honcho +sidebarTitle: 'Overview' +description: 'Honcho is an open source framework for building personalized AI experiences.' +icon: 'face-saluting' +--- + + + +Honcho provides a simple API for managing multiple concurrent sessions across numerous users. +Now you can focus on improving your service instead of spending countless hours figuring out how to get it to scale. To learn more about the project, check out our blog post. + +Get started below! + + + + Quickly set up a local environment to interface with the Honcho API + + + Get an overview of the different primitives and structure of Honcho + + + Learn how to use LangChain and Honcho together to quickly scale your agents to many users + + diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx new file mode 100644 index 0000000..c6d78a6 --- /dev/null +++ b/docs/getting-started/quickstart.mdx @@ -0,0 +1,72 @@ +--- +title: 'Quickstart' +description: 'Start building awesome documentation in under 5 minutes' +icon: 'bolt' +--- + +To make things easy, there's an instance of Honcho up and running on a demo +server at [https://demo.honcho.dev](https://demo.honcho.dev/docs). The python +package defaults to this instance, so let's dive into how to get up and +running! + + +Install the Honcho client SDK with the following command: +```bash +pip install honcho-ai +``` +Alternatively, if you're using [Poetry](https://python-poetry.org/), run: +```bash +poetry add honcho-ai +``` + +Let's walk through simple Python steps. First, import the `Client` from the package: + +```python +from honcho import Honcho +``` + +Next, we want to register an application with the Honcho client: +```python +from uuid import uuid4 + +app_name = str(uuid4()) # random int for the app_name, but this can be any string +honcho = Honcho(app_name=app_name) +honcho.initialize() +``` + +This will create an application with the above name if it does not already exist or retrieve it if it does. After we have our application +initialized, we can make a user with the following: + +```python +user_name = str(uuid4()) # random int for the user_name, but this can be anything, should be unique for each user +user = honcho.create_user(user_name) +``` + +Now let's create a session for that application. Honcho is a user context management system, so you can create sessions for users. Thus, a `user_id` is required. +```python +session = user.create_session() +``` + +Let's add a user message and an AI message to that session: +```python +user_input = "Here's a message!" +ai_response = "I'm a helpful assistant!" + +session.create_message(is_user=True, content=user_input) +session.create_message(is_user=False, content=ai_response) +``` + +You can also easily query Honcho to get the session objects for that user with the following: +```python +sessions = list(user.get_sessions_generator()) +session = sessions[0] # gets the most recent session for that user +``` + +And finally you can get the messages within a session with the following: +```python +messages = list(session.get_messages_generator()) +``` + +This is a super simple overview of how to get up and running with the Honcho SDK. We covered the basic methods for reading and writing from the hosted storage service. Next, we'll cover alternative forms of hosting Honcho. + +For a more detailed look at the SDK check out the SDK reference [here](https://api.python.honcho.dev/). \ No newline at end of file diff --git a/docs/getting-started/self-hosting.mdx b/docs/getting-started/self-hosting.mdx new file mode 100644 index 0000000..2546f1e --- /dev/null +++ b/docs/getting-started/self-hosting.mdx @@ -0,0 +1,64 @@ +--- +title: 'Self-Hosting Honcho' +sidebarTitle: 'Self-Hosting' +description: 'Running a local version of the Honcho API Server' +icon: 'cloud' +--- + + + +Honcho is a monorepo that contains the server and API to manage database interactions. It stores data about an application's state along with the python sdk for interacting with the API. +You can host it locally quite easily! This can be beneficial for iterative development, testing, and debugging. This guide will go through the different ways to host locally. + +### Setup + +1. Clone the repository: +```bash +git clone git@github.com:plastic-labs/honcho.git +``` + +2. Copy the `.env.template` file to `.env` and specify the type of database and `CONNECTION_URI`. For testing sqlite is fine. The below example uses a sqlite database with a local file: + +> Honcho has been tested with Postgresql and SQLite + +```env +DATABASE_TYPE=sqlite +CONNECTION_URI=sqlite:///api.db +``` + +Now the tutorial will diverge based on how you'd like to run the API. + +### Poetry + +This project utilizes [Poetry](https://python-poetry.org/) for dependency management, so you can run the API through a poetry virtual environment. + +3. Create a virtualenv and install the API's dependencies: +```bash +cd honcho/api/ # change to the api directory +poetry shell # Activate virutal environment +poetry install # install dependencies +``` + +4. Run the API via uvicorn: +```bash +cd honcho/api # change to the api directory +poetry shell # Activate virtual environment if not already enabled +python -m uvicorn src.main:app --reload +``` + +### Docker + +Alternatively there is also a `Dockerfile` included to run the API server from a docker container. + +The `.env` file is not loaded into the docker container and should still be configured from outside. + +3. Build the docker image: +```bash +cd honcho/api +docker build -t honcho-api . +``` + +4. Run the docker image: +```bash +docker run --env-file .env -p 8000:8000 honcho-api:latest +``` \ No newline at end of file diff --git a/docs/guides/dialectic-endpoint.mdx b/docs/guides/dialectic-endpoint.mdx new file mode 100644 index 0000000..0c83908 --- /dev/null +++ b/docs/guides/dialectic-endpoint.mdx @@ -0,0 +1,54 @@ +--- +title: "Dialectic Endpoint" +description: "An endpoint for easily reasoning about your users" +icon: "comments" +--- + +> This guide goes over automatic insights generated by Honcho. An example +> of this being used can be found in [Curation Buddy](https://github.com/vintrocode/curation-buddy) + +Honcho will do automatic reasoning for you to derive facts about users and +allow your own agents to use them to reason about the user's needs. There are +two aspects to this: + +1. Automatic Fact Derivation +2. Dialectic Endpoint + +## Automatic Fact Derivation + +When you are saving conversations in sessions and messages via Honcho an automatic callback is run that +will reason about the conversations and store facts in a `collection` named **Honcho**. This is a reserved `collection` +specifically for the backend Honcho agent to interact with. + +These facts are derived asynchonously and automatically as your users interact with your agents. + +## Dialectic Endpoint + +You can make use the automatically derived facts in the `Honcho` collection directly by querying the documents stored in it, +but an alternative is to use the *Dialectic Endpoint`. What this is, is an endpoint that allows you to talk to an agent that +can automatically take the collection into their context and reason about the users with you. + +This chat interface is exposed via the `Sessions` object. + +Belows is some example code on how this works. + +```python +from honcho import Honcho + +honcho = Honcho(app_name="demo") +honcho.initialize() + +# create or get user +user = honcho.create_or_get_user("demo-user") + +# create or get session +session = honcho.create_or_get_session(user, "demo-session") + +# Talk to the dialectic agent to reason about their needs +answer = session.chat("What is the user's favorite way of completing the task") +``` + + + + + diff --git a/docs/guides/discord.mdx b/docs/guides/discord.mdx new file mode 100644 index 0000000..a55b193 --- /dev/null +++ b/docs/guides/discord.mdx @@ -0,0 +1,113 @@ +--- +title: "Discord Bots with Honcho" +icon: 'discord' +description: "Discord is a powerful chat application that handles many UI complications" +sidebarTitle: 'Discord' +--- + +Any application interface that defines logic based on events and supports special commands can work easily with Honcho. Here's how to use Honcho with **Discord** as an interface. If you're not familiar with Discord bot application logic, the [py-cord](https://pycord.dev/) docs would be a good place to start. + +## Events + +Most Discord bots have async functions that listen for specific events, the most common one being messages. We can use Honcho to store messages by user and session based on an interface's event logic. Take the following function definition for example: +```python +@bot.event +async def on_message(message): + if message.author == bot.user: + return + + user_id = f"discord_{str(message.author.id)}" + user = honcho.get_or_create_user(user_id) + location_id=str(message.channel.id) + + sessions = list(user.get_sessions_generator(location_id)) + + if len(sessions) > 0: + session = sessions[0] + else: + session = user.create_session(location_id) + + history = list(session.get_messages_generator()) + chat_history = langchain_message_converter(history) + + inp = message.content + session.create_message(is_user=True, content=inp) + + async with message.channel.typing(): + response = await chain.ainvoke({"chat_history": chat_history, "input": inp}) + await message.channel.send(response) + + session.create_message(is_user=False, content=response) +``` + +Let's break down what each chunk of code is doing... +```python +@bot.event +async def on_message(message): + if message.author == bot.user: + return +``` + +This is how you define an event function in `py-cord` that listens for messages and checks that the bot doesn't respond to itself. + +```python +user_id = f"discord_{str(message.author.id)}" +location_id = str(message.channel.id) +``` + +Honcho accepts a `location_id` argument to help separate out locations messages were sent (which is convenient for Discord channels). + +```python +sessions = list(user.get_sessions_generator(location_id)) +if len(sessions) > 0: + session = sessions[0] +else: + session = user.create_session(user_id, location_id) +``` + +Here we're querying the `session` object for the user based on the location (channel) they're in. This will get all the sessions, so the if statement just pops the most recent one (if there are many) or creates a new one if none exist. + +```python +history = list(session.get_messages_generator()) +chat_history = langchain_message_converter(history) + +inp = message.content +session.create_message(is_user=True, content=inp) + +async with message.channel.typing(): + response = await chain.ainvoke({"chat_history": chat_history, "input": inp}) + await message.channel.send(response) + +session.create_message(is_user=False, content=response) +``` + +This chunk is all about constructing the object to send to an LLM API. We get the messages from a `session` and construct a `chat_history` object with a quick utility function (more on that in the [Langchain](../llm-frameworks/langchain) guide). Then, we access the user message via `message.content` and use `session.create_message` to add it to Honcho. The `async with` method allows the bot to show that it's "typing" while waiting for an LLM response and then uses `message.channel.send` to respond to the user. We can then add that AI response to Honcho with the same `session.create_message` method, this time specifying that this message did not come from a user with `is_user=False`. + +## Slash Commands + +Discord bots also offer slash command functionality. We can use Honcho to do interesting things via slash commands. Here's a simple example: + +```python +@bot.slash_command(name = "restart", description = "Restart the Conversation") +async def restart(ctx): + user_id=f"discord_{str(ctx.author.id)}" + user = honcho.get_or_create_user(user_id) + location_id=str(ctx.channel_id) + sessions = list(user.get_sessions_generator(location_id)) + sessions[0].close() if len(sessions) > 0 else None + + msg = "Great! The conversation has been restarted. What would you like to talk about?" + await ctx.respond(msg) +``` + +This slash command restarts a conversation with a bot. In that case, we want to remove that session from storage. You can see we follow the same steps to access the user metadata via commands from the application interface: +```python +user_id=f"discord_{str(ctx.author.id)}" +user = honcho.get_or_create_user(user_id) +location_id=str(ctx.channel_id) +``` +Then we can retrieve and delete the messages associated with that metadata: +```python +sessions = list(user.get_sessions_generator(user_id, location_id)) +sessions[0].close() if len(sessions) > 0 else None +``` \ No newline at end of file diff --git a/docs/guides/langchain.mdx b/docs/guides/langchain.mdx new file mode 100644 index 0000000..5bb9c74 --- /dev/null +++ b/docs/guides/langchain.mdx @@ -0,0 +1,75 @@ +--- +title: 'LangChain Integration 🦜⛓️' +sidebarTitle: 'LangChain' +description: 'Using Honcho with LangChain with drop-in primitives' +icon: 'bird' +--- + +You can use Honcho to manage user context around LLM frameworks like LangChain. First, import the appropriate packages: + +```python +from honcho import Honcho + +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.output_parsers import StrOutputParser +from langchain_core.messages import AIMessage, HumanMessage + +from uuid import uuid4 # for generating random app and user names +from dotenv import load_dotenv # for loading in LLM API keys + +load_dotenv() # assumes you have a .env file with OPENAI_API_KEY defined +``` + +Next let's instantiate our Honcho client: + +```python +app_name = str(uuid4()) + +honcho = Honcho(app_name=app_name) +honcho.initialize() +user_name = str(uuid4()) +user = honcho.create_or_get_user(user_name) # create or get user +``` + +Then we can define our chain using the LangChain Expression Language ([LCEL](https://python.langchain.com/docs/expression_language/why)): +```python +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are a helpful assistant."), + MessagesPlaceholder(variable_name="chat_history"), + ("user", "{input}") +]) +model = ChatOpenAI(model="gpt-3.5-turbo") +output_parser = StrOutputParser() + +chain = prompt | model | output_parser +``` + +Honcho returns lists of `Message` objects when queried using a built-in method like `get_messages()`, so a quick utility function is needed to change the list format to message objects LangChain expects: + +```python +def langchain_message_converter(messages: List): + new_messages = [] + for message in messages: + if message.is_user: + new_messages.append(HumanMessage(content=message.content)) + else: + new_messages.append(AIMessage(content=message.content)) + return new_messages +``` + +Now we can structure Honcho calls around our LLM inference: +```python +sessions = list(user.get_sessions_generator(location_id)) # args come from application logic +session = sessions[0] # most recent session for user +history = list(session.get_messages_generator()) +chat_history = langchain_message_converter(history) # convert messages for LangChain +inp = "Here's a user message!" +session.create_message(is_user=True, content=inp) + +response = await chain.ainvoke({"chat_history": chat_history, "input": inp}) + +session.create_message(is_user=False, content=response) +``` + +Here we query messages from a user's session using Honcho and construct a chat history object to send to the LLM alongside our immediate user input. Once the LLM has responded, we can add that to Honcho! \ No newline at end of file diff --git a/docs/guides/overview.mdx b/docs/guides/overview.mdx new file mode 100644 index 0000000..176bdef --- /dev/null +++ b/docs/guides/overview.mdx @@ -0,0 +1,6 @@ +--- +title: "Spellbooks and Tutorials" +sidebarTitle: 'Overview' +description: 'Helpful guides and design patterns for building with Honcho' +icon: 'hat-wizard' +--- \ No newline at end of file diff --git a/docs/guides/simple-memory.mdx b/docs/guides/simple-memory.mdx new file mode 100644 index 0000000..bfe02f6 --- /dev/null +++ b/docs/guides/simple-memory.mdx @@ -0,0 +1,184 @@ +--- +title: "Simple Memory" +icon: "brain" +description: "A simple example of how to store and derive facts about individual users" +--- + +This guide shows how to implement a simple user memory system that derives and stores facts about users that +are then referenced later on. + +A fully working example can be found on [GitHub](https://github.com/plastic-labs/honcho/tree/main/example/discord/honcho-fact-memory). +It's setup as a discord bot so view our [Discord guide](../interfaces/discord) +for more details on how to set that up. + +## Initial Setup + +First we'll have some setup code to initialize our LLM, Honcho client, and prompts. + +```python +from langchain import OpenAI +from langchain.chat_models import ChatOpenAI +from langchain.prompts.chat import ( + ChatPromptTemplate, + SystemMessagePromptTemplate, + HumanMessagePromptTemplate, +) +from langchain.schema import ( + AIMessage, + HumanMessage, + SystemMessage +) +from langchain_core.output_parsers import NumberedListOutputParser + +llm: ChatOpenAI = ChatOpenAI(model_name="gpt-4") +honcho = Honcho(app_id="Simple User Memory") +``` + +The 3 steps to this project are: + +1. Derive facts about the user. +2. Store those facts in a per-user vector db (`Collection`). +3. Retrieve those facts later on to improve our response. + + +## Step 1 - Deriving Facts + +For this part we will be leveraging LangChain and GPT-4 to derivce facts on the user based on their recent input. + +```python +def derive_facts(user_input): + # Derive facts about the user + + fact_derivation = ChatPromptTemplate.from_messages([ + SystemMessagePromptTemplate(prompt=prompt) + ]) + + chain = fact_derivation | llm + + response = await chain.ainvoke({ + "user_input": input + }) + + output_parser = NumberedListOutputParser() + facts = output_parser.parse(response.content) + + # Return a list of facts + return facts +``` + +Breaking down this function we start by using LCEL to make a chain that will take the `user_input` as a variable. The +prompt used here is below: + +```yaml +_type: prompt +input_variables: + ["user_input"] +template: > + You are tasked with deriving discrete facts about the user based on their input. The goal is to only extract absolute facts from the message, do not make inferences beyond the text provided. + + ```{user_input}``` + + Output the facts as a numbered list. +``` + +We then invoke the chain and parse the output to get our list of facts. We take advantage of one of LangChain's built in output parsers for this. + +## Step 2 - Storing Facts + +This is where Honcho comes into play. With Honcho we can initialize `Collections` for each user and can store facts as vector embeddings. You can use multiple collections if you want to segment different types of facts or data, but for now we just need one. + +```python +def store_facts(user_name, facts): + user = honcho.get_or_create_user(user_name) + collection: Collection + try: # Check if collection exists if not create it + collection = user.get_collection("simple-memory") + except Exception + collection = user.create_collection("simple-memory") + + for fact in facts: # store each fact in the collection + collection.create_document(content=fact) + +``` + +That's it. We just make the collection if it doesn't already exist for the user and add the facts. + +## Step 3 - Retrieving Facts + +This is where we actually make user of the facts. To do this we need to first determine a query to the collection. This query is natural language that is compared to other facts in the collection using cosine similarity. + +The trick we use below is to have the LLM determine the query and then use it in a later step. + +```python +def introspect(chat_history, input): + + introspection_prompt = ChatPromptTemplate.from_messages([ + system_introspection + ]) + + # LCEL + chain = introspection_prompt | llm + + # inference + response = await chain.ainvoke({ + "chat_history": chat_history, + "input": input + }) + + # parse output + questions = output_parser.parse(response.content) + + return questions + +def respond(chat_history, input): + response_prompt = ChatPromptTemplate.from_messages([ + system_response, + chat_history + ]) + + retrieved_facts = collection.query(query=input, top_k=10) + + # LCEL + chain = response_prompt | llm + + # inference + response = await chain.ainvoke({ + "facts": retrieved_facts, + }) + + return response.content + +``` + +In the the `introspect` method we are using the user's response as input to derive the query. The prompt used is below: + +```yaml +_type: prompt +input_variables: + ["chat_history", "user_input"] +template: > + Given the conversation history and user input, use your theory of mind skills to list out questions you'd like to know about the user in order to best respond to them. + + Chat history: ```{chat_history}``` + User input: ```{user_input}``` + + Output the questions as a numbered list. +``` + +Then we use the question that it generated as the query in our `collection.query()` method. the `top_k` parameter allows you to control +how many retrieved facts to return with a max of 50. + +The prompt use there is below as well: + +```yaml +_type: prompt +input_variables: + ["facts"] +template: > + You are a helpful assistant. Craft a useful response based on the context provided in the conversation history and the facts we know about the user: + ```{facts}``` +``` +--- + +This is a very simple method of using Honcho to hold user context. For further reading on the limits read about +[violation of expectation](https://arxiv.org/abs/2310.06983). \ No newline at end of file diff --git a/docs/images/checks-passed.png b/docs/images/checks-passed.png new file mode 100644 index 0000000000000000000000000000000000000000..3303c773646ca12fb6852356663540e3ed048115 GIT binary patch literal 160724 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ⅈ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 lt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/hero-light.svg b/docs/images/hero-light.svg new file mode 100644 index 0000000..297d68f --- /dev/null +++ b/docs/images/hero-light.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg new file mode 100644 index 0000000..a628378 --- /dev/null +++ b/docs/logo/dark.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/logo/honcho-dark.svg b/docs/logo/honcho-dark.svg new file mode 100644 index 0000000..5e5244d --- /dev/null +++ b/docs/logo/honcho-dark.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/logo/honcho-light.svg b/docs/logo/honcho-light.svg new file mode 100644 index 0000000..db7d3d4 --- /dev/null +++ b/docs/logo/honcho-light.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 0000000..582b3b9 --- /dev/null +++ b/docs/logo/light.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/mint.json b/docs/mint.json new file mode 100644 index 0000000..f4284a4 --- /dev/null +++ b/docs/mint.json @@ -0,0 +1,177 @@ +{ + "$schema": "https://mintlify.com/schema.json", + "name": "Honcho", + "logo": { + "dark": "/logo/honcho-light.svg", + "light": "/logo/honcho-dark.svg" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#FF5A7E", + "light": "#FF5A7E", + "dark": "#FF5A7E", + "anchors": { + "from": "#FF5A7E", + "to": "#FF5A7E" + } + }, + "topbarCtaButton": { + "type": "github", + "url": "https://github.com/plastic-labs/honcho" + }, + "tabs": [ + { + "name": "Guides", + "url": "guides" + }, + { + "name": "API Reference", + "url": "api-reference" + } + ], + "anchors": [ + { + "name": "SDK Reference", + "icon": "book-open-cover", + "url": "https://api.python.honcho.dev" + }, + { + "name": "Community", + "icon": "discord", + "url": "https://discord.gg/plasticlabs" + }, + { + "name": "Blog", + "icon": "newspaper", + "url": "https://blog.plasticlabs.ai" + } + ], + "navigation": [ + { + "group": "Get Started", + "pages": [ + "getting-started/introduction", + "getting-started/quickstart", + "getting-started/architecture", + "getting-started/self-hosting", + "getting-started/deploying" + ] + }, + { + "group": "About", + "pages": [ + "about/contributing", + "about/license" + ] + }, + { + "group": "Getting Started", + "pages": [ + "guides/overview" + ] + }, + { + "group": "Application Interfaces", + "pages": [ + "guides/discord" + ] + }, + { + "group": "Integrations", + "pages": [ + "guides/langchain" + ] + }, + { + "group": "Personal Memory", + "pages": [ + "guides/simple-memory", + "guides/dialectic-endpoint" + ] + }, + { + "group": "API Documentation", + "pages": [ + "api-reference/introduction" + ] + }, + { + "group": "apps", + "pages": [ + "api-reference/endpoint/apps/get-app", + "api-reference/endpoint/apps/update-app", + "api-reference/endpoint/apps/get-app-by-name", + "api-reference/endpoint/apps/create-app", + "api-reference/endpoint/apps/get-or-create-app" + ] + }, + { + "group": "users", + "pages": [ + "api-reference/endpoint/users/get-users", + "api-reference/endpoint/users/create-user", + "api-reference/endpoint/users/get-user-by-name", + "api-reference/endpoint/users/get-or-create-user", + "api-reference/endpoint/users/update-user" + ] + }, + { + "group": "sessions", + "pages": [ + "api-reference/endpoint/sessions/get-sessions", + "api-reference/endpoint/sessions/create-session", + "api-reference/endpoint/sessions/get-session", + "api-reference/endpoint/sessions/update-session", + "api-reference/endpoint/sessions/delete-session", + "api-reference/endpoint/sessions/get-chat" + ] + }, + { + "group": "messages", + "pages": [ + "api-reference/endpoint/messages/get-messages", + "api-reference/endpoint/messages/create-message-for-session", + "api-reference/endpoint/messages/get-message", + "api-reference/endpoint/messages/update-message", + "api-reference/endpoint/messages/get-metamessages", + "api-reference/endpoint/messages/create-metamessage", + "api-reference/endpoint/messages/get-metamessage", + "api-reference/endpoint/messages/update-metamessage" + ] + }, + { + "group": "collections", + "pages": [ + "api-reference/endpoint/collections/get-collections", + "api-reference/endpoint/collections/create-collection", + "api-reference/endpoint/collections/get-collection-by-name", + "api-reference/endpoint/collections/update-collection", + "api-reference/endpoint/collections/delete-collection" + ] + }, + { + "group": "documents", + "pages": [ + "api-reference/endpoint/documents/get-documents", + "api-reference/endpoint/documents/create-document", + "api-reference/endpoint/documents/get-document", + "api-reference/endpoint/documents/update-document", + "api-reference/endpoint/documents/delete-document", + "api-reference/endpoint/documents/query-documents" + ] + } + ], + "footerSocials": { + "twitter": "https://twitter.com/plastic_labs", + "github": "https://github.com/plastic-labs", + "linkedin": "https://www.linkedin.com/company/plasticlabs" + }, + "openapi": [ + "https://demo.honcho.dev/openapi.json" + ], + "analytics": { + "posthog": { + "apiKey": "phc_1yrzzcgywqXGcerkkI4g7C0YfyPMcAKNOOvGcjTCiUk" + } + } +} diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..eba52aa --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,7910 @@ +{ + "name": "honcho-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "honcho-docs", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@mintlify/scraping": "^3.0.92", + "mintlify": "^4.0.132" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz", + "integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz", + "integrity": "sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "9.0.6", + "@apidevtools/openapi-schemas": "^2.1.0", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "ajv": "^8.6.3", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", + "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", + "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", + "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", + "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", + "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", + "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", + "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", + "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", + "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", + "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", + "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", + "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", + "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", + "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", + "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", + "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^0.45.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", + "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", + "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "node_modules/@mdx-js/mdx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz", + "integrity": "sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/mdx": "^2.0.0", + "estree-util-build-jsx": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-to-js": "^1.1.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^2.0.0", + "markdown-extensions": "^1.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^2.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "unified": "^10.0.0", + "unist-util-position-from-estree": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@mdx-js/mdx/node_modules/estree-util-to-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-1.2.0.tgz", + "integrity": "sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/unist-util-position-from-estree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz", + "integrity": "sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", + "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", + "dependencies": { + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@mintlify/cli": { + "version": "4.0.132", + "resolved": "https://registry.npmjs.org/@mintlify/cli/-/cli-4.0.132.tgz", + "integrity": "sha512-xMpMuUPWJpX2SX7lOPErjBhmqn86d6LOubY+26jGGDZoJPCsbI+lZUUllVxOGNU4kKPrLDR1a4NMncKWqtNoRg==", + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@mintlify/link-rot": "3.0.143", + "@mintlify/models": "0.0.71", + "@mintlify/prebuild": "1.0.143", + "@mintlify/previewing": "4.0.129", + "@mintlify/validation": "0.1.128", + "chalk": "^5.2.0", + "detect-port": "^1.5.1", + "fs-extra": "^11.1.1", + "gray-matter": "^4.0.3", + "ora": "^6.1.2", + "unist-util-visit": "^4.1.1", + "yargs": "^17.6.0" + }, + "bin": { + "mintlify": "bin/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/common": { + "version": "1.0.75", + "resolved": "https://registry.npmjs.org/@mintlify/common/-/common-1.0.75.tgz", + "integrity": "sha512-UnkMdpkSHOpfoDEWiai8AuzOOAh3quOwuPQf5nK/p9U5YBcJl35zHQl6FU6R+TXiatPVbZlYhx7RbIV4BXk0Jg==", + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@mintlify/mdx": "0.0.44", + "@mintlify/models": "0.0.71", + "@mintlify/validation": "0.1.128", + "@sindresorhus/slugify": "^2.1.1", + "acorn": "^8.11.2", + "acorn-jsx": "^5.3.2", + "esast-util-from-js": "^2.0.0", + "estree-util-to-js": "^2.0.0", + "estree-walker": "^3.0.3", + "gray-matter": "^4.0.3", + "hast-util-from-html": "^1.0.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-html": "^8.0.3", + "hast-util-to-text": "^4.0.0", + "is-absolute-url": "^4.0.1", + "lodash": "^4.17.21", + "mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.2.0", + "mdast-util-gfm": "^2.0.1", + "mdast-util-mdx": "^2.0.0", + "mdast-util-mdx-jsx": "^2.1.2", + "mdast-util-mdxjs-esm": "^1.3.0", + "micromark-extension-mdx-jsx": "^1.0.3", + "micromark-extension-mdxjs": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "openapi-types": "^12.0.0", + "remark": "^14.0.2", + "remark-frontmatter": "^4.0.1", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-mdx": "^2.0.0", + "unist-builder": "^3.0.1", + "unist-util-map": "^3.1.2", + "unist-util-remove": "^3.1.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-visit": "^4.1.1", + "unist-util-visit-parents": "^5.0.0", + "vfile": "^5.3.6" + } + }, + "node_modules/@mintlify/link-rot": { + "version": "3.0.143", + "resolved": "https://registry.npmjs.org/@mintlify/link-rot/-/link-rot-3.0.143.tgz", + "integrity": "sha512-Yow8zByr9Kr4DjIdRcp6LtJSi61exAj8A/aeMg3uYIHIhWl5aZqAdBGi17XhtIRMXc/AqTqTxG5XjTzHdVoEMg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.x", + "@mintlify/common": "1.0.75", + "@mintlify/prebuild": "1.0.143", + "chalk": "^5.1.0", + "fs-extra": "^11.1.0", + "gray-matter": "^4.0.3", + "is-absolute-url": "^4.0.1", + "openapi-types": "^12.0.0", + "unist-util-visit": "^4.1.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/mdx": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@mintlify/mdx/-/mdx-0.0.44.tgz", + "integrity": "sha512-lvgadIgVxvqJUC4OeRugQGDfbNE7WnxoVaJk4Wvd2EFSB+3o9b1/XvXLrsOcD9uwyFgK94YhpmZQbftHu8ol0A==", + "dependencies": { + "hast-util-to-string": "^2.0.0", + "next-mdx-remote": "^4.4.1", + "refractor": "^4.8.0", + "rehype-katex": "^6.0.3", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-smartypants": "^2.0.0", + "unist-util-visit": "^4.1.1" + } + }, + "node_modules/@mintlify/models": { + "version": "0.0.71", + "resolved": "https://registry.npmjs.org/@mintlify/models/-/models-0.0.71.tgz", + "integrity": "sha512-yQOCjQgpTdy3Y7SC5y5KHJ3WXADwhwj35NnDDVXQgthMkZ/sUjTkG5eiVt0s1pU0IJY5oBDGUQQ8hEEpExlmeg==", + "dependencies": { + "axios": "^1.4.0", + "openapi-types": "12.x" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/prebuild": { + "version": "1.0.143", + "resolved": "https://registry.npmjs.org/@mintlify/prebuild/-/prebuild-1.0.143.tgz", + "integrity": "sha512-nFV6tsPqPE48dRJJFhyXNQ7lIwLxXYdCkkNLyqpdlYv2uv/sWNT0fu//XCUU0Wjiks86rrg5RLy3sXfwDFksOg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.x", + "@mintlify/common": "1.0.75", + "@mintlify/validation": "0.1.128", + "favicons": "^7.0.2", + "fs-extra": "^11.1.0", + "gray-matter": "^4.0.3", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "openapi-types": "12.x", + "unist-util-visit": "^4.1.1" + } + }, + "node_modules/@mintlify/prebuild/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@mintlify/prebuild/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@mintlify/previewing": { + "version": "4.0.129", + "resolved": "https://registry.npmjs.org/@mintlify/previewing/-/previewing-4.0.129.tgz", + "integrity": "sha512-JpRMHs8Yop5JHkARqBt47MwwxU4bU3m6pufD2cm7evYFUtKXDPVV/kfNFqBb7AuWQP5cMlHgCJNAMDbGNSUp2g==", + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@mintlify/common": "1.0.75", + "@mintlify/prebuild": "1.0.143", + "@mintlify/validation": "0.1.128", + "@octokit/rest": "^19.0.5", + "chalk": "^5.1.0", + "chokidar": "^3.5.3", + "express": "^4.18.2", + "fs-extra": "^11.1.0", + "got": "^13.0.0", + "gray-matter": "^4.0.3", + "is-absolute-url": "^4.0.1", + "is-online": "^10.0.0", + "open": "^8.4.0", + "openapi-types": "^12.0.2", + "ora": "^6.1.2", + "socket.io": "^4.7.2", + "tar": "^6.1.15", + "unist-util-visit": "^4.1.1", + "yargs": "^17.6.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/scraping": { + "version": "3.0.92", + "resolved": "https://registry.npmjs.org/@mintlify/scraping/-/scraping-3.0.92.tgz", + "integrity": "sha512-cg8tSDahcIXpvGr8CBBAC836yJEWofdK8eRJu8rCoA7kTuRYEaYXFMV8RA4UDLY9bPQ4wiOkXvZzocIcEACskg==", + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@mintlify/common": "1.0.76", + "axios": "^1.2.2", + "cheerio": "^0.22.0", + "fs-extra": "^11.1.1", + "node-html-markdown": "^1.3.0", + "ora": "^6.1.2", + "puppeteer": "^19.4.0", + "yargs": "^17.6.0" + }, + "bin": { + "mintlify-scrape": "bin/cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/scraping/node_modules/@mintlify/common": { + "version": "1.0.76", + "resolved": "https://registry.npmjs.org/@mintlify/common/-/common-1.0.76.tgz", + "integrity": "sha512-aM+E0u5rLKu+6z64eoCKsvnAB0WziBx+wnRStFrbRdTCSt/LpsSbW+bNdz9AJOQADtiQdS39cLFPUVow7IPi7w==", + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@mintlify/mdx": "0.0.44", + "@mintlify/models": "0.0.72", + "@mintlify/validation": "0.1.129", + "@sindresorhus/slugify": "^2.1.1", + "acorn": "^8.11.2", + "acorn-jsx": "^5.3.2", + "esast-util-from-js": "^2.0.0", + "estree-util-to-js": "^2.0.0", + "estree-walker": "^3.0.3", + "gray-matter": "^4.0.3", + "hast-util-from-html": "^1.0.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-html": "^8.0.3", + "hast-util-to-text": "^4.0.0", + "is-absolute-url": "^4.0.1", + "lodash": "^4.17.21", + "mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.2.0", + "mdast-util-gfm": "^2.0.1", + "mdast-util-mdx": "^2.0.0", + "mdast-util-mdx-jsx": "^2.1.2", + "mdast-util-mdxjs-esm": "^1.3.0", + "micromark-extension-mdx-jsx": "^1.0.3", + "micromark-extension-mdxjs": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "openapi-types": "^12.0.0", + "remark": "^14.0.2", + "remark-frontmatter": "^4.0.1", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-mdx": "^2.0.0", + "unist-builder": "^3.0.1", + "unist-util-map": "^3.1.2", + "unist-util-remove": "^3.1.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-visit": "^4.1.1", + "unist-util-visit-parents": "^5.0.0", + "vfile": "^5.3.6" + } + }, + "node_modules/@mintlify/scraping/node_modules/@mintlify/models": { + "version": "0.0.72", + "resolved": "https://registry.npmjs.org/@mintlify/models/-/models-0.0.72.tgz", + "integrity": "sha512-nwwC/PT2ieB7eDFzwB8p8j/rm35dmzvaLf2+1bZiuCmgi3xMcx39QY0HH83lgV98YgUzPs4EvBeAmpwdn4mkdQ==", + "dependencies": { + "axios": "^1.4.0", + "openapi-types": "12.x" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/scraping/node_modules/@mintlify/validation": { + "version": "0.1.129", + "resolved": "https://registry.npmjs.org/@mintlify/validation/-/validation-0.1.129.tgz", + "integrity": "sha512-xJ/G9rt0ryDwtRfcWuvkIeqHyfKdin/LEri/mRyS+caY/qACTWtRQBo/W99E++xPOq+ZRe/xppxmajXsTuU8Pg==", + "dependencies": { + "@mintlify/models": "0.0.72", + "lcm": "^0.0.3", + "lodash": "^4.17.21", + "openapi-types": "12.x", + "zod": "^3.20.6", + "zod-to-json-schema": "^3.20.3" + } + }, + "node_modules/@mintlify/validation": { + "version": "0.1.128", + "resolved": "https://registry.npmjs.org/@mintlify/validation/-/validation-0.1.128.tgz", + "integrity": "sha512-fXfsaRWddFs621Hf1NYpm4aFWFypLPrH68WRNqnImqad3dz6bPgtEYoS0q7bxsIb4HsxSdZUHBhdEsOtY9jcZA==", + "dependencies": { + "@mintlify/models": "0.0.71", + "lcm": "^0.0.3", + "lodash": "^4.17.21", + "openapi-types": "12.x", + "zod": "^3.20.6", + "zod-to-json-schema": "^3.20.3" + } + }, + "node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", + "dependencies": { + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz", + "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==", + "dependencies": { + "@octokit/types": "^10.0.0" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz", + "integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/rest": { + "version": "19.0.13", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.13.tgz", + "integrity": "sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==", + "dependencies": { + "@octokit/core": "^4.2.1", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^7.1.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==" + }, + "node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", + "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=14.1.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/hast/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" + }, + "node_modules/@types/katex": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz", + "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==" + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/mdast/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/mdx": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.11.tgz", + "integrity": "sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/nlcst": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-1.0.4.tgz", + "integrity": "sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/nlcst/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" + }, + "node_modules/@types/prismjs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", + "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/react": { + "version": "18.2.67", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.67.tgz", + "integrity": "sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", + "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-bidi": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", + "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==", + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "engines": { + "node": "*" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1107588", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", + "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dns-socket": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/dns-socket/-/dns-socket-4.2.2.tgz", + "integrity": "sha512-BDeBd8najI4/lS00HSKpdFia+OvUMytaVjfzR9n5Lq8MlZRSvtbI+uLtx1+XmQFls5wFU9dssccTmQQ6nfpjdg==", + "dependencies": { + "dns-packet": "^5.2.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.1.tgz", + "integrity": "sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz", + "integrity": "sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.1.0.tgz", + "integrity": "sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/favicons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.2.0.tgz", + "integrity": "sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw==", + "dependencies": { + "escape-html": "^1.0.3", + "sharp": "^0.33.1", + "xml2js": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gcd": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/gcd/-/gcd-0.0.1.tgz", + "integrity": "sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw==" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-dom/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-1.0.2.tgz", + "integrity": "sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^7.0.0", + "vfile": "^5.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/hast-util-from-html/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/hast-util-to-estree": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.3.3.tgz", + "integrity": "sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "estree-util-attach-comments": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.1", + "unist-util-position": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", + "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.0.tgz", + "integrity": "sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-online": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/is-online/-/is-online-10.0.0.tgz", + "integrity": "sha512-WCPdKwNDjXJJmUubf2VHLMDBkUZEtuOvpXUfUnUFbEnM6In9ByiScL4f4jKACz/fsb2qDkesFerW3snf/AYz3A==", + "dependencies": { + "got": "^12.1.0", + "p-any": "^4.0.0", + "p-timeout": "^5.1.0", + "public-ip": "^5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-online/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz", + "integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lcm": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/lcm/-/lcm-0.0.3.tgz", + "integrity": "sha512-TB+ZjoillV6B26Vspf9l2L/vKaRY/4ep3hahcyVkCGFgsTNRUQdc24bQeNFiZeoxH0vr5+7SfNRMQuPHv/1IrQ==", + "dependencies": { + "gcd": "^0.0.1" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" + }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast/-/mdast-3.0.0.tgz", + "integrity": "sha512-xySmf8g4fPKMeC07jXGz971EkLbWAJ83s4US2Tj9lEdnZ142UP5grN73H1Xd3HzrdbU5o9GYYP/y8F9ZSwLE9g==", + "deprecated": "`mdast` was renamed to `remark`" + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mdast-util-frontmatter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-1.0.1.tgz", + "integrity": "sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-extension-frontmatter": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.2.tgz", + "integrity": "sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.1.tgz", + "integrity": "sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdx-jsx": "^2.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.2.tgz", + "integrity": "sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.4.tgz", + "integrity": "sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "mdast-util-from-markdown": "^1.1.0", + "mdast-util-to-markdown": "^1.3.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.1.tgz", + "integrity": "sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-1.1.1.tgz", + "integrity": "sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.1.2.tgz", + "integrity": "sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==", + "dependencies": { + "@types/katex": "^0.16.0", + "katex": "^0.16.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.8.tgz", + "integrity": "sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.5.tgz", + "integrity": "sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.1.tgz", + "integrity": "sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.1.tgz", + "integrity": "sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^1.0.0", + "micromark-extension-mdx-jsx": "^1.0.0", + "micromark-extension-mdx-md": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.5.tgz", + "integrity": "sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==", + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-core-commonmark": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.1.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/unist-util-position-from-estree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz", + "integrity": "sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.9.tgz", + "integrity": "sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/micromark-factory-mdx-expression/node_modules/unist-util-position-from-estree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz", + "integrity": "sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.3.tgz", + "integrity": "sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^2.0.0", + "estree-util-visit": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/micromark-util-events-to-acorn/node_modules/estree-util-visit": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.1.tgz", + "integrity": "sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mintlify": { + "version": "4.0.132", + "resolved": "https://registry.npmjs.org/mintlify/-/mintlify-4.0.132.tgz", + "integrity": "sha512-aT4HjfHAd6BE4MsdFfHzgu/e64OkVJaZnIrLqJNOHAxdjpPXZ539GdLHY7llHvImcBY4IFCFoeGGQzZHTTnOQw==", + "dependencies": { + "@mintlify/cli": "4.0.132" + }, + "bin": { + "mintlify": "index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-mdx-remote": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.4.1.tgz", + "integrity": "sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==", + "dependencies": { + "@mdx-js/mdx": "^2.2.1", + "@mdx-js/react": "^2.2.1", + "vfile": "^5.3.0", + "vfile-matter": "^3.0.1" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "peerDependencies": { + "react": ">=16.x <=18.x", + "react-dom": ">=16.x <=18.x" + } + }, + "node_modules/nlcst-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz", + "integrity": "sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==", + "dependencies": { + "@types/nlcst": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-html-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-html-markdown/-/node-html-markdown-1.3.0.tgz", + "integrity": "sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g==", + "dependencies": { + "node-html-parser": "^6.1.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/node-html-parser": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.12.tgz", + "integrity": "sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/node-html-parser/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/node-html-parser/node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/node-html-parser/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/node-html-parser/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" + }, + "node_modules/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-any": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-any/-/p-any-4.0.0.tgz", + "integrity": "sha512-S/B50s+pAVe0wmEZHmBs/9yJXeZ5KhHzOsgKzt0hRdgkoR3DxW9ts46fcsWi/r3VnzsnkKS7q4uimze+zjdryw==", + "dependencies": { + "p-cancelable": "^3.0.0", + "p-some": "^6.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-some": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-some/-/p-some-6.0.0.tgz", + "integrity": "sha512-CJbQCKdfSX3fIh8/QKgS+9rjm7OBNUTmwWswAFQAhc8j1NR1dsEDETUEuVUtQHZpV+J03LqWBEwvu0g1Yn+TYg==", + "dependencies": { + "aggregate-error": "^4.0.0", + "p-cancelable": "^3.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-latin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-5.0.1.tgz", + "integrity": "sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==", + "dependencies": { + "nlcst-to-string": "^3.0.0", + "unist-util-modify-children": "^3.0.0", + "unist-util-visit-children": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/property-information": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz", + "integrity": "sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/public-ip": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/public-ip/-/public-ip-5.0.0.tgz", + "integrity": "sha512-xaH3pZMni/R2BG7ZXXaWS9Wc9wFlhyDVJF47IJ+3ali0TGv+2PsckKxbmo+rnx3ZxiV2wblVhtdS3bohAP6GGw==", + "dependencies": { + "dns-socket": "^4.2.2", + "got": "^12.0.0", + "is-ip": "^3.1.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/public-ip/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", + "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "deprecated": "< 21.5.0 is no longer supported", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "0.5.0", + "cosmiconfig": "8.1.3", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.11.1" + } + }, + "node_modules/puppeteer-core": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", + "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "dependencies": { + "@puppeteer/browsers": "0.5.0", + "chromium-bidi": "0.4.7", + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1107588", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.13.0" + }, + "engines": { + "node": ">=14.14.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/refractor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", + "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^7.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.3.tgz", + "integrity": "sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/katex": "^0.14.0", + "hast-util-from-html-isomorphic": "^1.0.0", + "hast-util-to-text": "^3.1.0", + "katex": "^0.16.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/rehype-katex/node_modules/hast-util-from-dom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz", + "integrity": "sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==", + "dependencies": { + "hastscript": "^7.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-from-html-isomorphic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-1.0.0.tgz", + "integrity": "sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-dom": "^4.0.0", + "hast-util-from-html": "^1.0.0", + "unist-util-remove-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-is-element": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", + "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-to-text": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz", + "integrity": "sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/unist-util-find-after": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz", + "integrity": "sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", + "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", + "dependencies": { + "@types/mdast": "^3.0.0", + "remark-parse": "^10.0.0", + "remark-stringify": "^10.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-frontmatter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz", + "integrity": "sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-frontmatter": "^1.0.0", + "micromark-extension-frontmatter": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz", + "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-math": "^2.0.0", + "micromark-extension-math": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.3.0.tgz", + "integrity": "sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==", + "dependencies": { + "mdast-util-mdx": "^2.0.0", + "micromark-extension-mdxjs": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.1.0.tgz", + "integrity": "sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==", + "dependencies": { + "retext": "^8.1.0", + "retext-smartypants": "^5.2.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-smartypants/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", + "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", + "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "retext-latin": "^3.0.0", + "retext-stringify": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-3.1.0.tgz", + "integrity": "sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "parse-latin": "^5.0.0", + "unherit": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-5.2.0.tgz", + "integrity": "sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-3.1.0.tgz", + "integrity": "sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sharp": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", + "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "semver": "^7.5.4" + }, + "engines": { + "libvips": ">=8.15.1", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.2", + "@img/sharp-darwin-x64": "0.33.2", + "@img/sharp-libvips-darwin-arm64": "1.0.1", + "@img/sharp-libvips-darwin-x64": "1.0.1", + "@img/sharp-libvips-linux-arm": "1.0.1", + "@img/sharp-libvips-linux-arm64": "1.0.1", + "@img/sharp-libvips-linux-s390x": "1.0.1", + "@img/sharp-libvips-linux-x64": "1.0.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", + "@img/sharp-libvips-linuxmusl-x64": "1.0.1", + "@img/sharp-linux-arm": "0.33.2", + "@img/sharp-linux-arm64": "0.33.2", + "@img/sharp-linux-s390x": "0.33.2", + "@img/sharp-linux-x64": "0.33.2", + "@img/sharp-linuxmusl-arm64": "0.33.2", + "@img/sharp-linuxmusl-x64": "0.33.2", + "@img/sharp-wasm32": "0.33.2", + "@img/sharp-win32-ia32": "0.33.2", + "@img/sharp-win32-x64": "0.33.2" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/tar-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "optional": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unherit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", + "integrity": "sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-builder": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-builder/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-map": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/unist-util-map/-/unist-util-map-3.1.3.tgz", + "integrity": "sha512-4/mDauoxqZ6geK97lJ6n2kDk6JK88Vh+hWMSJqyaaP/7eqN1dDhjcjnNxKNm3YU6Sw7PVJtcFMUbnmHvYzb6Vg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-map/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-modify-children": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz", + "integrity": "sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==", + "dependencies": { + "@types/unist": "^2.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-remove": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-3.1.1.tgz", + "integrity": "sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz", + "integrity": "sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-remove/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz", + "integrity": "sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/vfile-matter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-3.0.1.tgz", + "integrity": "sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==", + "dependencies": { + "@types/js-yaml": "^4.0.0", + "is-buffer": "^2.0.0", + "js-yaml": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-matter/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/vfile-matter/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/vfile/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.4.tgz", + "integrity": "sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==", + "peerDependencies": { + "zod": "^3.22.4" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..06cb917 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,16 @@ +{ + "name": "honcho-docs", + "version": "1.0.0", + "description": "## Setting Up `honcho-docs` Locally", + "main": ".pnp.js", + "scripts": { + "dev": "mintlify dev", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@mintlify/scraping": "^3.0.92", + "mintlify": "^4.0.132" + } +} diff --git a/example/cli/main.py b/example/cli/main.py index 2e59878..57c2450 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -7,7 +7,7 @@ from langchain_community.chat_models.fake import FakeListChatModel from honcho import Honcho -from honcho.ext.langchain import _messages_to_langchain +from honcho.ext.langchain import messages_to_langchain app_name = str(uuid4()) @@ -36,7 +36,7 @@ def chat(): break user_message = HumanMessage(content=user_input) history = list(session.get_messages_generator()) - langchain_history = _messages_to_langchain(history) + langchain_history = messages_to_langchain(history) prompt = ChatPromptTemplate.from_messages( [system, *langchain_history, user_message] ) diff --git a/example/discord/honcho-dspy-personas/bot.py b/example/discord/honcho-dspy-personas/bot.py index 2e0ebfd..8a7c9ba 100644 --- a/example/discord/honcho-dspy-personas/bot.py +++ b/example/discord/honcho-dspy-personas/bot.py @@ -2,7 +2,7 @@ from uuid import uuid1 import discord from honcho import Honcho -from honcho.ext.langchain import _messages_to_langchain +from honcho.ext.langchain import messages_to_langchain from graph import chat from dspy import Example @@ -61,7 +61,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator())[:5] - chat_history = _messages_to_langchain(history) + chat_history = messages_to_langchain(history) inp = message.content user_message = session.create_message(is_user=True, content=inp) diff --git a/example/discord/honcho-fact-memory/bot.py b/example/discord/honcho-fact-memory/bot.py index aba5099..d29b2c2 100644 --- a/example/discord/honcho-fact-memory/bot.py +++ b/example/discord/honcho-fact-memory/bot.py @@ -2,7 +2,7 @@ from uuid import uuid1 import discord from honcho import Honcho -from honcho.ext.langchain import _messages_to_langchain +from honcho.ext.langchain import messages_to_langchain from chain import LMChain @@ -59,7 +59,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator()) - chat_history = _messages_to_langchain(history) + chat_history = messages_to_langchain(history) inp = message.content user_message = session.create_message(is_user=True, content=inp) diff --git a/example/discord/simple-roast-bot/main.py b/example/discord/simple-roast-bot/main.py index 281f07d..2b81280 100644 --- a/example/discord/simple-roast-bot/main.py +++ b/example/discord/simple-roast-bot/main.py @@ -11,8 +11,7 @@ from langchain_core.messages import AIMessage, HumanMessage from honcho import Honcho -from honcho.ext.langchain import _messages_to_langchain ->>>>>>> main +from honcho.ext.langchain import messages_to_langchain load_dotenv() @@ -68,7 +67,7 @@ async def on_message(message): session = user.create_session(location_id) history = list(session.get_messages_generator()) - chat_history = _messages_to_langchain(history) + chat_history = messages_to_langchain(history) inp = message.content session.create_message(is_user=True, content=inp) diff --git a/sdk/honcho/ext/langchain.py b/sdk/honcho/ext/langchain.py index a27a9fb..434ed7c 100644 --- a/sdk/honcho/ext/langchain.py +++ b/sdk/honcho/ext/langchain.py @@ -10,7 +10,7 @@ from honcho.schemas import Message -def requires_langchain(func): +def _requires_langchain(func): """A utility to check if langchain is installed before running a function""" @functools.wraps(func) @@ -25,8 +25,8 @@ def wrapper(*args, **kwargs): return wrapper -@requires_langchain -def _messages_to_langchain(messages: List[Message]): +@_requires_langchain +def messages_to_langchain(messages: List[Message]): """Converts Honcho messages to Langchain messages Args: @@ -47,8 +47,8 @@ def _messages_to_langchain(messages: List[Message]): return new_messages -@requires_langchain -def _langchain_to_messages( +@_requires_langchain +def langchain_to_messages( messages, session: Union[Session, AsyncSession] ) -> List[Message]: """Converts Langchain messages to Honcho messages and adds to appropriate session From 2067168e31076fc4dcb9e5bea95638ea8baa77be Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:47:02 -0700 Subject: [PATCH 81/85] Basic Auth Header validation --- api/.env.template | 4 ++++ api/docker-compose.yml.example | 1 + api/src/main.py | 37 ++++++++++++++++++++++++++++++++++ sdk/honcho/client.py | 34 ++++++++++++++++++++++--------- sdk/honcho/sync_client.py | 34 ++++++++++++++++++++++--------- sdk/poetry.lock | 17 +++++++++++++++- sdk/pyproject.toml | 1 + sdk/tests/test_sync.py | 8 ++++++-- 8 files changed, 115 insertions(+), 21 deletions(-) diff --git a/api/.env.template b/api/.env.template index 747fcaa..7940192 100644 --- a/api/.env.template +++ b/api/.env.template @@ -8,6 +8,10 @@ OPENAI_API_KEY= OPENTELEMETRY_ENABLED=false # Set to true to enable OpenTelemetry logging and tracing SENTRY_ENABLED=false # Set to true to enable Sentry logging and tracing +# Auth + +USE_AUTH_SERVICE=false + ## Sentry SENTRY_DSN= diff --git a/api/docker-compose.yml.example b/api/docker-compose.yml.example index 5be0752..7aabd04 100644 --- a/api/docker-compose.yml.example +++ b/api/docker-compose.yml.example @@ -25,6 +25,7 @@ services: - OTEL_RESOURCE_ATTRIBUTES= - DEBUG_LOG_OTEL_TO_PROVIDER=false - DEBUG_LOG_OTEL_TO_CONSOLE=true + - USE_AUTH_SERVICE=false database: image: ankane/pgvector restart: always diff --git a/api/src/main.py b/api/src/main.py index 8d7f9d6..167d003 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -9,6 +9,7 @@ from fastapi import ( APIRouter, FastAPI, + Request, ) from fastapi.responses import PlainTextResponse from fastapi_pagination import add_pagination @@ -43,6 +44,8 @@ from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address from starlette.exceptions import HTTPException as StarletteHTTPException +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.responses import Response from src.routers import ( apps, @@ -216,6 +219,40 @@ async def lifespan(app: FastAPI): add_pagination(app) +USE_AUTH_SERVICE = os.getenv("USE_AUTH_SERVICE", "False").lower() == "true" + + +# TODO make the API Token Validation Optional +class BearerTokenMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + authorization: Optional[str] = request.headers.get("Authorization") + if authorization: + scheme, _, token = authorization.partition(" ") + if scheme.lower() == "bearer": + if token == "default": + return await call_next(request) + + return Response(content="Invalid token.", status_code=400) + # Here you can add your token validation logic + # For example, checking token validity, expiration, etc. + # If the token is valid, you let the request pass through: + else: + # If the scheme is not Bearer, you might want to either + # 1. Reject the request + # 2. Ignore and proceed with the next middleware or the request + # This example demonstrates rejecting the request: + return Response( + content="Invalid authentication scheme.", status_code=400 + ) + + # If no Authorization header is present, you can choose to reject the request or let it pass + # This example demonstrates rejecting the request: + return Response(content="Authorization header missing.", status_code=401) + + +if USE_AUTH_SERVICE: + app.add_middleware(BearerTokenMiddleware) + @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request, exc): diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 268ce54..23df5ce 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -6,10 +6,12 @@ import datetime import json +import os import uuid from typing import Optional import httpx +from dotenv import load_dotenv from .schemas import Document, Message, Metamessage @@ -345,7 +347,12 @@ async def next(self): class AsyncHoncho: """Honcho API Client Object""" - def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): + def __init__( + self, + app_name: str, + base_url: str = "https://demo.honcho.dev", + api_key: str = "default", + ): """Constructor for Client Args: @@ -353,21 +360,30 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): base_url (str): Base URL for the instance of the Honcho API defaults to https://demo.honcho.dev """ + load_dotenv() + token = os.getenv("HONCHO_API_KEY", api_key) self.server_url: str = base_url # Base URL for the instance of the Honcho API - self.client: httpx.AsyncClient = httpx.AsyncClient() + self.client: httpx.AsyncClient = httpx.AsyncClient( + headers={"Authorization": f"Bearer {token}"} + ) self.app_name: str = app_name # Representing name of the client application self.app_id: uuid.UUID self.metadata: dict async def initialize(self): """Run initialization tasks for the Honcho client""" - res = await self.client.get( - f"{self.server_url}/apps/get_or_create/{self.app_name}" - ) - res.raise_for_status() - data = res.json() - self.app_id: uuid.UUID = data["id"] - self.metadata: dict = data["metadata"] + try: + res = await self.client.get( + f"{self.server_url}/apps/get_or_create/{self.app_name}" + ) + res.raise_for_status() + data = res.json() + self.app_id: uuid.UUID = data["id"] + self.metadata: dict = data["metadata"] + except httpx.HTTPStatusError as e: + error_content = e.response.content + print(error_content) + raise Exception(error_content) from None async def init(self): """Synonym for initialize""" diff --git a/sdk/honcho/sync_client.py b/sdk/honcho/sync_client.py index 205df5b..a47d4da 100644 --- a/sdk/honcho/sync_client.py +++ b/sdk/honcho/sync_client.py @@ -6,10 +6,12 @@ import datetime import json +import os import uuid from typing import Optional import httpx +from dotenv import load_dotenv from .schemas import Document, Message, Metamessage @@ -345,7 +347,12 @@ def next(self): class Honcho: """Honcho API Client Object""" - def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): + def __init__( + self, + app_name: str, + base_url: str = "https://demo.honcho.dev", + api_key: str = "default", + ): """Constructor for Client Args: @@ -353,21 +360,30 @@ def __init__(self, app_name: str, base_url: str = "https://demo.honcho.dev"): base_url (str): Base URL for the instance of the Honcho API defaults to https://demo.honcho.dev """ + load_dotenv() + token = os.getenv("HONCHO_API_KEY", api_key) self.server_url: str = base_url # Base URL for the instance of the Honcho API - self.client: httpx.Client = httpx.Client() + self.client: httpx.Client = httpx.Client( + headers={"Authorization": f"Bearer {token}"} + ) self.app_name: str = app_name # Representing name of the client application self.app_id: uuid.UUID self.metadata: dict def initialize(self): """Run initialization tasks for the Honcho client""" - res = self.client.get( - f"{self.server_url}/apps/get_or_create/{self.app_name}" - ) - res.raise_for_status() - data = res.json() - self.app_id: uuid.UUID = data["id"] - self.metadata: dict = data["metadata"] + try: + res = self.client.get( + f"{self.server_url}/apps/get_or_create/{self.app_name}" + ) + res.raise_for_status() + data = res.json() + self.app_id: uuid.UUID = data["id"] + self.metadata: dict = data["metadata"] + except httpx.HTTPStatusError as e: + error_content = e.response.content + print(error_content) + raise Exception(error_content) from None def init(self): """Synonym for initialize""" diff --git a/sdk/poetry.lock b/sdk/poetry.lock index a2c2b7c..9dde738 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -668,6 +668,21 @@ pytest = ">=7.0.0,<9" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "requests" version = "2.31.0" @@ -968,4 +983,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3265d992168555f7d9d60f6f03c18dc515b32de3b975290158b086d82247fefc" +content-hash = "181a418ea413f687cc31004dc10c50b606aba04cd4073391270d11ab99430e56" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index a4eb01c..5403326 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -10,6 +10,7 @@ packages = [{include = "honcho"}] [tool.poetry.dependencies] python = "^3.9" httpx = "^0.26.0" +python-dotenv = "^1.0.1" [tool.poetry.group.test.dependencies] pytest = "^7.4.4" diff --git a/sdk/tests/test_sync.py b/sdk/tests/test_sync.py index fc20b11..0077ff0 100644 --- a/sdk/tests/test_sync.py +++ b/sdk/tests/test_sync.py @@ -251,7 +251,9 @@ def test_paginated_messages(): created_session.create_message(is_user=False, content="Hi") page_size = 7 - get_message_response = created_session.get_messages(page=1, page_size=page_size) + get_message_response = created_session.get_messages( + page=1, page_size=page_size + ) assert get_message_response is not None assert isinstance(get_message_response, GetMessagePage) @@ -446,7 +448,9 @@ def test_collection_query(): collection = user.create_collection(col_name) # Add documents - doc1 = collection.create_document(content="The user loves puppies", metadata={}) + doc1 = collection.create_document( + content="The user loves puppies", metadata={} + ) doc2 = collection.create_document(content="The user owns a dog", metadata={}) doc3 = collection.create_document(content="The user is a doctor", metadata={}) From 9d76eb60277566c30d61474d94ca6592065ce648 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Mon, 25 Mar 2024 01:01:56 -0700 Subject: [PATCH 82/85] Basic Auth Service --- api/poetry.lock | 2 +- api/pyproject.toml | 1 + api/src/main.py | 42 +++++++++++++++++++++++++++++------------ api/src/routers/apps.py | 33 ++++++++++++++++++++++++++++---- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index 3275945..34d8d27 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -2696,4 +2696,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "aa7928d0a259d20dd9805ab5b159eebb293eac0b2e46a40d5df55f797222a8f3" +content-hash = "d2b0e968cff39082c16334498a3571c90b10fec28268f89821f13e2764990393" diff --git a/api/pyproject.toml b/api/pyproject.toml index 17c6c60..3b1619f 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -27,6 +27,7 @@ realtime = "^1.0.2" psycopg = {extras = ["binary"], version = "^3.1.18"} langchain = "^0.1.12" langchain-openai = "^0.0.8" +httpx = "^0.27.0" [tool.ruff.lint] # from https://docs.astral.sh/ruff/linter/#rule-selection example diff --git a/api/src/main.py b/api/src/main.py index 167d003..08e567f 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -1,10 +1,12 @@ import json import logging import os +import re import uuid from contextlib import asynccontextmanager from typing import Optional, Sequence +import httpx import sentry_sdk from fastapi import ( APIRouter, @@ -220,33 +222,49 @@ async def lifespan(app: FastAPI): add_pagination(app) USE_AUTH_SERVICE = os.getenv("USE_AUTH_SERVICE", "False").lower() == "true" +AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://localhost:8001") -# TODO make the API Token Validation Optional class BearerTokenMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): authorization: Optional[str] = request.headers.get("Authorization") if authorization: scheme, _, token = authorization.partition(" ") - if scheme.lower() == "bearer": - if token == "default": + if scheme.lower() == "bearer" and token: + id_pattern = r"\/apps\/([^\/]+)" + name_pattern = r"\/apps\/name\/([^\/]+)|\/apps\/get_or_create\/([^\/]+)" + match_id = re.search(id_pattern, request.url.path) + match_name = re.search(name_pattern, request.url.path) + payload = {"token": token} + if match_name: + payload["name"] = match_name.group(1) + elif match_id: + payload["app_id"] = match_id.group(1) + + res = httpx.get( + f"{AUTH_SERVICE_URL}/validate", + params=payload, + ) + data = res.json() + if ( + data["app_id"] or data["name"] + ): # Anything that checks app_id if True is valid return await call_next(request) + if data["token"]: + check_pattern = r"^\/apps$|^\/apps\/get_or_create" + match = re.search(check_pattern, request.url.path) + if match: + return await call_next(request) return Response(content="Invalid token.", status_code=400) - # Here you can add your token validation logic - # For example, checking token validity, expiration, etc. - # If the token is valid, you let the request pass through: else: - # If the scheme is not Bearer, you might want to either - # 1. Reject the request - # 2. Ignore and proceed with the next middleware or the request - # This example demonstrates rejecting the request: return Response( content="Invalid authentication scheme.", status_code=400 ) - # If no Authorization header is present, you can choose to reject the request or let it pass - # This example demonstrates rejecting the request: + exclude_paths = ["/docs", "/redoc", "/openapi.json"] + if request.url.path in exclude_paths: + return await call_next(request) return Response(content="Authorization header missing.", status_code=401) diff --git a/api/src/routers/apps.py b/api/src/routers/apps.py index 648c28b..d45f72f 100644 --- a/api/src/routers/apps.py +++ b/api/src/routers/apps.py @@ -1,5 +1,8 @@ +import os import uuid +from typing import Optional +import httpx from fastapi import APIRouter, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession @@ -57,8 +60,30 @@ async def create_app(request: Request, app: schemas.AppCreate, db=db): schemas.App: Created App object """ - - return await crud.create_app(db, app=app) + USE_AUTH_SERVICE = os.getenv("USE_AUTH_SERVICE", "False").lower() == "true" + if USE_AUTH_SERVICE: + AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://localhost:8001") + authorization: Optional[str] = request.headers.get("Authorization") + if authorization: + scheme, _, token = authorization.partition(" ") + if token is not None: + honcho_app = await crud.create_app(db, app=app) + if token == "default": + return honcho_app + res = httpx.put( + f"{AUTH_SERVICE_URL}/organizations", + json={ + "id": str(honcho_app.id), + "name": honcho_app.name, + "token": token, + }, + ) + data = res.json() + if data: + return honcho_app + else: + honcho_app = await crud.create_app(db, app=app) + return honcho_app @router.get("/get_or_create/{name}", response_model=schemas.App) @@ -73,9 +98,9 @@ async def get_or_create_app(request: Request, name: str, db=db): """ print("name", name) - app = await crud.get_app_by_name(db, name=name) + app = await crud.get_app_by_name(db=db, name=name) if app is None: - app = await crud.create_app(db, app=schemas.AppCreate(name=name)) + app = await create_app(request=request, db=db, app=schemas.AppCreate(name=name)) return app From 74fba337f3101a828674fac14cfa6e288d022e49 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:48:58 -0700 Subject: [PATCH 83/85] Deriver reliability and default auth revoke --- api/fly.toml | 15 --- api/src/deriver.py | 21 ++++- api/src/harvester.py | 203 ---------------------------------------- api/src/routers/apps.py | 4 +- 4 files changed, 22 insertions(+), 221 deletions(-) delete mode 100644 api/src/harvester.py diff --git a/api/fly.toml b/api/fly.toml index cf47e59..9f884c8 100644 --- a/api/fly.toml +++ b/api/fly.toml @@ -7,25 +7,10 @@ app = "honcho" kill_signal = "SIGINT" kill_timeout = "5s" -[experimental] - auto_rollback = true - -[build] - [processes] api = "python -m uvicorn src.main:app --host 0.0.0.0 --port 8000" deriver = "python -m src.deriver" -[[services]] - auto_stop_machines = false - auto_start_machines = true - min_machines_running = 1 - processes = ["deriver"] - protocol = "tcp" - [services.concurrency] - hard_limit = 250 - soft_limit = 200 - [http_service] internal_port = 8000 auto_stop_machines = false diff --git a/api/src/deriver.py b/api/src/deriver.py index 6fbb760..1149708 100644 --- a/api/src/deriver.py +++ b/api/src/deriver.py @@ -1,5 +1,6 @@ import asyncio import os +import time import uuid from typing import List @@ -15,6 +16,7 @@ from realtime.connection import Socket from sqlalchemy import select from sqlalchemy.orm import selectinload +from websockets.exceptions import ConnectionClosedError from . import crud, models, schemas from .db import SessionLocal @@ -32,7 +34,7 @@ SUPABASE_ID = os.getenv("SUPABASE_ID") SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY") -llm = ChatOpenAI(model_name="gpt-4") +llm = ChatOpenAI(model_name="gpt-3.5") output_parser = NumberedListOutputParser() SYSTEM_DERIVE_FACTS = load_prompt( @@ -200,9 +202,26 @@ async def check_dups( return new_facts +# def listen_to_websocket(url): +# while True: +# try: +# s = Socket(url) +# s.connect() +# channel = s.set_channel("realtime:public:messages") +# channel.join().on( +# "INSERT", lambda payload: asyncio.create_task(callback(payload)) +# ) + +# s.listen() +# except ConnectionClosedError: +# print("Connection closed, attempting to reconnect...") +# time.sleep(5) + + if __name__ == "__main__": URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={SUPABASE_API_KEY}&vsn=1.0.0" # URL = f"ws://127.0.0.1:54321/realtime/v1/websocket?apikey={SUPABASE_API_KEY}" # For local Supabase + # listen_to_websocket(URL) s = Socket(URL) s.connect() diff --git a/api/src/harvester.py b/api/src/harvester.py deleted file mode 100644 index 7be78bc..0000000 --- a/api/src/harvester.py +++ /dev/null @@ -1,203 +0,0 @@ -import asyncio -import os -import uuid -from typing import List - -from dotenv import load_dotenv -from langchain_core.output_parsers import NumberedListOutputParser -from langchain_core.prompts import ( - ChatPromptTemplate, - SystemMessagePromptTemplate, - load_prompt, -) -from langchain_openai import ChatOpenAI -from realtime.connection import Socket -from sqlalchemy import select -from sqlalchemy.orm import selectinload - -from . import crud, models, schemas -from .db import SessionLocal - -load_dotenv() - -SUPABASE_ID = os.getenv("SUPABASE_ID") -SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY") - -llm = ChatOpenAI(model_name="gpt-4") -output_parser = NumberedListOutputParser() - -SYSTEM_DERIVE_FACTS = load_prompt( - os.path.join(os.path.dirname(__file__), "prompts/derive_facts.yaml") -) -SYSTEM_CHECK_DUPS = load_prompt( - os.path.join(os.path.dirname(__file__), "prompts/check_dup_facts.yaml") -) - -system_check_dups: SystemMessagePromptTemplate = SystemMessagePromptTemplate( - prompt=SYSTEM_CHECK_DUPS -) - -system_derive_facts: SystemMessagePromptTemplate = SystemMessagePromptTemplate( - prompt=SYSTEM_DERIVE_FACTS -) - - -async def callback(payload): - # print(payload["record"]["is_user"]) - # print(type(payload["record"]["is_user"])) - if payload["record"]["is_user"]: # Check if the message is from a user - session_id = payload["record"]["session_id"] - message_id = payload["record"]["id"] - content = payload["record"]["content"] - - # Example of querying for a user_id based on session_id, adjust according to your schema - session: models.Session - user_id: uuid.UUID - app_id: uuid.UUID - async with SessionLocal() as db: - stmt = ( - select(models.Session) - .join(models.Session.messages) - .where(models.Message.id == message_id) - .where(models.Session.id == session_id) - .options(selectinload(models.Session.user)) - ) - result = await db.execute(stmt) - session = result.scalars().one() - user = session.user - user_id = user.id - app_id = user.app_id - collection: models.Collection - async with SessionLocal() as db: - collection = await crud.get_collection_by_name( - db, app_id, user_id, "honcho" - ) - if collection is None: - collection_create = schemas.CollectionCreate(name="honcho", metadata={}) - collection = await crud.create_collection( - db, - collection=collection_create, - app_id=app_id, - user_id=user_id, - ) - collection_id = collection.id - await process_user_message( - content, app_id, user_id, session_id, collection_id, message_id - ) - return - - -async def process_user_message( - content: str, - app_id: uuid.UUID, - user_id: uuid.UUID, - session_id: uuid.UUID, - collection_id: uuid.UUID, - message_id: uuid.UUID, -): - # TODO get messages for the session - async with SessionLocal() as db: - messages_stmt = await crud.get_messages( - db=db, app_id=app_id, user_id=user_id, session_id=session_id, reverse=True - ) - messages_stmt = messages_stmt.limit(10) - response = await db.execute(messages_stmt) - messages = response.scalars().all() - messages = messages[::-1] - contents = [m.content for m in messages] - # print(contents) - - facts = await derive_facts(messages, content) - print("===================") - print(f"DERIVED FACTS: {facts}") - print("===================") - new_facts = await check_dups(app_id, user_id, collection_id, facts) - - print("===================") - print(f"CHECKED FOR DUPLICATES: {new_facts}") - print("===================") - - for fact in new_facts: - create_document = schemas.DocumentCreate(content=fact) - async with SessionLocal() as db: - doc = await crud.create_document( - db, - document=create_document, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - ) - print(f"Returned Document: {doc}") - # doc = crud.create_document(content=fact) - # for fact in new_facts: - # session.create_metamessage( - # message=user_message, metamessage_type="fact", content=fact - # ) - # print(f"Created fact: {fact}") - - -async def derive_facts(chat_history, input: str) -> List[str]: - """Derive facts from the user input""" - - fact_derivation = ChatPromptTemplate.from_messages([system_derive_facts]) - chain = fact_derivation | llm - response = await chain.ainvoke( - { - "chat_history": [ - ( - "user: " + message.content - if message.is_user - else "ai: " + message.content - ) - for message in chat_history - ], - "user_input": input, - } - ) - facts = output_parser.parse(response.content) - - return facts - - -async def check_dups( - app_id: uuid.UUID, user_id: uuid.UUID, collection_id: uuid.UUID, facts: List[str] -): - """Check that we're not storing duplicate facts""" - - check_duplication = ChatPromptTemplate.from_messages([system_check_dups]) - query = " ".join(facts) - result = None - async with SessionLocal() as db: - result = await crud.query_documents( - db=db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - query=query, - top_k=10, - ) - # result = collection.query(query=query, top_k=10) - existing_facts = [document.content for document in result] - print("===================") - print(f"Existing Facts {existing_facts}") - print("===================") - if len(existing_facts) == 0: - return facts - chain = check_duplication | llm - response = await chain.ainvoke({"existing_facts": existing_facts, "facts": facts}) - new_facts = output_parser.parse(response.content) - print("===================") - print(f"New Facts {facts}") - print("===================") - return new_facts - - -if __name__ == "__main__": - URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={SUPABASE_API_KEY}&vsn=1.0.0" - # URL = f"ws://127.0.0.1:54321/realtime/v1/websocket?apikey={SUPABASE_API_KEY}" # For local Supabase - s = Socket(URL) - s.connect() - - channel = s.set_channel("realtime:public:messages") - channel.join().on("INSERT", lambda payload: asyncio.create_task(callback(payload))) - s.listen() diff --git a/api/src/routers/apps.py b/api/src/routers/apps.py index d45f72f..e9d3df6 100644 --- a/api/src/routers/apps.py +++ b/api/src/routers/apps.py @@ -68,8 +68,8 @@ async def create_app(request: Request, app: schemas.AppCreate, db=db): scheme, _, token = authorization.partition(" ") if token is not None: honcho_app = await crud.create_app(db, app=app) - if token == "default": - return honcho_app + # if token == "default": + # return honcho_app res = httpx.put( f"{AUTH_SERVICE_URL}/organizations", json={ From e4e3147456e7777eff208410b9266df39778f706 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:55:31 -0700 Subject: [PATCH 84/85] .env.template --- api/.env.template | 1 + 1 file changed, 1 insertion(+) diff --git a/api/.env.template b/api/.env.template index 7940192..4cf2a18 100644 --- a/api/.env.template +++ b/api/.env.template @@ -11,6 +11,7 @@ SENTRY_ENABLED=false # Set to true to enable Sentry logging and tracing # Auth USE_AUTH_SERVICE=false +AUTH_SERVICE_URL=http://localhost:8001 ## Sentry SENTRY_DSN= From b0fdf24aab4cd9733aa77b41273d5a9d598e9d89 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:17:51 -0700 Subject: [PATCH 85/85] 0.0.7 Updates --- README.md | 2 +- api/CHANGELOG.md | 6 ++++++ api/pyproject.toml | 2 +- sdk/CHANGELOG.md | 6 ++++++ sdk/pyproject.toml | 2 +- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c020cd..f911cc3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # 🫡 Honcho -![Static Badge](https://img.shields.io/badge/Version-0.0.6-blue) +![Static Badge](https://img.shields.io/badge/Version-0.0.7-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) ![GitHub Repo stars](https://img.shields.io/github/stars/plastic-labs/honcho) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index aa75c81..734bbba 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.7] — 2024-04-01 + +### Added + +* Authentication Middleware Interface + ## [0.0.6] — 2024-03-21 ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index 3b1619f..268072b 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho" -version = "0.0.6" +version = "0.0.7" description = "Honcho Server" authors = ["Plastic Labs "] readme = "README.md" diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 397baf0..f5f1b57 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.0.7] — 2024-04-01 + +### Changed + +* Langchain conversion utility names + ## [0.0.6] — 2024-03-21 ### Added diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 5403326..8c07174 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "honcho-ai" -version = "0.0.6" +version = "0.0.7" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] license = "AGPL-3.0"