[0.0.10] Release
* chore: Save Point * fix(db): change connection logic and remove unnecessary refreshes * fix(documents): switch to Azure embedding model * feat: Setup pytest fixtures * feat(tests): Initial test routes for tranche 1 * feat(test) tranche 2 of tests and associated bug fixes * feat(test) tranche 3 of tests and associated bug fixes * fix(tests) Address PR comments and update version and changelog
This commit is contained in:
parent
659b13a10f
commit
ddcde6bcea
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.0.10] — 2024-07-23
|
||||
|
||||
### Added
|
||||
|
||||
* Test cases for Storage API
|
||||
* Sentry tracing and profiling
|
||||
* Additional Error handling
|
||||
|
||||
### Changed
|
||||
|
||||
* Document API uses same embedding endpoint as deriver
|
||||
* CRUD operations use one less database call by removing extra refresh
|
||||
* Use database for timestampz rather than API
|
||||
* Pydantic schemas to use modern syntax
|
||||
|
||||
### Fixed
|
||||
|
||||
* Deriver queue resolution
|
||||
|
||||
|
||||
## [0.0.9] — 2024-05-16
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# 🫡 Honcho
|
||||

|
||||

|
||||
[](https://discord.gg/plasticlabs)
|
||||
[](https://arxiv.org/abs/2310.06983)
|
||||

|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
version: "3.8"
|
||||
services:
|
||||
api:
|
||||
image: honcho:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "honcho"
|
||||
version = "0.0.9"
|
||||
version = "0.0.10"
|
||||
description = "Honcho Server"
|
||||
authors = ["Plastic Labs <hello@plasticlabs.ai>"]
|
||||
readme = "README.md"
|
||||
|
|
@ -8,19 +8,16 @@ readme = "README.md"
|
|||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
fastapi = "^0.111.0"
|
||||
uvicorn = "^0.29.0"
|
||||
python-dotenv = "^1.0.0"
|
||||
sqlalchemy = "^2.0.30"
|
||||
slowapi = "^0.1.9"
|
||||
fastapi-pagination = "^0.12.24"
|
||||
pgvector = "^0.2.5"
|
||||
openai = "^1.12.0"
|
||||
sentry-sdk = "^2.3.0"
|
||||
sentry-sdk = {extras = ["fastapi", "sqlalchemy"], version = "^2.3.1"}
|
||||
greenlet = "^3.0.3"
|
||||
psycopg = {extras= ["binary"], version="^3.1.19"}
|
||||
httpx = "^0.27.0"
|
||||
uvloop = "^0.19.0"
|
||||
httptools = "^0.6.1"
|
||||
mirascope = "^0.15.1"
|
||||
opentelemetry-instrumentation-fastapi = "^0.45b0"
|
||||
opentelemetry-sdk = "^1.24.0"
|
||||
|
|
@ -28,6 +25,13 @@ opentelemetry-exporter-otlp = "^1.24.0"
|
|||
opentelemetry-instrumentation-sqlalchemy = "^0.45b0"
|
||||
opentelemetry-instrumentation-logging = "^0.45b0"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.2.2"
|
||||
sqlalchemy-utils = "^0.41.2"
|
||||
pytest-asyncio = "^0.23.7"
|
||||
coverage = "^7.6.0"
|
||||
interrogate = "^1.7.0"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# from https://docs.astral.sh/ruff/linter/#rule-selection example
|
||||
select = [
|
||||
|
|
@ -51,3 +55,6 @@ extend-immutable-calls = ["fastapi.Depends"]
|
|||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.lpytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
|
|
|
|||
64
src/crud.py
64
src/crud.py
|
|
@ -1,8 +1,9 @@
|
|||
import datetime
|
||||
import os
|
||||
import uuid
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from openai import OpenAI
|
||||
from openai import AzureOpenAI, OpenAI
|
||||
from sqlalchemy import Select, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -10,7 +11,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
# from sqlalchemy.orm import Session
|
||||
from . import models, schemas
|
||||
|
||||
openai_client = OpenAI()
|
||||
openai_client = AzureOpenAI(
|
||||
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
|
||||
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
|
||||
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
||||
)
|
||||
|
||||
########################################################
|
||||
# app methods
|
||||
|
|
@ -39,7 +44,7 @@ async def create_app(db: AsyncSession, app: schemas.AppCreate) -> models.App:
|
|||
honcho_app = models.App(name=app.name, h_metadata=app.metadata)
|
||||
db.add(honcho_app)
|
||||
await db.commit()
|
||||
await db.refresh(honcho_app)
|
||||
# await db.refresh(honcho_app)
|
||||
return honcho_app
|
||||
|
||||
|
||||
|
|
@ -50,12 +55,12 @@ async def update_app(
|
|||
if honcho_app is None:
|
||||
raise ValueError("App not found")
|
||||
if app.name is not None:
|
||||
honcho_app.content = app.name
|
||||
honcho_app.name = app.name
|
||||
if app.metadata is not None:
|
||||
honcho_app.h_metadata = app.metadata
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(honcho_app)
|
||||
# await db.refresh(honcho_app)
|
||||
return honcho_app
|
||||
|
||||
|
||||
|
|
@ -83,7 +88,7 @@ async def create_user(
|
|||
)
|
||||
db.add(honcho_user)
|
||||
await db.commit()
|
||||
await db.refresh(honcho_user)
|
||||
# await db.refresh(honcho_user)
|
||||
return honcho_user
|
||||
|
||||
|
||||
|
|
@ -139,12 +144,12 @@ async def update_user(
|
|||
if honcho_user is None:
|
||||
raise ValueError("User not found")
|
||||
if user.name is not None:
|
||||
honcho_user.content = user.name
|
||||
honcho_user.name = user.name
|
||||
if user.metadata is not None:
|
||||
honcho_user.h_metadata = user.metadata
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(honcho_user)
|
||||
# await db.refresh(honcho_user)
|
||||
return honcho_user
|
||||
|
||||
|
||||
|
|
@ -228,8 +233,18 @@ async def create_session(
|
|||
h_metadata=session.metadata,
|
||||
)
|
||||
db.add(honcho_session)
|
||||
# print("====== Testing State of ORM Object ====")
|
||||
# print(honcho_session)
|
||||
# print("=======================================")
|
||||
#
|
||||
# await db.flush()
|
||||
#
|
||||
# print("====== Testing State of ORM Object ====")
|
||||
# print(honcho_session)
|
||||
# print("=======================================")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(honcho_session)
|
||||
# await db.refresh(honcho_session)
|
||||
return honcho_session
|
||||
|
||||
|
||||
|
|
@ -250,7 +265,7 @@ async def update_session(
|
|||
): # Need to explicitly be there won't make it empty by default
|
||||
honcho_session.h_metadata = session.metadata
|
||||
await db.commit()
|
||||
await db.refresh(honcho_session)
|
||||
# await db.refresh(honcho_session)
|
||||
return honcho_session
|
||||
|
||||
|
||||
|
|
@ -300,7 +315,7 @@ async def create_message(
|
|||
db.add(honcho_message)
|
||||
await db.commit()
|
||||
# await db.refresh(honcho_message, attribute_names=["id", "content", "h_metadata"])
|
||||
await db.refresh(honcho_message)
|
||||
# await db.refresh(honcho_message)
|
||||
return honcho_message
|
||||
|
||||
|
||||
|
|
@ -372,7 +387,7 @@ async def update_message(
|
|||
): # Need to explicitly be there won't make it empty by default
|
||||
honcho_message.h_metadata = message.metadata
|
||||
await db.commit()
|
||||
await db.refresh(honcho_message)
|
||||
# await db.refresh(honcho_message)
|
||||
return honcho_message
|
||||
|
||||
|
||||
|
|
@ -388,7 +403,7 @@ async def create_metamessage(
|
|||
user_id: uuid.UUID,
|
||||
session_id: uuid.UUID,
|
||||
):
|
||||
message = get_message(
|
||||
message = await get_message(
|
||||
db,
|
||||
app_id=app_id,
|
||||
session_id=session_id,
|
||||
|
|
@ -407,7 +422,7 @@ async def create_metamessage(
|
|||
|
||||
db.add(honcho_metamessage)
|
||||
await db.commit()
|
||||
await db.refresh(honcho_metamessage)
|
||||
# await db.refresh(honcho_metamessage)
|
||||
return honcho_metamessage
|
||||
|
||||
|
||||
|
|
@ -498,7 +513,7 @@ async def update_metamessage(
|
|||
if metamessage.metamessage_type is not None:
|
||||
honcho_metamessage.metamessage_type = metamessage.metamessage_type
|
||||
await db.commit()
|
||||
await db.refresh(honcho_metamessage)
|
||||
# await db.refresh(honcho_metamessage)
|
||||
return honcho_metamessage
|
||||
|
||||
|
||||
|
|
@ -582,7 +597,7 @@ async def create_collection(
|
|||
except IntegrityError:
|
||||
await db.rollback()
|
||||
raise ValueError("Collection already exists") from None
|
||||
await db.refresh(honcho_collection)
|
||||
# await db.refresh(honcho_collection)
|
||||
return honcho_collection
|
||||
|
||||
|
||||
|
|
@ -601,12 +616,13 @@ async def update_collection(
|
|||
if collection.metadata is not None:
|
||||
honcho_collection.h_metadata = collection.metadata
|
||||
try:
|
||||
honcho_collection.name = collection.name
|
||||
await db.commit()
|
||||
if collection.name is not None:
|
||||
honcho_collection.name = collection.name
|
||||
await db.commit()
|
||||
except IntegrityError:
|
||||
await db.rollback()
|
||||
raise ValueError("Collection already exists") from None
|
||||
await db.refresh(honcho_collection)
|
||||
# await db.refresh(honcho_collection)
|
||||
return honcho_collection
|
||||
|
||||
|
||||
|
|
@ -700,7 +716,7 @@ async def query_documents(
|
|||
top_k: int = 5,
|
||||
) -> Sequence[models.Document]:
|
||||
response = openai_client.embeddings.create(
|
||||
input=query, model="text-embedding-3-small"
|
||||
input=query, model=os.getenv("AZURE_OPENAI_EMBED_DEPLOYMENT")
|
||||
)
|
||||
embedding_query = response.data[0].embedding
|
||||
stmt = (
|
||||
|
|
@ -736,7 +752,7 @@ async def create_document(
|
|||
raise ValueError("Session not found or does not belong to user")
|
||||
|
||||
response = openai_client.embeddings.create(
|
||||
input=document.content, model="text-embedding-3-small"
|
||||
input=document.content, model=os.getenv("AZURE_OPENAI_EMBED_DEPLOYMENT")
|
||||
)
|
||||
|
||||
embedding = response.data[0].embedding
|
||||
|
|
@ -749,7 +765,7 @@ async def create_document(
|
|||
)
|
||||
db.add(honcho_document)
|
||||
await db.commit()
|
||||
await db.refresh(honcho_document)
|
||||
# await db.refresh(honcho_document)
|
||||
return honcho_document
|
||||
|
||||
|
||||
|
|
@ -773,7 +789,7 @@ async def update_document(
|
|||
if document.content is not None:
|
||||
honcho_document.content = document.content
|
||||
response = openai_client.embeddings.create(
|
||||
input=document.content, model="text-embedding-3-small"
|
||||
input=document.content, model=os.getenv("AZURE_OPENAI_EMBED_DEPLOYMENT")
|
||||
)
|
||||
embedding = response.data[0].embedding
|
||||
honcho_document.embedding = embedding
|
||||
|
|
@ -782,7 +798,7 @@ async def update_document(
|
|||
if document.metadata is not None:
|
||||
honcho_document.h_metadata = document.metadata
|
||||
await db.commit()
|
||||
await db.refresh(honcho_document)
|
||||
# await db.refresh(honcho_document)
|
||||
return honcho_document
|
||||
|
||||
|
||||
|
|
|
|||
17
src/db.py
17
src/db.py
|
|
@ -1,9 +1,11 @@
|
|||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
# from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
|
@ -21,8 +23,15 @@ engine = create_async_engine(
|
|||
pool_pre_ping=True,
|
||||
)
|
||||
|
||||
SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
SessionLocal = async_sessionmaker(
|
||||
autocommit=False, autoflush=False, expire_on_commit=False, bind=engine
|
||||
)
|
||||
|
||||
table_schema = os.getenv("DATABASE_SCHEMA")
|
||||
meta = MetaData()
|
||||
if table_schema:
|
||||
meta.schema = table_schema
|
||||
Base = declarative_base(metadata=meta)
|
||||
|
||||
|
||||
def scaffold_db():
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import List
|
|||
import sentry_sdk
|
||||
import uvloop
|
||||
from dotenv import load_dotenv
|
||||
from sentry_sdk.integrations.asyncio import AsyncioIntegration
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
|
@ -23,14 +24,6 @@ from .voe import (
|
|||
|
||||
load_dotenv()
|
||||
|
||||
SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", "False").lower() == "true"
|
||||
if SENTRY_ENABLED:
|
||||
sentry_sdk.init(
|
||||
dsn=os.getenv("SENTRY_DSN"),
|
||||
enable_tracing=True,
|
||||
)
|
||||
|
||||
|
||||
# Turn of SQLAlchemy Echo logging
|
||||
logging.getLogger("sqlalchemy.engine.Engine").disabled = True
|
||||
|
||||
|
|
@ -148,7 +141,7 @@ async def process_ai_message(
|
|||
)
|
||||
|
||||
if user_prediction_thought_revision_response.content == "None":
|
||||
print(f"\033[94mModel predicted no changes to the user prediction thought")
|
||||
print("\033[94mModel predicted no changes to the user prediction thought")
|
||||
await add_metamessage(
|
||||
db,
|
||||
message_id,
|
||||
|
|
@ -178,27 +171,27 @@ async def process_ai_message(
|
|||
await db.commit()
|
||||
|
||||
# debugging
|
||||
print(f"\033[94m=================")
|
||||
print(f"\033[94mUser Prediction Thought Prompt:")
|
||||
print("\033[94m=================")
|
||||
print("\033[94mUser Prediction Thought Prompt:")
|
||||
content_lines = str(user_prediction_thought).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[94m{line}")
|
||||
print(f"\033[94mUser Prediction Thought:")
|
||||
print("\033[94mUser Prediction Thought:")
|
||||
content_lines = str(user_prediction_thought_response.content).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[94m{line}")
|
||||
print(f"\033[94m=================\033[0m")
|
||||
print("\033[94m=================\033[0m")
|
||||
|
||||
print(f"\033[95m=================")
|
||||
print(f"\033[95mUser Prediction Thought Revision:")
|
||||
print("\033[95m=================")
|
||||
print("\033[95mUser Prediction Thought Revision:")
|
||||
content_lines = str(user_prediction_thought_revision).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[95m{line}")
|
||||
print(f"\033[95mUser Prediction Thought Revision Response:")
|
||||
print("\033[95mUser Prediction Thought Revision Response:")
|
||||
content_lines = str(user_prediction_thought_revision_response.content).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[95m{line}")
|
||||
print(f"\033[95m=================\033[0m")
|
||||
print("\033[95m=================\033[0m")
|
||||
|
||||
|
||||
async def process_user_message(
|
||||
|
|
@ -222,9 +215,10 @@ async def process_user_message(
|
|||
|
||||
messages_stmt = (
|
||||
select(models.Message)
|
||||
.where(models.Message.created_at < subquery)
|
||||
.where(models.Message.session_id == session_id)
|
||||
.where(models.Message.is_user == False)
|
||||
.order_by(models.Message.created_at.desc())
|
||||
.where(models.Message.created_at < subquery)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
|
|
@ -265,31 +259,31 @@ async def process_user_message(
|
|||
voe_derive_facts_response = await voe_derive_facts.call_async()
|
||||
|
||||
# debugging
|
||||
print(f"\033[93m=================")
|
||||
print(f"\033[93mVoe Thought Prompt:")
|
||||
print("\033[93m=================")
|
||||
print("\033[93mVoe Thought Prompt:")
|
||||
content_lines = str(voe_thought).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[93m{line}")
|
||||
print(f"\033[93mVoe Thought:")
|
||||
print("\033[93mVoe Thought:")
|
||||
content_lines = str(voe_thought_response.content).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[93m{line}")
|
||||
print(f"\033[93m=================\033[0m")
|
||||
print("\033[93m=================\033[0m")
|
||||
|
||||
print(f"\033[93m=================")
|
||||
print(f"\033[93mVoe Derive Facts Prompt:")
|
||||
print("\033[93m=================")
|
||||
print("\033[93mVoe Derive Facts Prompt:")
|
||||
content_lines = str(voe_derive_facts).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[93m{line}")
|
||||
print(f"\033[93mVoe Derive Facts Response:")
|
||||
print("\033[93mVoe Derive Facts Response:")
|
||||
content_lines = str(voe_derive_facts_response.content).split("\n")
|
||||
for line in content_lines:
|
||||
print(f"\033[93m{line}")
|
||||
print(f"\033[93m=================\033[0m")
|
||||
print("\033[93m=================\033[0m")
|
||||
|
||||
facts = re.findall(r"\d+\.\s([^\n]+)", voe_derive_facts_response.content)
|
||||
print(f"\033[93m=================")
|
||||
print(f"\033[93mThe Facts Themselves:")
|
||||
print("\033[93m=================")
|
||||
print("\033[93mThe Facts Themselves:")
|
||||
print(facts)
|
||||
new_facts = await check_dups(app_id, user_id, collection_id, facts)
|
||||
|
||||
|
|
@ -305,9 +299,9 @@ async def process_user_message(
|
|||
)
|
||||
print(f"\033[93mReturned Document: {doc.content}")
|
||||
else:
|
||||
raise Exception(f"\033[91mUser Thought Prediction Revision NOT READY YET")
|
||||
raise Exception("\033[91mUser Thought Prediction Revision NOT READY YET")
|
||||
else:
|
||||
print(f"\033[91mNo AI message before this user message")
|
||||
print("\033[91mNo AI message before this user message")
|
||||
return
|
||||
|
||||
|
||||
|
|
@ -362,7 +356,7 @@ async def dequeue(semaphore: asyncio.Semaphore, queue_empty_flag: asyncio.Event)
|
|||
try:
|
||||
result = await db.execute(
|
||||
select(models.QueueItem)
|
||||
.order_by(models.QueueItem.created_at)
|
||||
.order_by(models.QueueItem.id)
|
||||
.where(models.QueueItem.processed == False)
|
||||
.with_for_update(skip_locked=True)
|
||||
.limit(1)
|
||||
|
|
@ -402,6 +396,17 @@ async def polling_loop(semaphore: asyncio.Semaphore, queue_empty_flag: asyncio.E
|
|||
|
||||
|
||||
async def main():
|
||||
SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", "False").lower() == "true"
|
||||
if SENTRY_ENABLED:
|
||||
sentry_sdk.init(
|
||||
dsn=os.getenv("SENTRY_DSN"),
|
||||
enable_tracing=True,
|
||||
traces_sample_rate=1.0,
|
||||
profiles_sample_rate=1.0,
|
||||
integrations=[
|
||||
AsyncioIntegration(),
|
||||
],
|
||||
)
|
||||
semaphore = asyncio.Semaphore(1) # Limit to 5 concurrent dequeuing operations
|
||||
queue_empty_flag = asyncio.Event() # Event to signal when the queue is empty
|
||||
await polling_loop(semaphore, queue_empty_flag)
|
||||
|
|
|
|||
13
src/main.py
13
src/main.py
|
|
@ -180,6 +180,8 @@ if SENTRY_ENABLED:
|
|||
sentry_sdk.init(
|
||||
dsn=os.getenv("SENTRY_DSN"),
|
||||
enable_tracing=True,
|
||||
traces_sample_rate=0.4,
|
||||
profiles_sample_rate=0.4,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -223,17 +225,6 @@ app.add_middleware(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
origins = ["http://localhost", "http://127.0.0.1:8000", "https://demo.honcho.dev"]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
if OPENTELEMTRY_ENABLED:
|
||||
FastAPIInstrumentor().instrument_app(app)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import datetime
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
|
@ -17,6 +16,7 @@ from sqlalchemy import (
|
|||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from .db import Base
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ class App(Base):
|
|||
name: Mapped[str] = mapped_column(String(512), index=True, unique=True)
|
||||
users = relationship("User", back_populates="app")
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
# Add any additional fields for an app here
|
||||
|
|
@ -49,7 +49,7 @@ class User(Base):
|
|||
name: Mapped[str] = mapped_column(String(512), index=True)
|
||||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
app_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("apps.id"), index=True)
|
||||
app = relationship("App", back_populates="users")
|
||||
|
|
@ -71,14 +71,14 @@ class Session(Base):
|
|||
is_active: Mapped[bool] = mapped_column(default=True)
|
||||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
messages = relationship("Message", back_populates="session")
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), index=True)
|
||||
user = relationship("User", back_populates="sessions")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Session(id={self.id}, app_id={self.app_id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})"
|
||||
return f"Session(id={self.id}, user_id={self.user_id}, location_id={self.location_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})"
|
||||
|
||||
|
||||
class Message(Base):
|
||||
|
|
@ -92,7 +92,7 @@ class Message(Base):
|
|||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
session = relationship("Session", back_populates="messages")
|
||||
metamessages = relationship("Metamessage", back_populates="message")
|
||||
|
|
@ -112,7 +112,7 @@ class Metamessage(Base):
|
|||
|
||||
message = relationship("Message", back_populates="metamessages")
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class Collection(Base):
|
|||
)
|
||||
name: Mapped[str] = mapped_column(String(512), index=True)
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={})
|
||||
documents = relationship(
|
||||
|
|
@ -150,7 +150,7 @@ class Document(Base):
|
|||
content: Mapped[str] = mapped_column(String(65535))
|
||||
embedding = mapped_column(Vector(1536))
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=datetime.datetime.utcnow
|
||||
DateTime(timezone=True), default=func.now()
|
||||
)
|
||||
|
||||
collection_id = Column(Uuid, ForeignKey("collections.id"), index=True)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import traceback
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -99,6 +100,7 @@ async def create_app(
|
|||
status_code=406, detail="App with name may already exist"
|
||||
) from e
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(status_code=400, detail="Unknown Error") from e
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,24 +48,6 @@ async def get_collections(
|
|||
)
|
||||
|
||||
|
||||
# @router.get("/id/{collection_id}", response_model=schemas.Collection)
|
||||
# def get_collection_by_id(
|
||||
# request: Request,
|
||||
# app_id: uuid.UUID,
|
||||
# user_id: uuid.UUID,
|
||||
# collection_id: uuid.UUID,
|
||||
# db=db,
|
||||
# ) -> schemas.Collection:
|
||||
# honcho_collection = crud.get_collection_by_id(
|
||||
# db, app_id=app_id, user_id=user_id, collection_id=collection_id
|
||||
# )
|
||||
# if honcho_collection is None:
|
||||
# raise HTTPException(
|
||||
# status_code=404, detail="collection not found or does not belong to user"
|
||||
# )
|
||||
# return honcho_collection
|
||||
|
||||
|
||||
@router.get("/name/{name}", response_model=schemas.Collection)
|
||||
async def get_collection_by_name(
|
||||
request: Request,
|
||||
|
|
@ -139,16 +121,11 @@ async def update_collection(
|
|||
db=db,
|
||||
auth=Depends(auth),
|
||||
):
|
||||
if collection.name is None:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="invalid request - name cannot be None"
|
||||
)
|
||||
if collection.name == "honcho":
|
||||
if collection.name is not None and collection.name == "honcho":
|
||||
raise HTTPException(
|
||||
status_code=406,
|
||||
detail="error invalid collection configuration - honcho is a reserved name",
|
||||
)
|
||||
|
||||
try:
|
||||
honcho_collection = await crud.update_collection(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ from src.dependencies import db
|
|||
from src.security import auth
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/apps/{app_id}/users/{user_id}/collections/{collection_id}",
|
||||
prefix="/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents",
|
||||
tags=["documents"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/documents", response_model=Page[schemas.Document])
|
||||
@router.get("", response_model=Page[schemas.Document])
|
||||
async def get_documents(
|
||||
request: Request,
|
||||
app_id: uuid.UUID,
|
||||
|
|
@ -51,7 +51,7 @@ async def get_documents(
|
|||
|
||||
|
||||
@router.get(
|
||||
"/documents/{document_id}",
|
||||
"/{document_id}",
|
||||
response_model=schemas.Document,
|
||||
)
|
||||
async def get_document(
|
||||
|
|
@ -105,7 +105,7 @@ async def query_documents(
|
|||
)
|
||||
|
||||
|
||||
@router.post("/documents", response_model=schemas.Document)
|
||||
@router.post("", response_model=schemas.Document)
|
||||
async def create_document(
|
||||
request: Request,
|
||||
app_id: uuid.UUID,
|
||||
|
|
@ -130,7 +130,7 @@ async def create_document(
|
|||
|
||||
|
||||
@router.put(
|
||||
"/documents/{document_id}",
|
||||
"/{document_id}",
|
||||
response_model=schemas.Document,
|
||||
)
|
||||
async def update_document(
|
||||
|
|
@ -147,17 +147,22 @@ async def update_document(
|
|||
raise HTTPException(
|
||||
status_code=400, detail="content and metadata cannot both be None"
|
||||
)
|
||||
return await crud.update_document(
|
||||
db,
|
||||
document=document,
|
||||
app_id=app_id,
|
||||
user_id=user_id,
|
||||
collection_id=collection_id,
|
||||
document_id=document_id,
|
||||
)
|
||||
try:
|
||||
return await crud.update_document(
|
||||
db,
|
||||
document=document,
|
||||
app_id=app_id,
|
||||
user_id=user_id,
|
||||
collection_id=collection_id,
|
||||
document_id=document_id,
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="collection not found or does not belong to user"
|
||||
) from None
|
||||
|
||||
|
||||
@router.delete("/documents/{document_id}")
|
||||
@router.delete("/{document_id}")
|
||||
async def delete_document(
|
||||
request: Request,
|
||||
app_id: uuid.UUID,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,10 @@ async def create_metamessage(
|
|||
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
|
||||
metamessage (schemas.MeteamessageCreate): The metamessage creation object
|
||||
|
||||
Returns:
|
||||
schemas.Message: The Message object of the added message
|
||||
schemas.Metamessage: The Metamessage object of the added metamessage
|
||||
|
||||
Raises:
|
||||
HTTPException: If the session is not found
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ async def create_session(
|
|||
return value
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.put("/{session_id}", response_model=schemas.Session)
|
||||
|
|
|
|||
167
src/schemas.py
167
src/schemas.py
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
class AppBase(BaseModel):
|
||||
|
|
@ -25,15 +25,21 @@ class App(AppBase):
|
|||
metadata: dict
|
||||
created_at: datetime.datetime
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
|
|
@ -58,15 +64,22 @@ class User(UserBase):
|
|||
h_metadata: dict = Field(exclude=True)
|
||||
metadata: dict
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class MessageBase(BaseModel):
|
||||
|
|
@ -92,15 +105,22 @@ class Message(MessageBase):
|
|||
metadata: dict
|
||||
created_at: datetime.datetime
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class SessionBase(BaseModel):
|
||||
|
|
@ -126,15 +146,22 @@ class Session(SessionBase):
|
|||
metadata: dict
|
||||
created_at: datetime.datetime
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class MetamessageBase(BaseModel):
|
||||
|
|
@ -163,15 +190,23 @@ class Metamessage(MetamessageBase):
|
|||
metadata: dict
|
||||
created_at: datetime.datetime
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class CollectionBase(BaseModel):
|
||||
|
|
@ -184,7 +219,7 @@ class CollectionCreate(CollectionBase):
|
|||
|
||||
|
||||
class CollectionUpdate(CollectionBase):
|
||||
name: str
|
||||
name: str | None = None
|
||||
metadata: dict | None = None
|
||||
|
||||
|
||||
|
|
@ -196,15 +231,23 @@ class Collection(CollectionBase):
|
|||
metadata: dict
|
||||
created_at: datetime.datetime
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class DocumentBase(BaseModel):
|
||||
|
|
@ -228,15 +271,23 @@ class Document(DocumentBase):
|
|||
created_at: datetime.datetime
|
||||
collection_id: uuid.UUID
|
||||
|
||||
@validator("metadata", pre=True, allow_reuse=True)
|
||||
def fetch_h_metadata(cls, value, values):
|
||||
if "h_metadata" in values:
|
||||
return values["h_metadata"]
|
||||
return {}
|
||||
@field_validator("metadata", mode="before")
|
||||
def fetch_h_metadata(cls, value, info):
|
||||
return info.data.get("h_metadata", {})
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
# @validator("metadata", pre=True, allow_reuse=True)
|
||||
# def fetch_h_metadata(cls, value, values):
|
||||
# if "h_metadata" in values:
|
||||
# return values["h_metadata"]
|
||||
# return {}
|
||||
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
json_schema_extra={"exclude": ["h_metadata"]},
|
||||
)
|
||||
# class Config:
|
||||
# from_attributes = True
|
||||
# json_schema_extra = {"exclude": ["h_metadata"]}
|
||||
|
||||
|
||||
class AgentChat(BaseModel):
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ async def auth(
|
|||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
|
||||
):
|
||||
if not USE_AUTH_SERVICE:
|
||||
print("Test of Auth")
|
||||
return True
|
||||
print(credentials)
|
||||
if not credentials or credentials.credentials != "test":
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import logging # noqa: I001
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession
|
||||
from sqlalchemy.engine.url import make_url
|
||||
from sqlalchemy.exc import OperationalError, ProgrammingError
|
||||
from sqlalchemy_utils import create_database, database_exists, drop_database
|
||||
|
||||
from src import models
|
||||
from src.db import Base
|
||||
from src.dependencies import get_db
|
||||
from src.main import app
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
stream=sys.stdout, # This ensures the output goes to stdout
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Test database URL
|
||||
# TODO use environment variable
|
||||
CONNECTION_URI = make_url(os.getenv("CONNECTION_URI"))
|
||||
TEST_DB_URL = CONNECTION_URI.set(database="test")
|
||||
DEFAULT_DB_URL = str(CONNECTION_URI.set(database="postgres"))
|
||||
|
||||
|
||||
def create_test_database(db_url):
|
||||
"""Helper function create a database if it does not already exist
|
||||
uses the `sqlalchemy_utils` library to create the database and takes a DB URL
|
||||
as the input
|
||||
|
||||
Args:
|
||||
db_url (str): Database URL
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"Checking if database exists: {db_url.database}")
|
||||
if not database_exists(db_url):
|
||||
logger.info(f"Creating test database: {db_url.database}")
|
||||
create_database(db_url)
|
||||
logger.info(f"Test database created successfully: {db_url.database}")
|
||||
else:
|
||||
logger.info(f"Database already exists: {db_url.database}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating database: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def setup_test_database(db_url):
|
||||
"""Helper function to setup the test database
|
||||
takes a DB URL as input and returns a SQLAlchemy engine
|
||||
|
||||
Args:
|
||||
db_url (str): Database URL
|
||||
|
||||
Returns:
|
||||
engine: SQLAlchemy engine
|
||||
"""
|
||||
engine = create_async_engine(str(db_url))
|
||||
async with engine.connect() as conn:
|
||||
try:
|
||||
logger.info("Attempting to create pgvector extension...")
|
||||
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
|
||||
await conn.commit()
|
||||
logger.info("pgvector extension created successfully.")
|
||||
except ProgrammingError as e:
|
||||
logger.error(f"ProgrammingError: {e}")
|
||||
raise RuntimeError(
|
||||
"Failed to create pgvector extension. Make sure it's installed on the PostgreSQL server."
|
||||
) from e
|
||||
except OperationalError as e:
|
||||
logger.error(f"OperationalError: {e}")
|
||||
raise RuntimeError(
|
||||
"Failed to connect to the database. Check your connection settings."
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
raise
|
||||
return engine
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def db_engine():
|
||||
create_test_database(TEST_DB_URL)
|
||||
engine = await setup_test_database(TEST_DB_URL)
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
yield engine
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
drop_database(TEST_DB_URL)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def db_session(db_engine):
|
||||
"""Create a database session for the scope of a single test function"""
|
||||
Session = async_sessionmaker(bind=db_engine, expire_on_commit=False)
|
||||
async with Session() as session:
|
||||
yield session
|
||||
await session.rollback()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def client(db_session):
|
||||
"""Create a FastAPI TestClient for the scope of a single test function"""
|
||||
|
||||
async def override_get_db():
|
||||
yield db_session
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def sample_data(db_session):
|
||||
"""Helper function to create test data"""
|
||||
# Create test app
|
||||
test_app = models.App(name=str(uuid.uuid4()), metadata={})
|
||||
db_session.add(test_app)
|
||||
await db_session.flush()
|
||||
|
||||
# Create test user
|
||||
test_user = models.User(name=str(uuid.uuid4()), app_id=test_app.id, metadata={})
|
||||
db_session.add(test_user)
|
||||
await db_session.flush()
|
||||
|
||||
yield test_app, test_user
|
||||
|
||||
await db_session.rollback()
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src import models # Import your SQLAlchemy models
|
||||
|
||||
|
||||
def test_create_app(client):
|
||||
name = str(uuid.uuid4())
|
||||
response = client.post("/apps", json={"name": name, "metadata": {"key": "value"}})
|
||||
print(response)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == name
|
||||
assert data["metadata"] == {"key": "value"}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_or_create_app(client):
|
||||
name = str(uuid.uuid4())
|
||||
response = client.get(f"/apps/name/{name}")
|
||||
assert response.status_code == 404
|
||||
response = client.get(f"/apps/get_or_create/{name}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == name
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_or_create_existing_app(client):
|
||||
name = str(uuid.uuid4())
|
||||
response = client.get(f"/apps/name/{name}")
|
||||
assert response.status_code == 404
|
||||
response = client.post("/apps", json={"name": name, "metadata": {"key": "value"}})
|
||||
assert response.status_code == 200
|
||||
app1 = response.json()
|
||||
response = client.get(f"/apps/get_or_create/{name}")
|
||||
assert response.status_code == 200
|
||||
app2 = response.json()
|
||||
assert app1["name"] == app2["name"]
|
||||
assert app1["id"] == app2["id"]
|
||||
assert app1["metadata"] == app2["metadata"]
|
||||
|
||||
|
||||
def test_get_app_by_id(client, sample_data):
|
||||
test_app, _ = sample_data
|
||||
response = client.get(f"/apps/{test_app.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == test_app.name
|
||||
assert data["id"] == str(test_app.id)
|
||||
|
||||
|
||||
def test_get_app_by_name(client, sample_data):
|
||||
test_app, _ = sample_data
|
||||
response = client.get(f"/apps/name/{test_app.name}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == test_app.name
|
||||
assert data["id"] == str(test_app.id)
|
||||
|
||||
|
||||
def test_update_app(client, sample_data):
|
||||
test_app, _ = sample_data
|
||||
new_name = str(uuid.uuid4())
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}",
|
||||
json={"name": new_name, "metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == new_name
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import uuid
|
||||
|
||||
|
||||
def test_create_collection(client, sample_data) -> None:
|
||||
test_app, test_user = sample_data
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == "test_collection"
|
||||
assert data["metadata"] == {}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_collection_by_id(client, sample_data) -> None:
|
||||
test_app, test_user = sample_data
|
||||
# Make the collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Get the collection
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == "test_collection"
|
||||
assert data["metadata"] == {}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_collection_by_name(client, sample_data) -> None:
|
||||
test_app, test_user = sample_data
|
||||
# Make the collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Get the collection
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/name/test_collection"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == "test_collection"
|
||||
assert data["metadata"] == {}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_update_collection(client, sample_data) -> None:
|
||||
test_app, test_user = sample_data
|
||||
# Make the collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Update the collection
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}",
|
||||
json={"name": "test_collection_updated", "metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == "test_collection_updated"
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_delete_collection(client, sample_data) -> None:
|
||||
test_app, test_user = sample_data
|
||||
# Make the collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Delete the collection
|
||||
response = client.delete(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
def test_create_document(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Create a document
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}/documents",
|
||||
json={"content": "test_text", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["content"] == "test_text"
|
||||
assert data["metadata"] == {}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_document(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
collection = response.json()
|
||||
# Create a document
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{collection['id']}/documents",
|
||||
json={"content": "test_text", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
document = response.json()
|
||||
# Get the document
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{collection['id']}/documents/{document['id']}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["content"] == "test_text"
|
||||
assert data["metadata"] == {}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_update_document(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Create a document
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}/documents",
|
||||
json={"content": "test_text", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# Update the document
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{data['id']}/documents/{data['id']}",
|
||||
json={"content": "test_text_updated", "metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
|
||||
|
||||
def test_delete_document(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a collection
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections",
|
||||
json={"name": "test_collection", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
collection = response.json()
|
||||
# Create a document
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{collection['id']}/documents",
|
||||
json={"content": "test_text", "metadata": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
document = response.json()
|
||||
# Delete the document
|
||||
response = client.delete(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{collection['id']}/documents/{document['id']}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/collections/{collection['id']}/documents/{document['id']}"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import pytest
|
||||
|
||||
from src import models # Import your SQLAlchemy models
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_message(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/messages",
|
||||
json={
|
||||
"content": "Test message",
|
||||
"is_user": True,
|
||||
"metadata": {"message_key": "message_value"},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["content"] == "Test message"
|
||||
assert data["is_user"] is True
|
||||
assert data["metadata"] == {"message_key": "message_value"}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_messages(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session and message
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
test_message = models.Message(
|
||||
session_id=test_session.id, content="Test message", is_user=True, metadata={}
|
||||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/messages"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "items" in data
|
||||
assert len(data["items"]) > 0
|
||||
assert data["items"][0]["content"] == "Test message"
|
||||
assert data["items"][0]["is_user"] is True
|
||||
assert data["items"][0]["metadata"] == {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_message(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session and message
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
test_message = models.Message(
|
||||
session_id=test_session.id, content="Test message", is_user=True, metadata={}
|
||||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/messages/{test_message.id}",
|
||||
json={"metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import pytest
|
||||
|
||||
from src import models # Import your SQLAlchemy models
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_metamessage(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
test_message = models.Message(
|
||||
session_id=test_session.id, content="Test message", is_user=True, metadata={}
|
||||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/metamessages",
|
||||
json={
|
||||
"message_id": str(test_message.id),
|
||||
"content": "Test Metamessage",
|
||||
"metadata": {},
|
||||
"metamessage_type": "test_type",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["message_id"] == str(test_message.id)
|
||||
assert data["content"] == "Test Metamessage"
|
||||
assert data["metadata"] == {}
|
||||
assert data["metamessage_type"] == "test_type"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_metamessage(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
test_message = models.Message(
|
||||
session_id=test_session.id, content="Test message", is_user=True, metadata={}
|
||||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
test_metamessage = models.Metamessage(
|
||||
message_id=test_message.id,
|
||||
content="Test Metamessage",
|
||||
metadata={},
|
||||
metamessage_type="test_type",
|
||||
)
|
||||
db_session.add(test_metamessage)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/metamessages/{test_metamessage.id}?message_id={test_message.id}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["message_id"] == str(test_message.id)
|
||||
assert data["content"] == "Test Metamessage"
|
||||
assert data["metadata"] == {}
|
||||
assert data["metamessage_type"] == "test_type"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_metamessage(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
test_message = models.Message(
|
||||
session_id=test_session.id, content="Test message", is_user=True, metadata={}
|
||||
)
|
||||
db_session.add(test_message)
|
||||
await db_session.commit()
|
||||
test_metamessage = models.Metamessage(
|
||||
message_id=test_message.id,
|
||||
content="Test Metamessage",
|
||||
metadata={},
|
||||
metamessage_type="test_type",
|
||||
)
|
||||
db_session.add(test_metamessage)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}/metamessages/{test_metamessage.id}",
|
||||
json={"message_id": str(test_message.id), "metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import pytest
|
||||
|
||||
from src import models # Import your SQLAlchemy models
|
||||
|
||||
|
||||
def test_create_session(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions",
|
||||
json={
|
||||
"location_id": "test_location",
|
||||
"metadata": {"session_key": "session_value"},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["location_id"] == "test_location"
|
||||
assert data["metadata"] == {"session_key": "session_value"}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_sessions(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.get(f"/apps/{test_app.id}/users/{test_user.id}/sessions")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "items" in data
|
||||
assert len(data["items"]) > 0
|
||||
assert data["items"][0]["location_id"] == "test_location"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_session(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}",
|
||||
json={"metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_session(client, db_session, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
# Create a test session
|
||||
test_session = models.Session(
|
||||
user_id=test_user.id, location_id="test_location", metadata={}
|
||||
)
|
||||
db_session.add(test_session)
|
||||
await db_session.commit()
|
||||
response = client.delete(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
response = client.get(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}/sessions/{test_session.id}"
|
||||
)
|
||||
data = response.json()
|
||||
assert data["is_active"] is False
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import uuid
|
||||
|
||||
|
||||
def test_create_user(client, sample_data):
|
||||
test_app, _ = sample_data
|
||||
name = str(uuid.uuid4())
|
||||
response = client.post(
|
||||
f"/apps/{test_app.id}/users",
|
||||
json={"name": name, "metadata": {"user_key": "user_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == name
|
||||
assert data["metadata"] == {"user_key": "user_value"}
|
||||
assert "id" in data
|
||||
|
||||
|
||||
def test_get_user_by_id(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
response = client.get(f"/apps/{test_app.id}/users/{test_user.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == test_user.name
|
||||
assert data["id"] == str(test_user.id)
|
||||
|
||||
|
||||
def test_get_user_by_name(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
response = client.get(f"/apps/{test_app.id}/users/name/{test_user.name}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == test_user.name
|
||||
assert data["id"] == str(test_user.id)
|
||||
|
||||
|
||||
def test_get_or_create_user(client, sample_data):
|
||||
test_app, _ = sample_data
|
||||
name = str(uuid.uuid4())
|
||||
response = client.get(f"/apps/{test_app.id}/users/name/{name}")
|
||||
assert response.status_code == 404
|
||||
response = client.get(f"/apps/{test_app.id}/users/get_or_create/{name}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == name
|
||||
assert "id" in data
|
||||
|
||||
|
||||
# def test_get_users(client, sample_data):
|
||||
# test_app, _ = sample_data
|
||||
# response = client.get(f"/apps/{test_app.id}/users")
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
# assert "items" in data
|
||||
# assert len(data["items"]) > 0
|
||||
|
||||
|
||||
def test_update_user(client, sample_data):
|
||||
test_app, test_user = sample_data
|
||||
new_name = str(uuid.uuid4())
|
||||
response = client.put(
|
||||
f"/apps/{test_app.id}/users/{test_user.id}",
|
||||
json={"name": new_name, "metadata": {"new_key": "new_value"}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
print(new_name)
|
||||
print(data)
|
||||
assert data["name"] == new_name
|
||||
assert data["metadata"] == {"new_key": "new_value"}
|
||||
Loading…
Reference in New Issue