v0.0.2 (#15)
* 🧪 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 --------- Co-authored-by: hyusap <paulayush@gmail.com> Co-authored-by: vintro <vince@plasticlabs.ai>
This commit is contained in:
parent
e7204f101d
commit
8539b87804
|
|
@ -1,6 +1,6 @@
|
|||
api/**/*.db
|
||||
|
||||
.vscode/
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../sdk"
|
||||
},
|
||||
{
|
||||
"path": "../api"
|
||||
},
|
||||
{
|
||||
"path": "../example/cli"
|
||||
},
|
||||
{
|
||||
"path": "../example/discord"
|
||||
},
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"files.exclude": {
|
||||
"sdk": true,
|
||||
"api": true,
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# Honcho
|
||||

|
||||

|
||||
[](https://discord.gg/plasticlabs)
|
||||

|
||||

|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "honcho"
|
||||
version = "0.0.1"
|
||||
description = ""
|
||||
version = "0.0.2"
|
||||
description = "Honcho Server"
|
||||
authors = ["Plastic Labs <hello@plasticlabs.ai>"]
|
||||
readme = "README.md"
|
||||
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
147
api/src/crud.py
147
api/src/crud.py
|
|
@ -1,13 +1,14 @@
|
|||
import json
|
||||
from typing import Sequence, Optional
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, Select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
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)
|
||||
|
|
@ -18,33 +19,20 @@ 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)
|
||||
.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:
|
||||
stmt = stmt.where(models.Session.location_id == location_id)
|
||||
|
||||
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()
|
||||
# )
|
||||
|
||||
return stmt
|
||||
# return db.scalars(stmt).all()
|
||||
|
||||
def create_session(
|
||||
db: Session, app_id: str, user_id: str, session: schemas.SessionCreate
|
||||
|
|
@ -61,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)
|
||||
|
|
@ -74,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)
|
||||
|
|
@ -82,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
|
||||
|
|
@ -91,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:
|
||||
|
|
@ -109,36 +93,95 @@ def create_message(
|
|||
|
||||
|
||||
def get_messages(
|
||||
db: Session, app_id: str, user_id: str, session_id: int
|
||||
) -> Sequence[schemas.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).where(models.Message.session_id == session_id)
|
||||
return db.scalars(stmt).all()
|
||||
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)
|
||||
.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 (
|
||||
# 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)
|
||||
.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)
|
||||
|
||||
# def get_metacognitions(db: Session, message_id: int):
|
||||
# return (
|
||||
# db.query(models.Metacognitions)
|
||||
# .filter(models.Metacognitions.message_id == message_id)
|
||||
# .all()
|
||||
# )
|
||||
)
|
||||
return db.scalars(stmt).one_or_none()
|
||||
|
||||
# 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 = 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
|
||||
|
|
|
|||
167
api/src/main.py
167
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
|
||||
|
|
@ -5,6 +6,9 @@ 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_pagination import Page, add_pagination
|
||||
from fastapi_pagination.ext.sqlalchemy import paginate
|
||||
# import uvicorn
|
||||
|
||||
from . import crud, models, schemas
|
||||
|
|
@ -17,7 +21,7 @@ app = FastAPI()
|
|||
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 +29,8 @@ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|||
app.add_middleware(SlowAPIMiddleware)
|
||||
|
||||
|
||||
add_pagination(app)
|
||||
|
||||
def get_db():
|
||||
"""FastAPI Dependency Generator for Database"""
|
||||
db = SessionLocal()
|
||||
|
|
@ -37,7 +43,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 +56,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)
|
||||
|
|
@ -70,14 +78,15 @@ 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)
|
||||
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,
|
||||
app_id: str,
|
||||
user_id: str,
|
||||
session_id: int,
|
||||
session_id: uuid.UUID,
|
||||
session: schemas.SessionUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
|
|
@ -105,7 +114,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
|
||||
|
|
@ -129,7 +138,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:
|
||||
|
|
@ -160,7 +169,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),
|
||||
):
|
||||
|
|
@ -186,13 +195,13 @@ 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,
|
||||
app_id: str,
|
||||
user_id: str,
|
||||
session_id: int,
|
||||
session_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get all messages for a session
|
||||
|
|
@ -210,40 +219,122 @@ 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")
|
||||
|
||||
@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)
|
||||
@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,
|
||||
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
|
||||
|
||||
"""
|
||||
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:
|
||||
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.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)
|
||||
|
||||
app.include_router(router)
|
||||
|
|
|
|||
|
|
@ -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 @@ from .db import Base
|
|||
|
||||
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 @@ class Session(Base):
|
|||
|
||||
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:]})"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
messages: list[Message]
|
||||
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):
|
||||
pass
|
||||
class MetamessageCreate(MetamessageBase):
|
||||
message_id: uuid.UUID
|
||||
|
||||
|
||||
class Metamessages(MetamessagesBase):
|
||||
id: int
|
||||
class Metamessage(MetamessageBase):
|
||||
id: uuid.UUID
|
||||
message_id: uuid.UUID
|
||||
created_at: datetime.datetime
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from honcho import Client as HonchoClient
|
|||
|
||||
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)
|
||||
|
|
@ -35,10 +36,10 @@ def chat():
|
|||
while True:
|
||||
user_input = input("User: ")
|
||||
if user_input == "exit":
|
||||
session.delete()
|
||||
session.close()
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ intents.message_content = True
|
|||
|
||||
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,8 +51,8 @@ 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[0].delete() if len(sessions) > 0 else None
|
||||
sessions = list(honcho.get_sessions_generator(user_id, location_id))
|
||||
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?"
|
||||
|
|
@ -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,22 +376,82 @@ 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.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]
|
||||
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 = "159adce2e4b4e405c0e57a5b9a27ddc384184dd308d6b12ddb5db6fbe81c3c6b"
|
||||
content-hash = "f31c071455001b66fe72eb743b16c64f108172c4314475b772e5cbd18942d0dc"
|
||||
|
|
@ -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]
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
BOT_TOKEN=
|
||||
OPENAI_API_KEY=
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.env
|
||||
|
||||
.venv
|
||||
|
||||
.DS_Store
|
||||
|
|
@ -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.
|
||||
|
|
@ -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].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"])
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 <vince@plasticlabs.ai>"]
|
||||
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"
|
||||
|
|
@ -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/sync_client.py")
|
||||
with open(destination_file_path, "w") as destination_file:
|
||||
destination_file.write(sync_code)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.testing.pytestArgs": ["tests"],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
from .client import Client
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,33 +1,161 @@
|
|||
import json
|
||||
from typing import Dict
|
||||
import requests
|
||||
import uuid
|
||||
from typing import Dict, Optional
|
||||
import httpx
|
||||
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"]
|
||||
self.pages = response["pages"]
|
||||
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.items = [
|
||||
AsyncSession(
|
||||
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"]
|
||||
]
|
||||
|
||||
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
|
||||
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.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"]
|
||||
]
|
||||
|
||||
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"]
|
||||
self.metamessage_type = options["metamessage_type"]
|
||||
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):
|
||||
"""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)
|
||||
|
||||
|
||||
class Client:
|
||||
|
||||
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
|
||||
self.app_id = app_id # Representing ID of the client application
|
||||
self.client = httpx.AsyncClient()
|
||||
|
||||
@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: int):
|
||||
async def get_session(self, user_id: str, 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 (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}"
|
||||
response = requests.get(url)
|
||||
response = await self.client.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return Session(
|
||||
return AsyncSession(
|
||||
client=self,
|
||||
id=data["id"],
|
||||
user_id=data["user_id"],
|
||||
|
|
@ -36,34 +164,57 @@ class Client:
|
|||
session_data=data["session_data"],
|
||||
)
|
||||
|
||||
def get_sessions(self, user_id: str, location_id: str | None = None):
|
||||
"""Return sessions associated with a user
|
||||
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 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:
|
||||
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 ""
|
||||
)
|
||||
response = await self.client.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
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):
|
||||
"""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
|
||||
|
||||
Returns:
|
||||
list[Dict]: List of Session objects
|
||||
Yields:
|
||||
AsyncSession: The Session object of the requested Session
|
||||
|
||||
"""
|
||||
url = f"{self.common_prefix}/users/{user_id}/sessions" + (
|
||||
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()
|
||||
]
|
||||
page = 1
|
||||
page_size = 50
|
||||
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.items:
|
||||
yield session
|
||||
|
||||
def create_session(
|
||||
new_sessions = await get_session_response.next()
|
||||
if not new_sessions:
|
||||
break
|
||||
|
||||
get_session_response = new_sessions
|
||||
|
||||
async def create_session(
|
||||
self, user_id: str, location_id: str = "default", session_data: Dict = {}
|
||||
):
|
||||
"""Create a session for a user
|
||||
|
|
@ -74,14 +225,15 @@ class Client:
|
|||
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}
|
||||
url = f"{self.common_prefix}/users/{user_id}/sessions"
|
||||
response = requests.post(url, json=data)
|
||||
response = await self.client.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return Session(
|
||||
return AsyncSession(
|
||||
self,
|
||||
id=data["id"],
|
||||
user_id=user_id,
|
||||
|
|
@ -91,11 +243,13 @@ class Client:
|
|||
)
|
||||
|
||||
|
||||
class Session:
|
||||
class AsyncSession:
|
||||
"""Represents a single session for a user in an app"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
id: int,
|
||||
client: AsyncClient,
|
||||
id: uuid.UUID,
|
||||
user_id: str,
|
||||
location_id: str,
|
||||
session_data: dict | str,
|
||||
|
|
@ -103,6 +257,7 @@ class Session:
|
|||
):
|
||||
"""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
|
||||
|
|
@ -114,16 +269,19 @@ class Session:
|
|||
|
||||
@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
|
||||
|
||||
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:
|
||||
|
|
@ -131,18 +289,111 @@ class Session:
|
|||
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:
|
||||
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 = requests.post(url, json=data)
|
||||
response = await self.client.post(url, json=data)
|
||||
response.raise_for_status()
|
||||
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, created_at=data["created_at"])
|
||||
|
||||
def get_messages(self):
|
||||
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=data["is_user"], content=data["content"], created_at=data["created_at"])
|
||||
|
||||
async def get_messages(self, page: int = 1, page_size: int = 50) -> 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
|
||||
|
||||
Returns:
|
||||
AsyncGetMessagePage: Page 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 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)
|
||||
while True:
|
||||
# get_session_response = self.get_sessions(user_id, location_id, page, page_size)
|
||||
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 create_metamessage(self, message: Message, metamessage_type: str, content: str):
|
||||
"""Adds a metamessage to a session and links it to a specific message
|
||||
|
||||
Args:
|
||||
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:
|
||||
Metamessage: The Metamessage object of the added metamessage
|
||||
|
||||
"""
|
||||
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 metamessage
|
||||
|
||||
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, metamessage_type: Optional[str] = None, message: Optional[Message] = None, page: int = 1, page_size: int = 50) -> AsyncGetMetamessagePage:
|
||||
"""Get all messages for a session
|
||||
|
||||
Args:
|
||||
|
|
@ -153,24 +404,51 @@ class Session:
|
|||
list[Dict]: List of Message objects
|
||||
|
||||
"""
|
||||
url = f"{self.common_prefix}/users/{self.user_id}/sessions/{self.id}/messages"
|
||||
response = requests.get(url)
|
||||
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 [
|
||||
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
|
||||
options = {
|
||||
"metamessage_type": metamessage_type,
|
||||
"message_id": message.id if message else None
|
||||
}
|
||||
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:
|
||||
session_data (Dict): The Session object containing any new metadata
|
||||
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)
|
||||
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, session_data: Dict):
|
||||
"""Update the session_data of a session
|
||||
|
||||
Args:
|
||||
session_data (Dict): The Session object containing any new session_data
|
||||
|
||||
|
||||
Returns:
|
||||
|
|
@ -178,25 +456,15 @@ class Session:
|
|||
"""
|
||||
info = {"session_data": session_data}
|
||||
url = f"{self.common_prefix}/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):
|
||||
"""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 = requests.delete(url)
|
||||
response = await self.client.delete(url)
|
||||
response.raise_for_status()
|
||||
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})"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
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):
|
||||
"""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})"
|
||||
|
|
@ -0,0 +1,470 @@
|
|||
import json
|
||||
import uuid
|
||||
from typing import Dict, Optional
|
||||
import httpx
|
||||
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"]
|
||||
self.pages = response["pages"]
|
||||
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.items = [
|
||||
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):
|
||||
"""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_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.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"]
|
||||
]
|
||||
|
||||
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"]
|
||||
self.metamessage_type = options["metamessage_type"]
|
||||
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):
|
||||
"""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)
|
||||
|
||||
|
||||
|
||||
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
|
||||
self.app_id = app_id # Representing ID of the client application
|
||||
self.client = httpx.Client()
|
||||
|
||||
@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):
|
||||
"""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)
|
||||
response.raise_for_status()
|
||||
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: Optional[str] = None, page: int = 1, page_size: int = 50):
|
||||
"""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:
|
||||
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 ""
|
||||
)
|
||||
response = self.client.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
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):
|
||||
"""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)
|
||||
while True:
|
||||
# get_session_response = self.get_sessions(user_id, location_id, page, page_size)
|
||||
for session in get_session_response.items:
|
||||
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 = {}
|
||||
):
|
||||
"""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:
|
||||
Session: The Session object of the new 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,
|
||||
id=data["id"],
|
||||
user_id=user_id,
|
||||
location_id=location_id,
|
||||
session_data=session_data,
|
||||
is_active=data["is_active"],
|
||||
)
|
||||
|
||||
|
||||
class Session:
|
||||
"""Represents a single session for a user in an app"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
id: uuid.UUID,
|
||||
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.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
|
||||
|
||||
@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):
|
||||
"""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:
|
||||
Message: 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.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=data["is_user"], content=data["content"], created_at=data["created_at"])
|
||||
|
||||
def get_messages(self, page: int = 1, page_size: int = 50) -> 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
|
||||
|
||||
Returns:
|
||||
GetMessagePage: Page 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 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)
|
||||
while True:
|
||||
# get_session_response = self.get_sessions(user_id, location_id, page, page_size)
|
||||
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 create_metamessage(self, message: Message, metamessage_type: str, content: str):
|
||||
"""Adds a metamessage to a session and links it to a specific message
|
||||
|
||||
Args:
|
||||
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:
|
||||
Metamessage: The Metamessage object of the added metamessage
|
||||
|
||||
"""
|
||||
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 metamessage
|
||||
|
||||
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, metamessage_type: Optional[str] = None, message: Optional[Message] = None, 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}/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()
|
||||
options = {
|
||||
"metamessage_type": metamessage_type,
|
||||
"message_id": message.id if message else None
|
||||
}
|
||||
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)
|
||||
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, session_data: Dict):
|
||||
"""Update the session_data of a session
|
||||
|
||||
Args:
|
||||
session_data (Dict): The Session object containing any new session_data
|
||||
|
||||
|
||||
Returns:
|
||||
boolean: Whether the session was successfully updated
|
||||
"""
|
||||
info = {"session_data": session_data}
|
||||
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
|
||||
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)
|
||||
response.raise_for_status()
|
||||
self._is_active = False
|
||||
|
||||
|
|
@ -1,9 +1,33 @@
|
|||
# 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 = [
|
||||
{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"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
|
|
@ -11,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 = [
|
||||
|
|
@ -125,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 = [
|
||||
|
|
@ -135,10 +62,70 @@ 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"
|
||||
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 = "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"
|
||||
version = "3.6"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
|
|
@ -150,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 = [
|
||||
|
|
@ -161,6 +149,7 @@ files = [
|
|||
name = "packaging"
|
||||
version = "23.2"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
|
|
@ -172,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 = [
|
||||
|
|
@ -187,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 = [
|
||||
|
|
@ -206,30 +197,41 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
|||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
description = "Python HTTP for Humans."
|
||||
name = "pytest-asyncio"
|
||||
version = "0.23.4"
|
||||
description = "Pytest support for asyncio"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||
{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]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<4"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<3"
|
||||
pytest = ">=7.0.0,<8"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[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 = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
|
|
@ -238,22 +240,18 @@ files = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.1.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
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 = [
|
||||
{file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
|
||||
{file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
|
||||
{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.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 = "0aa81b5a08a235ff5f275db8e6d0265d97fd1e350567862ee6b5afddaaf15620"
|
||||
content-hash = "6ccea662fa5a5bae88618123d5d05e0d4955c234b7e1a688d2fae2f90cd9f7f8"
|
||||
|
|
|
|||
|
|
@ -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 <hello@plasticlabs.ai>"]
|
||||
license = "AGPL-3.0"
|
||||
|
|
@ -9,10 +9,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"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,274 @@
|
|||
import pytest
|
||||
from honcho import AsyncGetSessionPage, AsyncGetMessagePage, AsyncGetMetamessagePage, AsyncSession, Message, Metamessage
|
||||
from honcho import AsyncClient as Honcho
|
||||
from uuid import uuid1
|
||||
|
||||
|
||||
@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)
|
||||
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 == {}
|
||||
|
||||
|
||||
@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)
|
||||
retrieved_sessions = response.items
|
||||
|
||||
assert len(retrieved_sessions) == 2
|
||||
assert retrieved_sessions[0].id == created_session_1.id
|
||||
assert retrieved_sessions[1].id == created_session_2.id
|
||||
|
||||
|
||||
@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)
|
||||
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"}
|
||||
|
||||
|
||||
@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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
response = await retrieved_session.get_messages()
|
||||
messages = response.items
|
||||
assert len(messages) == 2
|
||||
user_message, ai_message = messages
|
||||
assert user_message.content == "Hello"
|
||||
assert user_message.is_user is True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_paginated_sessions():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(app_id, "http://localhost:8000")
|
||||
for i in range(10):
|
||||
await client.create_session(user_id)
|
||||
|
||||
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.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, 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.items) == 2
|
||||
next_page = await final_page.next()
|
||||
assert next_page is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_paginated_sessions_generator():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(app_id, "http://localhost:8000")
|
||||
for i in range(3):
|
||||
await client.create_session(user_id)
|
||||
|
||||
gen = client.get_sessions_generator(user_id)
|
||||
# print(type(gen))
|
||||
|
||||
item = await gen.__anext__()
|
||||
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")
|
||||
for i in range(3):
|
||||
await client.create_session(user_id)
|
||||
page = 2
|
||||
page_size = 50
|
||||
get_session_response = await 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.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)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
item = await gen.__anext__()
|
||||
assert isinstance(item, Message)
|
||||
assert item.content == "Hello"
|
||||
assert item.is_user is True
|
||||
item2 = await gen.__anext__()
|
||||
assert item2 is not None
|
||||
assert item2.content == "Hi"
|
||||
assert item2.is_user is False
|
||||
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__()
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
from honcho import Client
|
||||
from uuid import uuid1
|
||||
import pytest
|
||||
|
||||
def test_session_creation_retrieval():
|
||||
app_id = str(uuid1())
|
||||
client = Client(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)
|
||||
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 == {}
|
||||
|
||||
|
||||
def test_session_multiple_retrieval():
|
||||
app_id = str(uuid1())
|
||||
client = Client(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)
|
||||
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():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Client(app_id, "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():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Client(app_id, "http://localhost:8000")
|
||||
created_session = client.create_session(user_id)
|
||||
assert created_session.is_active is True
|
||||
created_session.delete()
|
||||
assert created_session.is_active is False
|
||||
retrieved_session = client.get_session(user_id, created_session.id)
|
||||
assert retrieved_session.is_active is False
|
||||
assert retrieved_session.id == created_session.id
|
||||
|
||||
|
||||
def test_messages():
|
||||
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")
|
||||
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 is True
|
||||
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 = Client(app_id, "http://localhost:8000")
|
||||
created_session = client.create_session(user_id)
|
||||
with pytest.raises(Exception):
|
||||
for _ in range(10):
|
||||
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 = Client(app_id_1, "http://localhost:8000")
|
||||
client_2 = Client(app_id_2, "http://localhost:8000")
|
||||
created_session = client_1.create_session(user_id)
|
||||
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)
|
||||
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
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())
|
||||
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)
|
||||
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 == {}
|
||||
|
||||
|
||||
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)
|
||||
retrieved_sessions = response.items
|
||||
|
||||
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():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(app_id, "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():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(app_id, "http://localhost:8000")
|
||||
created_session = client.create_session(user_id)
|
||||
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)
|
||||
assert retrieved_session.is_active is False
|
||||
assert retrieved_session.id == created_session.id
|
||||
|
||||
|
||||
def test_messages():
|
||||
app_id = str(uuid1())
|
||||
user_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")
|
||||
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.items
|
||||
assert len(messages) == 2
|
||||
user_message, ai_message = messages
|
||||
assert user_message.content == "Hello"
|
||||
assert user_message.is_user is True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
def test_paginated_sessions():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(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.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, 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.items) == 2
|
||||
next_page = final_page.next()
|
||||
assert next_page is None
|
||||
|
||||
|
||||
def test_paginated_sessions_generator():
|
||||
app_id = str(uuid1())
|
||||
user_id = str(uuid1())
|
||||
client = Honcho(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 = Honcho(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.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)
|
||||
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, 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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue