chore: linter fixes
This commit is contained in:
parent
84c9f81273
commit
4cdf7222d2
|
|
@ -32,6 +32,7 @@ dev-dependencies = [
|
|||
"coverage>=7.6.0",
|
||||
"interrogate>=1.7.0",
|
||||
"py-spy>=0.3.14",
|
||||
"ruff>=0.11.2",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
|
|
@ -52,7 +53,7 @@ select = [
|
|||
]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.ruff.flake8-bugbear]
|
||||
[tool.ruff.lint.flake8-bugbear]
|
||||
extend-immutable-calls = ["fastapi.Depends"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
|
|
|||
275
src/crud.py
275
src/crud.py
|
|
@ -1,5 +1,5 @@
|
|||
from collections.abc import Sequence
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
|
@ -12,9 +12,9 @@ from sqlalchemy.types import BigInteger
|
|||
|
||||
from . import models, schemas
|
||||
from .exceptions import (
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
ConflictException,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -31,14 +31,14 @@ openai_client = OpenAI()
|
|||
async def get_app(db: AsyncSession, app_id: str) -> models.App:
|
||||
"""
|
||||
Get an app by its ID.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
|
||||
|
||||
Returns:
|
||||
The app if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the app does not exist
|
||||
"""
|
||||
|
|
@ -54,14 +54,14 @@ async def get_app(db: AsyncSession, app_id: str) -> models.App:
|
|||
async def get_app_by_name(db: AsyncSession, name: str) -> models.App:
|
||||
"""
|
||||
Get an app by its name.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
name: Name of the app
|
||||
|
||||
|
||||
Returns:
|
||||
The app if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the app does not exist
|
||||
"""
|
||||
|
|
@ -81,14 +81,14 @@ async def get_app_by_name(db: AsyncSession, name: str) -> models.App:
|
|||
async def create_app(db: AsyncSession, app: schemas.AppCreate) -> models.App:
|
||||
"""
|
||||
Create a new app.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app: App creation schema
|
||||
|
||||
|
||||
Returns:
|
||||
The created app
|
||||
|
||||
|
||||
Raises:
|
||||
ConflictException: If an app with the same name already exists
|
||||
"""
|
||||
|
|
@ -109,21 +109,21 @@ async def update_app(
|
|||
) -> models.App:
|
||||
"""
|
||||
Update an app.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
app: App update schema
|
||||
|
||||
|
||||
Returns:
|
||||
The updated app
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the app does not exist
|
||||
"""
|
||||
try:
|
||||
honcho_app = await get_app(db, app_id)
|
||||
|
||||
|
||||
if app.name is not None:
|
||||
honcho_app.name = app.name
|
||||
if app.metadata is not None:
|
||||
|
|
@ -135,7 +135,9 @@ async def update_app(
|
|||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
logger.error(f"IntegrityError updating app {app_id}: {str(e)}")
|
||||
raise ConflictException("App update failed - unique constraint violation") from e
|
||||
raise ConflictException(
|
||||
"App update failed - unique constraint violation"
|
||||
) from e
|
||||
|
||||
|
||||
# def delete_app(db: AsyncSession, app_id: str) -> bool:
|
||||
|
|
@ -157,15 +159,15 @@ async def create_user(
|
|||
) -> models.User:
|
||||
"""
|
||||
Create a new user.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user: User creation schema
|
||||
|
||||
|
||||
Returns:
|
||||
The created user
|
||||
|
||||
|
||||
Raises:
|
||||
ConflictException: If a user with the same name already exists in this app
|
||||
"""
|
||||
|
|
@ -185,20 +187,18 @@ async def create_user(
|
|||
raise ConflictException("User with this name already exists") from e
|
||||
|
||||
|
||||
async def get_user(
|
||||
db: AsyncSession, app_id: str, user_id: str
|
||||
) -> models.User:
|
||||
async def get_user(db: AsyncSession, app_id: str, user_id: str) -> models.User:
|
||||
"""
|
||||
Get a user by app ID and user ID.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user_id: Public ID of the user
|
||||
|
||||
|
||||
Returns:
|
||||
The user if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the user does not exist
|
||||
"""
|
||||
|
|
@ -215,20 +215,18 @@ async def get_user(
|
|||
return user
|
||||
|
||||
|
||||
async def get_user_by_name(
|
||||
db: AsyncSession, app_id: str, name: str
|
||||
) -> models.User:
|
||||
async def get_user_by_name(db: AsyncSession, app_id: str, name: str) -> models.User:
|
||||
"""
|
||||
Get a user by app ID and name.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
name: Name of the user
|
||||
|
||||
|
||||
Returns:
|
||||
The user if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the user does not exist
|
||||
"""
|
||||
|
|
@ -269,16 +267,16 @@ async def update_user(
|
|||
) -> models.User:
|
||||
"""
|
||||
Update a user.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user_id: Public ID of the user
|
||||
user: User update schema
|
||||
|
||||
|
||||
Returns:
|
||||
The updated user
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the user does not exist
|
||||
ValidationException: If the update data is invalid
|
||||
|
|
@ -287,7 +285,7 @@ async def update_user(
|
|||
try:
|
||||
# get_user will raise ResourceNotFoundException if not found
|
||||
honcho_user = await get_user(db, app_id, user_id)
|
||||
|
||||
|
||||
if user.name is not None:
|
||||
honcho_user.name = user.name
|
||||
if user.metadata is not None:
|
||||
|
|
@ -299,7 +297,9 @@ async def update_user(
|
|||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
logger.warning(f"User update failed due to integrity error: {str(e)}")
|
||||
raise ConflictException("User update failed - unique constraint violation") from e
|
||||
raise ConflictException(
|
||||
"User update failed - unique constraint violation"
|
||||
) from e
|
||||
|
||||
|
||||
# def delete_user(db: AsyncSession, app_id: str, user_id: str) -> bool:
|
||||
|
|
@ -371,23 +371,23 @@ async def create_session(
|
|||
) -> models.Session:
|
||||
"""
|
||||
Create a new session for a user.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
session: Session creation schema
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
|
||||
|
||||
Returns:
|
||||
The created session
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the user does not exist
|
||||
"""
|
||||
try:
|
||||
# This will raise ResourceNotFoundException if user not found
|
||||
honcho_user = await get_user(db, app_id=app_id, user_id=user_id)
|
||||
|
||||
|
||||
honcho_session = models.Session(
|
||||
user_id=user_id,
|
||||
h_metadata=session.metadata,
|
||||
|
|
@ -411,17 +411,17 @@ async def update_session(
|
|||
) -> models.Session:
|
||||
"""
|
||||
Update a session.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
session: Session update schema
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
session_id: ID of the session
|
||||
|
||||
|
||||
Returns:
|
||||
The updated session
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the session does not exist or doesn't belong to the user
|
||||
"""
|
||||
|
|
@ -431,10 +431,12 @@ async def update_session(
|
|||
if honcho_session is None:
|
||||
logger.warning(f"Session {session_id} not found for user {user_id}")
|
||||
raise ResourceNotFoundException("Session not found or does not belong to user")
|
||||
|
||||
if session.metadata is not None: # Need to explicitly be there won't make it empty by default
|
||||
|
||||
if (
|
||||
session.metadata is not None
|
||||
): # Need to explicitly be there won't make it empty by default
|
||||
honcho_session.h_metadata = session.metadata
|
||||
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"Session {session_id} updated successfully")
|
||||
return honcho_session
|
||||
|
|
@ -445,16 +447,16 @@ async def delete_session(
|
|||
) -> bool:
|
||||
"""
|
||||
Mark a session as inactive (soft delete).
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
session_id: ID of the session
|
||||
|
||||
|
||||
Returns:
|
||||
True if the session was deleted successfully
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the session does not exist or doesn't belong to the user
|
||||
"""
|
||||
|
|
@ -467,11 +469,11 @@ async def delete_session(
|
|||
)
|
||||
result = await db.execute(stmt)
|
||||
honcho_session = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if honcho_session is None:
|
||||
logger.warning(f"Session {session_id} not found for user {user_id}")
|
||||
raise ResourceNotFoundException("Session not found or does not belong to user")
|
||||
|
||||
|
||||
honcho_session.is_active = False
|
||||
await db.commit()
|
||||
logger.info(f"Session {session_id} marked as inactive")
|
||||
|
|
@ -750,8 +752,10 @@ async def create_metamessage(
|
|||
# Validate user exists
|
||||
user = await get_user(db, app_id=app_id, user_id=metamessage.user_id)
|
||||
if user is None:
|
||||
raise ResourceNotFoundException(f"User with ID '{metamessage.user_id}' not found")
|
||||
|
||||
raise ResourceNotFoundException(
|
||||
f"User with ID '{metamessage.user_id}' not found"
|
||||
)
|
||||
|
||||
# Initialize metamessage data
|
||||
metamessage_data = {
|
||||
"user_id": metamessage.user_id,
|
||||
|
|
@ -759,16 +763,21 @@ async def create_metamessage(
|
|||
"content": metamessage.content,
|
||||
"h_metadata": metamessage.metadata,
|
||||
}
|
||||
|
||||
|
||||
# Validate session_id if provided
|
||||
if metamessage.session_id is not None:
|
||||
session = await get_session(
|
||||
db, app_id=app_id, user_id=metamessage.user_id, session_id=metamessage.session_id
|
||||
db,
|
||||
app_id=app_id,
|
||||
user_id=metamessage.user_id,
|
||||
session_id=metamessage.session_id,
|
||||
)
|
||||
if session is None:
|
||||
raise ResourceNotFoundException(f"Session not found or does not belong to user")
|
||||
raise ResourceNotFoundException(
|
||||
"Session not found or does not belong to user"
|
||||
)
|
||||
metamessage_data["session_id"] = metamessage.session_id
|
||||
|
||||
|
||||
# Validate message_id if provided
|
||||
if metamessage.message_id is not None:
|
||||
message = await get_message(
|
||||
|
|
@ -779,12 +788,14 @@ async def create_metamessage(
|
|||
message_id=metamessage.message_id,
|
||||
)
|
||||
if message is None:
|
||||
raise ResourceNotFoundException(f"Message not found or does not belong to session")
|
||||
raise ResourceNotFoundException(
|
||||
"Message not found or does not belong to session"
|
||||
)
|
||||
metamessage_data["message_id"] = metamessage.message_id
|
||||
elif metamessage.message_id is not None:
|
||||
# If message_id provided but no session_id, that's an error
|
||||
raise ValidationException("Cannot specify message_id without session_id")
|
||||
|
||||
|
||||
# Create metamessage
|
||||
honcho_metamessage = models.Metamessage(**metamessage_data)
|
||||
db.add(honcho_metamessage)
|
||||
|
|
@ -853,15 +864,15 @@ async def get_metamessage(
|
|||
.where(models.User.public_id == user_id)
|
||||
.where(models.Metamessage.public_id == metamessage_id)
|
||||
)
|
||||
|
||||
|
||||
# Add session filter if provided
|
||||
if session_id is not None:
|
||||
stmt = stmt.where(models.Metamessage.session_id == session_id)
|
||||
|
||||
|
||||
# Add message filter if provided
|
||||
if message_id is not None:
|
||||
stmt = stmt.where(models.Metamessage.message_id == message_id)
|
||||
|
||||
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
|
@ -881,10 +892,12 @@ async def update_metamessage(
|
|||
session_id=metamessage.session_id,
|
||||
message_id=metamessage.message_id,
|
||||
)
|
||||
|
||||
|
||||
if honcho_metamessage is None:
|
||||
raise ResourceNotFoundException("Metamessage not found or does not belong to user")
|
||||
|
||||
raise ResourceNotFoundException(
|
||||
"Metamessage not found or does not belong to user"
|
||||
)
|
||||
|
||||
# Validate the consistency of relationships if they're being changed
|
||||
# If we're setting message_id, we must have a session_id
|
||||
if metamessage.message_id is not None and metamessage.session_id is None:
|
||||
|
|
@ -892,7 +905,7 @@ async def update_metamessage(
|
|||
metamessage.session_id = honcho_metamessage.session_id
|
||||
if metamessage.session_id is None:
|
||||
raise ValidationException("Cannot specify message_id without session_id")
|
||||
|
||||
|
||||
# If we're updating session_id and message_id, validate they belong together
|
||||
if metamessage.session_id is not None and metamessage.message_id is not None:
|
||||
message = await get_message(
|
||||
|
|
@ -903,21 +916,23 @@ async def update_metamessage(
|
|||
message_id=metamessage.message_id,
|
||||
)
|
||||
if message is None:
|
||||
raise ResourceNotFoundException("Message not found or doesn't belong to session")
|
||||
|
||||
raise ResourceNotFoundException(
|
||||
"Message not found or doesn't belong to session"
|
||||
)
|
||||
|
||||
# Update fields
|
||||
if metamessage.session_id is not None:
|
||||
honcho_metamessage.session_id = metamessage.session_id
|
||||
|
||||
|
||||
if metamessage.message_id is not None:
|
||||
honcho_metamessage.message_id = metamessage.message_id
|
||||
|
||||
|
||||
if metamessage.metadata is not None:
|
||||
honcho_metamessage.h_metadata = metamessage.metadata
|
||||
|
||||
|
||||
if metamessage.metamessage_type is not None:
|
||||
honcho_metamessage.metamessage_type = metamessage.metamessage_type
|
||||
|
||||
|
||||
await db.commit()
|
||||
return honcho_metamessage
|
||||
|
||||
|
|
@ -960,16 +975,16 @@ async def get_collection_by_id(
|
|||
) -> models.Collection:
|
||||
"""
|
||||
Get a collection by ID for a specific user and app.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user_id: Public ID of the user
|
||||
collection_id: Public ID of the collection
|
||||
|
||||
|
||||
Returns:
|
||||
The collection if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the collection does not exist
|
||||
"""
|
||||
|
|
@ -983,8 +998,12 @@ async def get_collection_by_id(
|
|||
result = await db.execute(stmt)
|
||||
collection = result.scalar_one_or_none()
|
||||
if collection is None:
|
||||
logger.warning(f"Collection with ID '{collection_id}' not found for user {user_id}")
|
||||
raise ResourceNotFoundException(f"Collection not found or does not belong to user")
|
||||
logger.warning(
|
||||
f"Collection with ID '{collection_id}' not found for user {user_id}"
|
||||
)
|
||||
raise ResourceNotFoundException(
|
||||
"Collection not found or does not belong to user"
|
||||
)
|
||||
return collection
|
||||
|
||||
|
||||
|
|
@ -993,16 +1012,16 @@ async def get_collection_by_name(
|
|||
) -> models.Collection:
|
||||
"""
|
||||
Get a collection by name for a specific user and app.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user_id: Public ID of the user
|
||||
name: Name of the collection
|
||||
|
||||
|
||||
Returns:
|
||||
The collection if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the collection does not exist
|
||||
"""
|
||||
|
|
@ -1029,16 +1048,16 @@ async def create_collection(
|
|||
) -> models.Collection:
|
||||
"""
|
||||
Create a new collection for a user.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
collection: Collection creation schema
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
|
||||
|
||||
Returns:
|
||||
The created collection
|
||||
|
||||
|
||||
Raises:
|
||||
ConflictException: If a collection with the same name already exists for this user
|
||||
ValidationException: If the collection configuration is invalid
|
||||
|
|
@ -1047,12 +1066,16 @@ async def create_collection(
|
|||
try:
|
||||
# This will raise ResourceNotFoundException if user not found
|
||||
await get_user(db, app_id=app_id, user_id=user_id)
|
||||
|
||||
|
||||
# Check for reserved names
|
||||
if collection.name == "honcho":
|
||||
logger.warning(f"Attempted to create collection with reserved name 'honcho' for user {user_id}")
|
||||
raise ValidationException("Invalid collection configuration - 'honcho' is a reserved name")
|
||||
|
||||
logger.warning(
|
||||
f"Attempted to create collection with reserved name 'honcho' for user {user_id}"
|
||||
)
|
||||
raise ValidationException(
|
||||
"Invalid collection configuration - 'honcho' is a reserved name"
|
||||
)
|
||||
|
||||
honcho_collection = models.Collection(
|
||||
user_id=user_id,
|
||||
name=collection.name,
|
||||
|
|
@ -1060,12 +1083,16 @@ async def create_collection(
|
|||
)
|
||||
db.add(honcho_collection)
|
||||
await db.commit()
|
||||
logger.info(f"Collection '{collection.name}' created successfully for user {user_id}")
|
||||
logger.info(
|
||||
f"Collection '{collection.name}' created successfully for user {user_id}"
|
||||
)
|
||||
return honcho_collection
|
||||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
logger.warning(f"Failed to create collection - integrity error: {str(e)}")
|
||||
raise ConflictException(f"Collection with name '{collection.name}' already exists") from e
|
||||
raise ConflictException(
|
||||
f"Collection with name '{collection.name}' already exists"
|
||||
) from e
|
||||
|
||||
|
||||
async def update_collection(
|
||||
|
|
@ -1077,17 +1104,17 @@ async def update_collection(
|
|||
) -> models.Collection:
|
||||
"""
|
||||
Update a collection.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
collection: Collection update schema
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
collection_id: ID of the collection
|
||||
|
||||
|
||||
Returns:
|
||||
The updated collection
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the collection does not exist
|
||||
ValidationException: If the update data is invalid
|
||||
|
|
@ -1096,25 +1123,33 @@ async def update_collection(
|
|||
try:
|
||||
# Validate input
|
||||
if collection.name is None and collection.metadata is None:
|
||||
logger.warning(f"Collection update attempted with no fields provided for collection {collection_id}")
|
||||
raise ValidationException("Invalid collection configuration - at least one field must be provided")
|
||||
|
||||
logger.warning(
|
||||
f"Collection update attempted with no fields provided for collection {collection_id}"
|
||||
)
|
||||
raise ValidationException(
|
||||
"Invalid collection configuration - at least one field must be provided"
|
||||
)
|
||||
|
||||
# This will raise ResourceNotFoundException if not found
|
||||
honcho_collection = await get_collection_by_id(
|
||||
db, app_id=app_id, user_id=user_id, collection_id=collection_id
|
||||
)
|
||||
|
||||
|
||||
# Check for reserved names if name is being updated
|
||||
if collection.name == "honcho":
|
||||
logger.warning(f"Attempted to rename collection to reserved name 'honcho' for user {user_id}")
|
||||
raise ValidationException("Invalid collection configuration - 'honcho' is a reserved name")
|
||||
|
||||
logger.warning(
|
||||
f"Attempted to rename collection to reserved name 'honcho' for user {user_id}"
|
||||
)
|
||||
raise ValidationException(
|
||||
"Invalid collection configuration - 'honcho' is a reserved name"
|
||||
)
|
||||
|
||||
if collection.metadata is not None:
|
||||
honcho_collection.h_metadata = collection.metadata
|
||||
|
||||
|
||||
if collection.name is not None:
|
||||
honcho_collection.name = collection.name
|
||||
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"Collection {collection_id} updated successfully")
|
||||
return honcho_collection
|
||||
|
|
@ -1130,16 +1165,16 @@ async def delete_collection(
|
|||
"""
|
||||
Delete a Collection and all documents associated with it. Takes advantage of
|
||||
the orm cascade feature.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
collection_id: ID of the collection
|
||||
|
||||
|
||||
Returns:
|
||||
True if the collection was deleted successfully
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the collection does not exist
|
||||
"""
|
||||
|
|
@ -1148,7 +1183,7 @@ async def delete_collection(
|
|||
honcho_collection = await get_collection_by_id(
|
||||
db, app_id=app_id, user_id=user_id, collection_id=collection_id
|
||||
)
|
||||
|
||||
|
||||
await db.delete(honcho_collection)
|
||||
await db.commit()
|
||||
logger.info(f"Collection {collection_id} deleted successfully")
|
||||
|
|
@ -1206,17 +1241,17 @@ async def get_document(
|
|||
) -> models.Document:
|
||||
"""
|
||||
Get a document by ID.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
app_id: Public ID of the app
|
||||
user_id: Public ID of the user
|
||||
collection_id: Public ID of the collection
|
||||
document_id: Public ID of the document
|
||||
|
||||
|
||||
Returns:
|
||||
The document if found
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the document does not exist
|
||||
"""
|
||||
|
|
@ -1236,7 +1271,9 @@ async def get_document(
|
|||
result = await db.execute(stmt)
|
||||
document = result.scalar_one_or_none()
|
||||
if document is None:
|
||||
logger.warning(f"Document with ID '{document_id}' not found in collection {collection_id}")
|
||||
logger.warning(
|
||||
f"Document with ID '{document_id}' not found in collection {collection_id}"
|
||||
)
|
||||
raise ResourceNotFoundException(f"Document with ID '{document_id}' not found")
|
||||
return document
|
||||
|
||||
|
|
@ -1284,17 +1321,17 @@ async def create_document(
|
|||
) -> models.Document:
|
||||
"""
|
||||
Embed text as a vector and create a document.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
document: Document creation schema
|
||||
app_id: ID of the app
|
||||
user_id: ID of the user
|
||||
collection_id: ID of the collection
|
||||
|
||||
|
||||
Returns:
|
||||
The created document
|
||||
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the collection does not exist
|
||||
ValidationException: If the document data is invalid
|
||||
|
|
@ -1304,9 +1341,11 @@ async def create_document(
|
|||
await get_collection_by_id(
|
||||
db, app_id=app_id, collection_id=collection_id, user_id=user_id
|
||||
)
|
||||
|
||||
|
||||
if not document.content:
|
||||
logger.warning(f"Attempted to create document with empty content in collection {collection_id}")
|
||||
logger.warning(
|
||||
f"Attempted to create document with empty content in collection {collection_id}"
|
||||
)
|
||||
raise ValidationException("Document content cannot be empty")
|
||||
|
||||
response = openai_client.embeddings.create(
|
||||
|
|
@ -1326,9 +1365,13 @@ async def create_document(
|
|||
logger.info(f"Document created successfully in collection {collection_id}")
|
||||
return honcho_document
|
||||
except Exception as e:
|
||||
if not isinstance(e, ResourceNotFoundException) and not isinstance(e, ValidationException):
|
||||
if not isinstance(e, ResourceNotFoundException) and not isinstance(
|
||||
e, ValidationException
|
||||
):
|
||||
await db.rollback()
|
||||
logger.error(f"Error creating document in collection {collection_id}: {str(e)}")
|
||||
logger.error(
|
||||
f"Error creating document in collection {collection_id}: {str(e)}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from .. import models
|
||||
from ..exceptions import ResourceNotFoundException, ValidationException
|
||||
from ..exceptions import ValidationException
|
||||
from .tom import get_tom_inference, get_user_representation
|
||||
|
||||
# Configure logging
|
||||
|
|
@ -67,22 +67,29 @@ async def get_chat_history(db, session_id, message_id) -> str:
|
|||
async def process_item(db: AsyncSession, payload: dict):
|
||||
"""
|
||||
Process a queue item based on whether it's a user or AI message.
|
||||
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
payload: Message payload from the queue
|
||||
|
||||
|
||||
Raises:
|
||||
ValidationException: If the payload is missing required fields
|
||||
"""
|
||||
try:
|
||||
# Validate required fields
|
||||
required_fields = ["content", "app_id", "user_id", "session_id", "message_id", "is_user"]
|
||||
required_fields = [
|
||||
"content",
|
||||
"app_id",
|
||||
"user_id",
|
||||
"session_id",
|
||||
"message_id",
|
||||
"is_user",
|
||||
]
|
||||
for field in required_fields:
|
||||
if field not in payload:
|
||||
logger.error(f"Missing required field in payload: {field}")
|
||||
raise ValidationException(f"Missing required field in payload: {field}")
|
||||
|
||||
|
||||
processing_args = [
|
||||
payload["content"],
|
||||
payload["app_id"],
|
||||
|
|
@ -91,16 +98,18 @@ async def process_item(db: AsyncSession, payload: dict):
|
|||
payload["message_id"],
|
||||
db,
|
||||
]
|
||||
|
||||
|
||||
if payload["is_user"]:
|
||||
logger.info(f"Processing user message: {payload['message_id']}")
|
||||
await process_user_message(*processing_args)
|
||||
else:
|
||||
logger.info(f"Processing AI message: {payload['message_id']}")
|
||||
await process_ai_message(*processing_args)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing message {payload.get('message_id', 'unknown')}: {str(e)}")
|
||||
logger.error(
|
||||
f"Error processing message {payload.get('message_id', 'unknown')}: {str(e)}"
|
||||
)
|
||||
if os.getenv("SENTRY_ENABLED", "False").lower() == "true":
|
||||
sentry_sdk.capture_exception(e)
|
||||
raise
|
||||
|
|
@ -183,7 +192,9 @@ async def process_user_message(
|
|||
existing_representation.content if existing_representation else "None"
|
||||
)
|
||||
logger.info(f"User {user_id}: Existing Representation retrieved")
|
||||
logger.debug(f"User {user_id}: Existing Representation: {existing_representation_content}")
|
||||
logger.debug(
|
||||
f"User {user_id}: Existing Representation: {existing_representation_content}"
|
||||
)
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ class QueueManager:
|
|||
self.shutdown_event.set()
|
||||
|
||||
if self.active_tasks:
|
||||
logger.info(f"Waiting for {len(self.active_tasks)} active tasks to complete...")
|
||||
logger.info(
|
||||
f"Waiting for {len(self.active_tasks)} active tasks to complete..."
|
||||
)
|
||||
await asyncio.gather(*self.active_tasks, return_exceptions=True)
|
||||
|
||||
async def cleanup(self):
|
||||
|
|
@ -86,7 +88,9 @@ class QueueManager:
|
|||
async with SessionLocal() as db:
|
||||
await db.execute(
|
||||
delete(models.ActiveQueueSession).where(
|
||||
models.ActiveQueueSession.session_id.in_(self.owned_sessions)
|
||||
models.ActiveQueueSession.session_id.in_(
|
||||
self.owned_sessions
|
||||
)
|
||||
)
|
||||
)
|
||||
await db.commit()
|
||||
|
|
@ -193,11 +197,16 @@ class QueueManager:
|
|||
if not message:
|
||||
break
|
||||
try:
|
||||
logger.info(f"Processing message {message.id} from session {session_id}")
|
||||
logger.info(
|
||||
f"Processing message {message.id} from session {session_id}"
|
||||
)
|
||||
await process_item(db, payload=message.payload)
|
||||
logger.info(f"Successfully processed message {message.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing message {message.id}: {str(e)}", exc_info=True)
|
||||
logger.error(
|
||||
f"Error processing message {message.id}: {str(e)}",
|
||||
exc_info=True,
|
||||
)
|
||||
if os.getenv("SENTRY_ENABLED", "False").lower() == "true":
|
||||
sentry_sdk.capture_exception(e)
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -1,30 +1,47 @@
|
|||
from .conversational import get_tom_inference_conversational, get_user_representation_conversational
|
||||
from .single_prompt import get_tom_inference_single_prompt, get_user_representation_single_prompt
|
||||
from .conversational import (
|
||||
get_tom_inference_conversational,
|
||||
get_user_representation_conversational,
|
||||
)
|
||||
from .single_prompt import (
|
||||
get_tom_inference_single_prompt,
|
||||
get_user_representation_single_prompt,
|
||||
)
|
||||
|
||||
async def get_tom_inference(chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs
|
||||
) -> str:
|
||||
|
||||
async def get_tom_inference(
|
||||
chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs,
|
||||
) -> str:
|
||||
if method == "conversational":
|
||||
return await get_tom_inference_conversational(chat_history, session_id, user_representation, **kwargs)
|
||||
return await get_tom_inference_conversational(
|
||||
chat_history, session_id, user_representation, **kwargs
|
||||
)
|
||||
elif method == "single_prompt":
|
||||
return await get_tom_inference_single_prompt(chat_history, session_id, user_representation, **kwargs)
|
||||
return await get_tom_inference_single_prompt(
|
||||
chat_history, session_id, user_representation, **kwargs
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
|
||||
|
||||
async def get_user_representation(chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
tom_inference: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs
|
||||
) -> str:
|
||||
async def get_user_representation(
|
||||
chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
tom_inference: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs,
|
||||
) -> str:
|
||||
if method == "conversational":
|
||||
return await get_user_representation_conversational(chat_history, session_id, user_representation, tom_inference, **kwargs)
|
||||
return await get_user_representation_conversational(
|
||||
chat_history, session_id, user_representation, tom_inference, **kwargs
|
||||
)
|
||||
elif method == "single_prompt":
|
||||
return await get_user_representation_single_prompt(chat_history, session_id, user_representation, tom_inference, **kwargs)
|
||||
return await get_user_representation_single_prompt(
|
||||
chat_history, session_id, user_representation, tom_inference, **kwargs
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
"""
|
||||
Custom exceptions for the Honcho application.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class HonchoException(Exception):
|
||||
"""Base exception for all Honcho-specific errors."""
|
||||
|
||||
status_code = 500
|
||||
detail = "An unexpected error occurred"
|
||||
|
||||
|
||||
def __init__(self, detail: Optional[str] = None, status_code: Optional[int] = None):
|
||||
self.detail = detail or self.detail
|
||||
self.status_code = status_code or self.status_code
|
||||
|
|
@ -17,29 +19,34 @@ class HonchoException(Exception):
|
|||
|
||||
class ResourceNotFoundException(HonchoException):
|
||||
"""Exception raised when a requested resource is not found."""
|
||||
|
||||
status_code = 404
|
||||
detail = "Resource not found"
|
||||
|
||||
|
||||
class ValidationException(HonchoException):
|
||||
"""Exception raised when validation fails."""
|
||||
|
||||
status_code = 422
|
||||
detail = "Validation error"
|
||||
|
||||
|
||||
class ConflictException(HonchoException):
|
||||
"""Exception raised when there's a resource conflict."""
|
||||
|
||||
status_code = 409
|
||||
detail = "Resource conflict"
|
||||
|
||||
|
||||
class AuthenticationException(HonchoException):
|
||||
"""Exception raised when authentication fails."""
|
||||
|
||||
status_code = 401
|
||||
detail = "Authentication failed"
|
||||
|
||||
|
||||
class AuthorizationException(HonchoException):
|
||||
"""Exception raised when authorization fails."""
|
||||
|
||||
status_code = 403
|
||||
detail = "Not authorized to access this resource"
|
||||
detail = "Not authorized to access this resource"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from src.routers import (
|
|||
users,
|
||||
)
|
||||
|
||||
from .db import engine, scaffold_db
|
||||
from .db import engine
|
||||
|
||||
|
||||
def get_log_level(env_var="LOG_LEVEL", default="INFO"):
|
||||
|
|
|
|||
|
|
@ -151,11 +151,9 @@ class Metamessage(Base):
|
|||
)
|
||||
metamessage_type: Mapped[str] = mapped_column(TEXT, index=True)
|
||||
content: Mapped[str] = mapped_column(TEXT)
|
||||
|
||||
|
||||
# Foreign keys - message_id is now optional
|
||||
user_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("users.public_id"), index=True
|
||||
)
|
||||
user_id: Mapped[str] = mapped_column(ForeignKey("users.public_id"), index=True)
|
||||
session_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("sessions.public_id"), index=True, nullable=True
|
||||
)
|
||||
|
|
@ -167,7 +165,7 @@ class Metamessage(Base):
|
|||
user = relationship("User", back_populates="metamessages")
|
||||
session = relationship("Session", back_populates="metamessages")
|
||||
message = relationship("Message", back_populates="metamessages")
|
||||
|
||||
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), index=True, default=func.now()
|
||||
)
|
||||
|
|
@ -182,8 +180,8 @@ class Metamessage(Base):
|
|||
),
|
||||
# Added constraints to ensure consistency
|
||||
CheckConstraint(
|
||||
"(message_id IS NULL) OR (session_id IS NOT NULL)",
|
||||
name="message_requires_session"
|
||||
"(message_id IS NULL) OR (session_id IS NOT NULL)",
|
||||
name="message_requires_session",
|
||||
),
|
||||
# Keep existing index
|
||||
Index(
|
||||
|
|
@ -192,7 +190,7 @@ class Metamessage(Base):
|
|||
text("id DESC"),
|
||||
postgresql_include=["public_id", "message_id", "created_at"],
|
||||
),
|
||||
# Add new indices for user, session, and message lookups
|
||||
# Indices for user, session, and message lookups
|
||||
Index(
|
||||
"idx_metamessages_user_lookup",
|
||||
"user_id",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import json
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
|
|
@ -40,11 +39,15 @@ async def get_documents(
|
|||
filter=options.filter,
|
||||
reverse=reverse,
|
||||
)
|
||||
|
||||
|
||||
return await paginate(db, documents_query)
|
||||
except ValueError as e:
|
||||
logger.warning(f"Failed to get documents for collection {collection_id}: {str(e)}")
|
||||
raise ResourceNotFoundException("Collection not found or does not belong to user") from e
|
||||
logger.warning(
|
||||
f"Failed to get documents for collection {collection_id}: {str(e)}"
|
||||
)
|
||||
raise ResourceNotFoundException(
|
||||
"Collection not found or does not belong to user"
|
||||
) from e
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
@ -84,7 +87,7 @@ async def query_documents(
|
|||
filter = options.filter
|
||||
if options.filter == {}:
|
||||
filter = None
|
||||
|
||||
|
||||
documents = await crud.query_documents(
|
||||
db=db,
|
||||
app_id=app_id,
|
||||
|
|
@ -94,11 +97,13 @@ async def query_documents(
|
|||
filter=filter,
|
||||
top_k=top_k,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"Query documents successful for collection {collection_id}")
|
||||
return documents
|
||||
except ValueError as e:
|
||||
logger.error(f"Error querying documents in collection {collection_id}: {str(e)}")
|
||||
logger.error(
|
||||
f"Error querying documents in collection {collection_id}: {str(e)}"
|
||||
)
|
||||
raise ValidationException("Error querying documents") from e
|
||||
|
||||
|
||||
|
|
@ -122,8 +127,12 @@ async def create_document(
|
|||
logger.info(f"Document created successfully in collection {collection_id}")
|
||||
return document_obj
|
||||
except ValueError as e:
|
||||
logger.warning(f"Failed to create document in collection {collection_id}: {str(e)}")
|
||||
raise ResourceNotFoundException("Collection not found or does not belong to user") from e
|
||||
logger.warning(
|
||||
f"Failed to create document in collection {collection_id}: {str(e)}"
|
||||
)
|
||||
raise ResourceNotFoundException(
|
||||
"Collection not found or does not belong to user"
|
||||
) from e
|
||||
|
||||
|
||||
@router.put(
|
||||
|
|
@ -140,9 +149,11 @@ async def update_document(
|
|||
):
|
||||
"""Update the content and/or the metadata of a Document"""
|
||||
if document.content is None and document.metadata is None:
|
||||
logger.warning(f"Document update attempted with empty content and metadata for document {document_id}")
|
||||
logger.warning(
|
||||
f"Document update attempted with empty content and metadata for document {document_id}"
|
||||
)
|
||||
raise ValidationException("Content and metadata cannot both be None")
|
||||
|
||||
|
||||
try:
|
||||
updated_document = await crud.update_document(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ async def create_metamessage(
|
|||
try:
|
||||
# Set the user_id from the URL parameters
|
||||
metamessage.user_id = user_id
|
||||
|
||||
|
||||
metamessage_obj = await crud.create_metamessage(
|
||||
db,
|
||||
metamessage=metamessage,
|
||||
|
|
@ -56,9 +56,9 @@ async def get_metamessages(
|
|||
):
|
||||
"""
|
||||
Get metamessages with flexible filtering.
|
||||
|
||||
|
||||
- Filter by user only: No additional parameters needed
|
||||
- Filter by session: Provide session_id
|
||||
- Filter by session: Provide session_id
|
||||
- Filter by message: Provide message_id (and session_id)
|
||||
- Filter by type: Provide metamessage_type
|
||||
- Filter by metadata: Provide filter object
|
||||
|
|
@ -75,7 +75,7 @@ async def get_metamessages(
|
|||
filter=options.filter,
|
||||
reverse=reverse,
|
||||
)
|
||||
|
||||
|
||||
return await paginate(db, metamessages_query)
|
||||
except (ResourceNotFoundException, ValidationException) as e:
|
||||
logger.warning(f"Failed to get metamessages: {str(e)}")
|
||||
|
|
@ -101,7 +101,9 @@ async def get_metamessage(
|
|||
)
|
||||
if honcho_metamessage is None:
|
||||
logger.warning(f"Metamessage {metamessage_id} not found")
|
||||
raise ResourceNotFoundException(f"Metamessage with ID {metamessage_id} not found")
|
||||
raise ResourceNotFoundException(
|
||||
f"Metamessage with ID {metamessage_id} not found"
|
||||
)
|
||||
return honcho_metamessage
|
||||
|
||||
|
||||
|
|
@ -119,7 +121,7 @@ async def update_metamessage(
|
|||
"""Update a metamessage's metadata, type, or relationships"""
|
||||
# Ensure user_id from URL path is used
|
||||
metamessage.user_id = user_id
|
||||
|
||||
|
||||
try:
|
||||
updated_metamessage = await crud.update_metamessage(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ class MetamessageGet(MetamessageBase):
|
|||
|
||||
class MetamessageUpdate(MetamessageBase):
|
||||
user_id: str | None = None # Will be set from URL parameter in endpoint
|
||||
session_id: str | None = None
|
||||
session_id: str | None = None
|
||||
message_id: str | None = None
|
||||
metamessage_type: str | None = None
|
||||
metadata: dict | None = None
|
||||
|
|
@ -323,24 +323,26 @@ class Document(DocumentBase):
|
|||
class AgentQuery(BaseModel):
|
||||
queries: str | list[str]
|
||||
|
||||
@field_validator('queries')
|
||||
@field_validator("queries")
|
||||
def validate_queries(cls, v):
|
||||
MAX_STRING_LENGTH = 10000
|
||||
MAX_LIST_LENGTH = 25
|
||||
if isinstance(v, str):
|
||||
if len(v) > MAX_STRING_LENGTH:
|
||||
raise ValueError('Query too long')
|
||||
raise ValueError("Query too long")
|
||||
elif isinstance(v, list):
|
||||
if len(v) > MAX_LIST_LENGTH:
|
||||
raise ValueError('Too many queries')
|
||||
raise ValueError("Too many queries")
|
||||
if any(len(q) > MAX_STRING_LENGTH for q in v):
|
||||
raise ValueError('One or more queries too long')
|
||||
raise ValueError("One or more queries too long")
|
||||
return v
|
||||
|
||||
|
||||
class AgentChat(BaseModel):
|
||||
content: str
|
||||
|
||||
|
||||
class MessageBatchCreate(BaseModel):
|
||||
"""Schema for batch message creation with a max of 100 messages"""
|
||||
|
||||
messages: list[MessageCreate] = Field(..., max_length=100)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from fastapi import Request
|
|||
from fastapi.responses import JSONResponse
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.engine.url import make_url
|
||||
from sqlalchemy.exc import OperationalError, ProgrammingError
|
||||
from sqlalchemy_utils import create_database, database_exists, drop_database
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_get_or_create_app(client):
|
|||
response = client.get(f"/v1/apps/name/{name}")
|
||||
assert response.status_code == 404
|
||||
assert "detail" in response.json()
|
||||
|
||||
|
||||
# This should create the app
|
||||
response = client.get(f"/v1/apps/get_or_create/{name}")
|
||||
assert response.status_code == 200
|
||||
|
|
@ -48,23 +48,23 @@ def test_get_or_create_app(client):
|
|||
|
||||
def test_get_or_create_existing_app(client):
|
||||
name = str(generate_nanoid())
|
||||
|
||||
|
||||
# App doesn't exist yet
|
||||
response = client.get(f"/v1/apps/name/{name}")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# Create the app
|
||||
response = client.post(
|
||||
"/v1/apps", json={"name": name, "metadata": {"key": "value"}}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
app1 = response.json()
|
||||
|
||||
|
||||
# Now get_or_create should find the existing app
|
||||
response = client.get(f"/v1/apps/get_or_create/{name}")
|
||||
assert response.status_code == 200
|
||||
app2 = response.json()
|
||||
|
||||
|
||||
# Both should be the same app
|
||||
assert app1["name"] == app2["name"]
|
||||
assert app1["id"] == app2["id"]
|
||||
|
|
@ -107,10 +107,10 @@ def test_create_duplicate_app_name(client):
|
|||
name = str(generate_nanoid())
|
||||
response = client.post("/v1/apps", json={"name": name})
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
# Try to create another app with the same name
|
||||
response = client.post("/v1/apps", json={"name": name})
|
||||
|
||||
|
||||
# Should get a ConflictException with 409 status
|
||||
assert response.status_code == 409
|
||||
data = response.json()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ def test_update_document_validation_error(client, sample_data):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
collection = response.json()
|
||||
|
||||
|
||||
# Create a document
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents",
|
||||
|
|
@ -18,13 +18,13 @@ def test_update_document_validation_error(client, sample_data):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
document = response.json()
|
||||
|
||||
|
||||
# Try to update the document with empty content and metadata
|
||||
response = client.put(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}",
|
||||
json={"content": None, "metadata": None},
|
||||
)
|
||||
|
||||
|
||||
# Should get a ValidationException with 422 status
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ async def test_update_message(client, db_session, sample_data):
|
|||
data = response.json()
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_message_empty_metadata(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
|
|
@ -148,8 +149,9 @@ async def test_create_batch_messages(client, db_session, sample_data):
|
|||
{
|
||||
"content": f"Test message {i}",
|
||||
"is_user": i % 2 == 0, # Alternating user/non-user messages
|
||||
"metadata": {"batch_index": i}
|
||||
} for i in range(3)
|
||||
"metadata": {"batch_index": i},
|
||||
}
|
||||
for i in range(3)
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -157,13 +159,13 @@ async def test_create_batch_messages(client, db_session, sample_data):
|
|||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/batch",
|
||||
json=test_messages,
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
|
||||
# Verify the response contains all messages
|
||||
assert len(data) == 3
|
||||
|
||||
|
||||
# Verify messages are in the correct order and have correct content
|
||||
for i, message in enumerate(data):
|
||||
assert message["content"] == f"Test message {i}"
|
||||
|
|
@ -195,8 +197,9 @@ async def test_create_batch_messages_limit(client, db_session, sample_data):
|
|||
{
|
||||
"content": f"Test message {i}",
|
||||
"is_user": i % 2 == 0,
|
||||
"metadata": {"batch_index": i}
|
||||
} for i in range(101) # 101 messages
|
||||
"metadata": {"batch_index": i},
|
||||
}
|
||||
for i in range(101) # 101 messages
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +207,7 @@ async def test_create_batch_messages_limit(client, db_session, sample_data):
|
|||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/batch",
|
||||
json=test_messages,
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 422 # Validation error
|
||||
data = response.json()
|
||||
assert "messages" in data["detail"][0]["loc"] # Error should mention messages field
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ async def test_get_metamessages_by_session(client, db_session, sample_data):
|
|||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
|
||||
|
||||
# Create metamessages for the same session
|
||||
test_metamessage_1 = models.Metamessage(
|
||||
user_id=test_user.public_id,
|
||||
|
|
@ -129,7 +129,7 @@ async def test_get_metamessages_by_session(client, db_session, sample_data):
|
|||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/list",
|
||||
json={
|
||||
"session_id": str(test_session.public_id),
|
||||
"metamessage_type": "test_type"
|
||||
"metamessage_type": "test_type",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ async def test_get_metamessage_by_user(client, db_session, sample_data):
|
|||
@pytest.mark.asyncio
|
||||
async def test_create_user_level_metamessage(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
|
||||
|
||||
# Create a user-level metamessage (no session or message)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages",
|
||||
|
|
@ -281,7 +281,7 @@ async def test_update_metamessage(client, db_session, sample_data):
|
|||
json={
|
||||
"user_id": str(test_user.public_id),
|
||||
"metadata": {"new_key": "new_value"},
|
||||
"metamessage_type": "updated_type"
|
||||
"metamessage_type": "updated_type",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import pytest
|
||||
from nanoid import generate as generate_nanoid
|
||||
|
||||
|
||||
|
|
@ -32,8 +31,7 @@ def test_user_validations_api(client, sample_data):
|
|||
|
||||
# Test name too short
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users",
|
||||
json={"name": "", "metadata": {}}
|
||||
f"/v1/apps/{test_app.public_id}/users", json={"name": "", "metadata": {}}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -43,8 +41,7 @@ def test_user_validations_api(client, sample_data):
|
|||
|
||||
# Test name too long
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users",
|
||||
json={"name": "a" * 101, "metadata": {}}
|
||||
f"/v1/apps/{test_app.public_id}/users", json={"name": "a" * 101, "metadata": {}}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -58,18 +55,14 @@ def test_message_validations_api(client, sample_data):
|
|||
# Create a test session first
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test content too long
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages",
|
||||
json={
|
||||
"content": "a" * 50001,
|
||||
"is_user": True,
|
||||
"metadata": {}
|
||||
}
|
||||
json={"content": "a" * 50001, "is_user": True, "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -80,11 +73,7 @@ def test_message_validations_api(client, sample_data):
|
|||
# Test invalid is_user type
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages",
|
||||
json={
|
||||
"content": "test",
|
||||
"is_user": "not a bool",
|
||||
"metadata": {}
|
||||
}
|
||||
json={"content": "test", "is_user": "not a bool", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -98,7 +87,7 @@ def test_collection_validations_api(client, sample_data):
|
|||
# Test name too short
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": "", "metadata": {}}
|
||||
json={"name": "", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -109,7 +98,7 @@ def test_collection_validations_api(client, sample_data):
|
|||
# Test name too long
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": "a" * 101, "metadata": {}}
|
||||
json={"name": "a" * 101, "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -120,7 +109,7 @@ def test_collection_validations_api(client, sample_data):
|
|||
# Test 'honcho' name restriction
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": "honcho", "metadata": {}}
|
||||
json={"name": "honcho", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -134,14 +123,14 @@ def test_document_validations_api(client, sample_data):
|
|||
# Create a collection first
|
||||
collection_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": str(generate_nanoid()), "metadata": {}}
|
||||
json={"name": str(generate_nanoid()), "metadata": {}},
|
||||
)
|
||||
collection_id = collection_response.json()["id"]
|
||||
|
||||
# Test content too short
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents",
|
||||
json={"content": "", "metadata": {}}
|
||||
json={"content": "", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -152,7 +141,7 @@ def test_document_validations_api(client, sample_data):
|
|||
# Test content too long
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents",
|
||||
json={"content": "a" * 100001, "metadata": {}}
|
||||
json={"content": "a" * 100001, "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -166,14 +155,14 @@ def test_document_query_validations_api(client, sample_data):
|
|||
# Create a collection first
|
||||
collection_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": str(generate_nanoid()), "metadata": {}}
|
||||
json={"name": str(generate_nanoid()), "metadata": {}},
|
||||
)
|
||||
collection_id = collection_response.json()["id"]
|
||||
|
||||
# Test query too short
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query",
|
||||
json={"query": "", "top_k": 5}
|
||||
json={"query": "", "top_k": 5},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -184,7 +173,7 @@ def test_document_query_validations_api(client, sample_data):
|
|||
# Test query too long
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query",
|
||||
json={"query": "a" * 1001, "top_k": 5}
|
||||
json={"query": "a" * 1001, "top_k": 5},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -195,7 +184,7 @@ def test_document_query_validations_api(client, sample_data):
|
|||
# Test top_k too small
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query",
|
||||
json={"query": "test", "top_k": 0}
|
||||
json={"query": "test", "top_k": 0},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -206,7 +195,7 @@ def test_document_query_validations_api(client, sample_data):
|
|||
# Test top_k too large
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query",
|
||||
json={"query": "test", "top_k": 51}
|
||||
json={"query": "test", "top_k": 51},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -220,23 +209,19 @@ def test_message_batch_validations_api(client, sample_data):
|
|||
# Create a test session first
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test batch too large
|
||||
messages = [
|
||||
{
|
||||
"content": f"test message {i}",
|
||||
"is_user": True,
|
||||
"metadata": {}
|
||||
}
|
||||
{"content": f"test message {i}", "is_user": True, "metadata": {}}
|
||||
for i in range(101) # Create 101 messages
|
||||
]
|
||||
|
||||
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages/batch",
|
||||
json={"messages": messages}
|
||||
json={"messages": messages},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -250,13 +235,13 @@ def test_metamessage_validations_api(client, sample_data):
|
|||
# Create session and message first
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
|
||||
message_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages",
|
||||
json={"content": "test message", "is_user": True, "metadata": {}}
|
||||
json={"content": "test message", "is_user": True, "metadata": {}},
|
||||
)
|
||||
message_id = message_response.json()["id"]
|
||||
|
||||
|
|
@ -268,8 +253,8 @@ def test_metamessage_validations_api(client, sample_data):
|
|||
"content": "test content",
|
||||
"session_id": session_id,
|
||||
"message_id": message_id,
|
||||
"metadata": {}
|
||||
}
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -285,8 +270,8 @@ def test_metamessage_validations_api(client, sample_data):
|
|||
"content": "test content",
|
||||
"session_id": session_id,
|
||||
"message_id": message_id,
|
||||
"metadata": {}
|
||||
}
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -301,8 +286,8 @@ def test_metamessage_validations_api(client, sample_data):
|
|||
"metamessage_type": "test_type",
|
||||
"content": "a" * 50001,
|
||||
"message_id": message_id,
|
||||
"metadata": {}
|
||||
}
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -316,14 +301,14 @@ def test_collection_update_validations_api(client, sample_data):
|
|||
# Create a collection first
|
||||
collection_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": str(generate_nanoid()), "metadata": {}}
|
||||
json={"name": str(generate_nanoid()), "metadata": {}},
|
||||
)
|
||||
collection_id = collection_response.json()["id"]
|
||||
|
||||
# Test honcho name in update
|
||||
response = client.put(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}",
|
||||
json={"name": "honcho", "metadata": {}}
|
||||
json={"name": "honcho", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -337,26 +322,26 @@ def test_document_update_validations_api(client, sample_data):
|
|||
# Create collection and document first
|
||||
collection_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"name": str(generate_nanoid()), "metadata": {}}
|
||||
json={"name": str(generate_nanoid()), "metadata": {}},
|
||||
)
|
||||
collection_id = collection_response.json()["id"]
|
||||
|
||||
|
||||
document_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents",
|
||||
json={"content": "test content", "metadata": {}}
|
||||
json={"content": "test content", "metadata": {}},
|
||||
)
|
||||
document_id = document_response.json()["id"]
|
||||
|
||||
# Test content too long in update
|
||||
response = client.put(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/{document_id}",
|
||||
json={"content": "a" * 100001, "metadata": {}}
|
||||
json={"content": "a" * 100001, "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
assert error["loc"] == ["body", "content"]
|
||||
assert error["msg"] == "String should have at most 100000 characters"
|
||||
assert error["type"] == "string_too_long"
|
||||
assert error["type"] == "string_too_long"
|
||||
|
||||
|
||||
def test_session_validations_api(client, sample_data):
|
||||
|
|
@ -364,14 +349,14 @@ def test_session_validations_api(client, sample_data):
|
|||
# Create a test session first
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test invalid metadata type
|
||||
response = client.put(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}",
|
||||
json={"metadata": "not a dict"}
|
||||
json={"metadata": "not a dict"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -381,7 +366,7 @@ def test_session_validations_api(client, sample_data):
|
|||
# Test empty update
|
||||
response = client.put(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}",
|
||||
json={}
|
||||
json={},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -391,21 +376,21 @@ def test_agent_query_validations_api(client, sample_data):
|
|||
# Create a session first since agent queries are likely session-based
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test valid string query (under 10000 chars)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": "a" * 9999}
|
||||
json={"queries": "a" * 9999},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test string query too long (over 10000 chars)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": "a" * 10001}
|
||||
json={"queries": "a" * 10001},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -416,14 +401,14 @@ def test_agent_query_validations_api(client, sample_data):
|
|||
# Test valid list query (under 25 items, each under 10000 chars)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": ["a" * 9999 for _ in range(25)]}
|
||||
json={"queries": ["a" * 9999 for _ in range(25)]},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test list too long (over 25 items)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": ["test" for _ in range(26)]}
|
||||
json={"queries": ["test" for _ in range(26)]},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -433,7 +418,7 @@ def test_agent_query_validations_api(client, sample_data):
|
|||
# Test list item too long (item over 10000 chars)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": ["a" * 10001]}
|
||||
json={"queries": ["a" * 10001]},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -444,7 +429,7 @@ def test_agent_query_validations_api(client, sample_data):
|
|||
# Test that strings over 20 chars are allowed
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat",
|
||||
json={"queries": "a" * 100} # 100 chars should be fine
|
||||
json={"queries": "a" * 100}, # 100 chars should be fine
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
@ -453,14 +438,14 @@ def test_required_field_validations_api(client, sample_data):
|
|||
test_app, test_user = sample_data
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test missing required content in message
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages",
|
||||
json={"is_user": True, "metadata": {}}
|
||||
json={"is_user": True, "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -470,7 +455,7 @@ def test_required_field_validations_api(client, sample_data):
|
|||
# Test missing required is_user in message
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages",
|
||||
json={"content": "test", "metadata": {}}
|
||||
json={"content": "test", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -480,7 +465,7 @@ def test_required_field_validations_api(client, sample_data):
|
|||
# Test missing required name in collection
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -493,14 +478,14 @@ def test_filter_validations_api(client, sample_data):
|
|||
# Create a session first
|
||||
session_response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions",
|
||||
json={"metadata": {}}
|
||||
json={"metadata": {}},
|
||||
)
|
||||
session_id = session_response.json()["id"]
|
||||
|
||||
# Test invalid filter type in message list (at session level)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages/list",
|
||||
json={"filter": "not a dict"}
|
||||
json={"filter": "not a dict"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
@ -510,7 +495,7 @@ def test_filter_validations_api(client, sample_data):
|
|||
# Test invalid filter type in collection list (at user level)
|
||||
response = client.post(
|
||||
f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/list",
|
||||
json={"filter": "not a dict"}
|
||||
json={"filter": "not a dict"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from src.schemas import (
|
||||
AppCreate,
|
||||
UserCreate,
|
||||
MessageCreate,
|
||||
MetamessageCreate,
|
||||
CollectionCreate,
|
||||
DocumentCreate,
|
||||
DocumentQuery,
|
||||
MessageBatchCreate,
|
||||
MessageCreate,
|
||||
MetamessageCreate,
|
||||
UserCreate,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -200,9 +201,7 @@ class TestDocumentQueryValidations:
|
|||
class TestMessageBatchValidations:
|
||||
def test_valid_message_batch(self):
|
||||
batch = MessageBatchCreate(
|
||||
messages=[
|
||||
MessageCreate(content="test", is_user=True, metadata={})
|
||||
]
|
||||
messages=[MessageCreate(content="test", is_user=True, metadata={})]
|
||||
)
|
||||
assert len(batch.messages) == 1
|
||||
|
||||
|
|
@ -215,4 +214,4 @@ class TestMessageBatchValidations:
|
|||
]
|
||||
)
|
||||
error_dict = exc_info.value.errors()[0]
|
||||
assert error_dict["type"] == "too_long"
|
||||
assert error_dict["type"] == "too_long"
|
||||
|
|
|
|||
27
uv.lock
27
uv.lock
|
|
@ -443,6 +443,7 @@ dev = [
|
|||
{ name = "py-spy" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "ruff" },
|
||||
{ name = "sqlalchemy-utils" },
|
||||
]
|
||||
|
||||
|
|
@ -472,6 +473,7 @@ dev = [
|
|||
{ name = "py-spy", specifier = ">=0.3.14" },
|
||||
{ name = "pytest", specifier = ">=8.2.2" },
|
||||
{ name = "pytest-asyncio", specifier = ">=0.23.7" },
|
||||
{ name = "ruff", specifier = ">=0.11.2" },
|
||||
{ name = "sqlalchemy-utils", specifier = ">=0.41.2" },
|
||||
]
|
||||
|
||||
|
|
@ -1260,6 +1262,31 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/ac/3c/3b66696fc8a6c980674851108d7d57fbcbfedbefb3d8b61a64166dc9b18e/rich_toolkit-0.12.0-py3-none-any.whl", hash = "sha256:a2da4416384410ae871e890db7edf8623e1f5e983341dbbc8cc03603ce24f0ab", size = 13012 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.22.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue