diff --git a/apps/backend/src/rhesis/backend/app/crud/__init__.py b/apps/backend/src/rhesis/backend/app/crud/__init__.py index 8289e487f3..89232642db 100644 --- a/apps/backend/src/rhesis/backend/app/crud/__init__.py +++ b/apps/backend/src/rhesis/backend/app/crud/__init__.py @@ -148,122 +148,6 @@ def get_experiments( ) -# Prompt CRUD -def get_prompt( - db: Session, prompt_id: uuid.UUID, organization_id: str = None, user_id: str = None -) -> Optional[models.Prompt]: - """Get prompt.""" - return get_item(db, models.Prompt, prompt_id, organization_id, user_id) - - -def get_prompts( - db: Session, - skip: int = 0, - limit: int = 10, - sort_by: str = "created_at", - sort_order: str = "desc", - filter: str | None = None, - organization_id: str = None, - user_id: str = None, -) -> List[models.Prompt]: - # PromptDetail has no nested relationship fields -- plain get_items, no eager load. - return get_items( - db, - models.Prompt, - skip, - limit, - sort_by, - sort_order, - filter, - organization_id=organization_id, - user_id=user_id, - ) - - -def create_prompt( - db: Session, prompt: schemas.PromptCreate, organization_id: str = None, user_id: str = None -) -> models.Prompt: - """Create prompt.""" - return create_item(db, models.Prompt, prompt, organization_id, user_id) - - -def update_prompt( - db: Session, - prompt_id: uuid.UUID, - prompt: schemas.PromptUpdate, - organization_id: str = None, - user_id: str = None, -) -> Optional[models.Prompt]: - """Update prompt.""" - return update_item(db, models.Prompt, prompt_id, prompt, organization_id, user_id) - - -def delete_prompt( - db: Session, prompt_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.Prompt]: - return delete_item( - db, models.Prompt, prompt_id, organization_id=organization_id, user_id=user_id - ) - - -# Prompt Template CRUD -def get_prompt_template( - db: Session, prompt_template_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.PromptTemplate]: - return get_item(db, models.PromptTemplate, prompt_template_id, organization_id, user_id) - - -def get_prompt_templates( - db: Session, - skip: int = 0, - limit: int = 10, - sort_by: str = "created_at", - sort_order: str = "desc", - filter: str | None = None, - organization_id: str = None, - user_id: str = None, -) -> List[models.PromptTemplate]: - return get_items_detail( - db, - models.PromptTemplate, - skip, - limit, - sort_by, - sort_order, - filter, - organization_id=organization_id, - user_id=user_id, - ) - - -def create_prompt_template( - db: Session, - prompt_template: schemas.PromptTemplateCreate, - organization_id: str = None, - user_id: str = None, -) -> models.PromptTemplate: - """Create prompt template.""" - return create_item(db, models.PromptTemplate, prompt_template, organization_id, user_id) - - -def update_prompt_template( - db: Session, - prompt_template_id: uuid.UUID, - prompt_template: schemas.PromptTemplateUpdate, - organization_id: str, - user_id: str, -) -> Optional[models.PromptTemplate]: - return update_item( - db, models.PromptTemplate, prompt_template_id, prompt_template, organization_id, user_id - ) - - -def delete_prompt_template( - db: Session, prompt_template_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.PromptTemplate]: - return delete_item(db, models.PromptTemplate, prompt_template_id, organization_id, user_id) - - # Category CRUD def get_category( db: Session, category_id: uuid.UUID, organization_id: str = None, user_id: str = None diff --git a/apps/backend/src/rhesis/backend/app/crud/prompt.py b/apps/backend/src/rhesis/backend/app/crud/prompt.py new file mode 100644 index 0000000000..fc49550d7a --- /dev/null +++ b/apps/backend/src/rhesis/backend/app/crud/prompt.py @@ -0,0 +1,139 @@ +"""CRUD operations for prompts and prompt templates. + +Part of the incremental split of the ``crud`` monolith: ``crud/__init__.py`` still holds +the bulk of the functions, and per-entity modules like this one take over as the code +around them is touched. + +Prompt templates live here too rather than in their own module: a template is a prompt with +placeholders left in, so it is the same domain. +""" + +import uuid +from typing import List, Optional + +from sqlalchemy.orm import Session + +from rhesis.backend.app import models, schemas +from rhesis.backend.app.utils.crud_utils import ( + create_item, + delete_item, + get_item, + get_items, + get_items_detail, + update_item, +) + + +def get_prompt( + db: Session, prompt_id: uuid.UUID, organization_id: str = None, user_id: str = None +) -> Optional[models.Prompt]: + """Get prompt.""" + return get_item(db, models.Prompt, prompt_id, organization_id, user_id) + + +def get_prompts( + db: Session, + skip: int = 0, + limit: int = 10, + sort_by: str = "created_at", + sort_order: str = "desc", + filter: str | None = None, + organization_id: str = None, + user_id: str = None, +) -> List[models.Prompt]: + # PromptDetail has no nested relationship fields -- plain get_items, no eager load. + return get_items( + db, + models.Prompt, + skip, + limit, + sort_by, + sort_order, + filter, + organization_id=organization_id, + user_id=user_id, + ) + + +def create_prompt( + db: Session, prompt: schemas.PromptCreate, organization_id: str = None, user_id: str = None +) -> models.Prompt: + """Create prompt.""" + return create_item(db, models.Prompt, prompt, organization_id, user_id) + + +def update_prompt( + db: Session, + prompt_id: uuid.UUID, + prompt: schemas.PromptUpdate, + organization_id: str = None, + user_id: str = None, +) -> Optional[models.Prompt]: + """Update prompt.""" + return update_item(db, models.Prompt, prompt_id, prompt, organization_id, user_id) + + +def delete_prompt( + db: Session, prompt_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.Prompt]: + return delete_item( + db, models.Prompt, prompt_id, organization_id=organization_id, user_id=user_id + ) + + +# Prompt Template CRUD +def get_prompt_template( + db: Session, prompt_template_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.PromptTemplate]: + return get_item(db, models.PromptTemplate, prompt_template_id, organization_id, user_id) + + +def get_prompt_templates( + db: Session, + skip: int = 0, + limit: int = 10, + sort_by: str = "created_at", + sort_order: str = "desc", + filter: str | None = None, + organization_id: str = None, + user_id: str = None, +) -> List[models.PromptTemplate]: + return get_items_detail( + db, + models.PromptTemplate, + skip, + limit, + sort_by, + sort_order, + filter, + organization_id=organization_id, + user_id=user_id, + ) + + +def create_prompt_template( + db: Session, + prompt_template: schemas.PromptTemplateCreate, + organization_id: str = None, + user_id: str = None, +) -> models.PromptTemplate: + """Create prompt template.""" + return create_item(db, models.PromptTemplate, prompt_template, organization_id, user_id) + + +def update_prompt_template( + db: Session, + prompt_template_id: uuid.UUID, + prompt_template: schemas.PromptTemplateUpdate, + organization_id: str, + user_id: str, +) -> Optional[models.PromptTemplate]: + return update_item( + db, models.PromptTemplate, prompt_template_id, prompt_template, organization_id, user_id + ) + + +def delete_prompt_template( + db: Session, prompt_template_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.PromptTemplate]: + return delete_item(db, models.PromptTemplate, prompt_template_id, organization_id, user_id) diff --git a/apps/backend/src/rhesis/backend/app/routers/prompt.py b/apps/backend/src/rhesis/backend/app/routers/prompt.py index c4e008ef0b..2d962bc2b5 100644 --- a/apps/backend/src/rhesis/backend/app/routers/prompt.py +++ b/apps/backend/src/rhesis/backend/app/routers/prompt.py @@ -4,8 +4,9 @@ from rhesis.backend.app.routers.base import RhesisRouter from sqlalchemy.orm import Session -from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app import models, schemas from rhesis.backend.app.auth.user_utils import require_current_user_or_token +from rhesis.backend.app.crud import prompt as prompt_crud from rhesis.backend.app.dependencies import ( get_tenant_context, get_tenant_db_session, @@ -35,7 +36,7 @@ def create_prompt( ): """Create a new prompt.""" organization_id, user_id = tenant_context - return crud.create_prompt( + return prompt_crud.create_prompt( db=db, prompt=prompt, organization_id=organization_id, user_id=user_id ) @@ -55,7 +56,7 @@ def read_prompts( ): """Get all prompts with their related objects""" organization_id, user_id = tenant_context - return crud.get_prompts( + return prompt_crud.get_prompts( db=db, skip=skip, limit=limit, @@ -75,7 +76,7 @@ def read_prompt( current_user: User = Depends(require_current_user_or_token), ): organization_id, user_id = tenant_context - db_prompt = crud.get_prompt( + db_prompt = prompt_crud.get_prompt( db, prompt_id=prompt_id, organization_id=organization_id, user_id=user_id ) if db_prompt is None: @@ -93,7 +94,7 @@ def update_prompt( ): """Update a prompt""" organization_id, user_id = tenant_context - db_prompt = crud.update_prompt( + db_prompt = prompt_crud.update_prompt( db=db, prompt_id=prompt_id, prompt=prompt, organization_id=organization_id, user_id=user_id ) if db_prompt is None: @@ -110,7 +111,7 @@ def delete_prompt( ): """Delete a prompt""" organization_id, user_id = tenant_context - db_prompt = crud.delete_prompt( + db_prompt = prompt_crud.delete_prompt( db=db, prompt_id=prompt_id, organization_id=organization_id, user_id=user_id ) if db_prompt is None: diff --git a/apps/backend/src/rhesis/backend/app/routers/prompt_template.py b/apps/backend/src/rhesis/backend/app/routers/prompt_template.py index e39cb4cd5f..ae9cdfdaab 100644 --- a/apps/backend/src/rhesis/backend/app/routers/prompt_template.py +++ b/apps/backend/src/rhesis/backend/app/routers/prompt_template.py @@ -4,8 +4,9 @@ from rhesis.backend.app.routers.base import RhesisRouter from sqlalchemy.orm import Session -from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app import models, schemas from rhesis.backend.app.auth.user_utils import require_current_user_or_token +from rhesis.backend.app.crud import prompt as prompt_crud from rhesis.backend.app.dependencies import ( get_tenant_context, get_tenant_db_session, @@ -36,7 +37,7 @@ def create_prompt_template( ): """Create a new prompt template.""" organization_id, user_id = tenant_context - return crud.create_prompt_template( + return prompt_crud.create_prompt_template( db=db, prompt_template=template, organization_id=organization_id, user_id=user_id ) @@ -56,7 +57,7 @@ def read_prompt_templates( ): """Get all prompt templates with their related objects""" organization_id, user_id = tenant_context - return crud.get_prompt_templates( + return prompt_crud.get_prompt_templates( db=db, skip=skip, limit=limit, @@ -76,7 +77,7 @@ def read_prompt_template( current_user: User = Depends(require_current_user_or_token), ): organization_id, user_id = tenant_context - db_template = crud.get_prompt_template( + db_template = prompt_crud.get_prompt_template( db, prompt_template_id=prompt_template_id, organization_id=organization_id, user_id=user_id ) if db_template is None: @@ -92,7 +93,7 @@ def delete_prompt_template( current_user: User = Depends(require_current_user_or_token), ): organization_id, user_id = tenant_context - db_prompt_template = crud.delete_prompt_template( + db_prompt_template = prompt_crud.delete_prompt_template( db, prompt_template_id=prompt_template_id, organization_id=organization_id, user_id=user_id ) if db_prompt_template is None: @@ -109,7 +110,7 @@ def update_prompt_template( current_user: User = Depends(require_current_user_or_token), ): organization_id, user_id = tenant_context - db_prompt_template = crud.update_prompt_template( + db_prompt_template = prompt_crud.update_prompt_template( db, prompt_template_id=prompt_template_id, prompt_template=prompt_template, diff --git a/apps/backend/src/rhesis/backend/app/services/test_run.py b/apps/backend/src/rhesis/backend/app/services/test_run.py index a75aa3df77..ef1ea09f21 100644 --- a/apps/backend/src/rhesis/backend/app/services/test_run.py +++ b/apps/backend/src/rhesis/backend/app/services/test_run.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Session from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app.crud import prompt as prompt_crud from rhesis.backend.app.crud import test_result as test_result_crud from rhesis.backend.app.crud.metric import get_requirement_metrics from rhesis.backend.app.crud.test_run import get_test_run, get_test_run_requirements @@ -80,7 +81,7 @@ def get_test_results_for_test_run( else None ) prompt = ( - crud.get_prompt(db, result.prompt_id, organization_id=organization_id) + prompt_crud.get_prompt(db, result.prompt_id, organization_id=organization_id) if result.prompt_id else None ) diff --git a/tests/backend/security/test_organization_filtering.py b/tests/backend/security/test_organization_filtering.py index 51d2b5c5b8..360ea4397e 100644 --- a/tests/backend/security/test_organization_filtering.py +++ b/tests/backend/security/test_organization_filtering.py @@ -12,6 +12,7 @@ from rhesis.backend.app import crud, models from rhesis.backend.app.crud import model as model_crud +from rhesis.backend.app.crud import prompt as prompt_crud from rhesis.backend.app.crud import task as task_crud from rhesis.backend.app.crud import test_result as test_result_crud from rhesis.backend.app.crud.metric import create_metric, get_metric @@ -97,7 +98,7 @@ def test_get_test_organization_filtering(self, test_db: Session): from tests.backend.routes.fixtures.data_factories import PromptDataFactory prompt_data = PromptDataFactory.minimal_data() - prompt = crud.create_prompt( + prompt = prompt_crud.create_prompt( db=test_db, prompt=prompt_data, organization_id=str(org1.id), user_id=str(user1.id) ) @@ -145,7 +146,7 @@ def test_get_test_result_organization_filtering(self, test_db: Session): ) prompt_data = PromptDataFactory.minimal_data() - prompt = crud.create_prompt( + prompt = prompt_crud.create_prompt( db=test_db, prompt=prompt_data, organization_id=str(org1.id), user_id=str(user1.id) ) @@ -235,7 +236,7 @@ def test_get_test_run_organization_filtering(self, test_db: Session): ) prompt_data = PromptDataFactory.minimal_data() - prompt = crud.create_prompt( + prompt = prompt_crud.create_prompt( db=test_db, prompt=prompt_data, organization_id=str(org1.id), user_id=str(user1.id) ) @@ -377,12 +378,12 @@ def test_get_prompt_organization_filtering(self, test_db: Session): from tests.backend.routes.fixtures.data_factories import PromptDataFactory prompt_data = PromptDataFactory.minimal_data() - prompt = crud.create_prompt( + prompt = prompt_crud.create_prompt( db=test_db, prompt=prompt_data, organization_id=str(org1.id), user_id=str(user1.id) ) # User from org1 should be able to access the prompt - result_org1 = crud.get_prompt( + result_org1 = prompt_crud.get_prompt( test_db, prompt.id, organization_id=str(org1.id), user_id=str(user1.id) ) assert result_org1 is not None @@ -390,7 +391,7 @@ def test_get_prompt_organization_filtering(self, test_db: Session): assert str(result_org1.organization_id) == str(org1.id) # User from org2 should NOT be able to access the prompt - result_org2 = crud.get_prompt( + result_org2 = prompt_crud.get_prompt( test_db, prompt.id, organization_id=str(org2.id), user_id=str(user2.id) ) assert result_org2 is None @@ -613,7 +614,7 @@ def test_organization_filtering_regression_suite(self, test_db: Session): ("get_test_result", test_result_crud.get_test_result), ("get_test_run", get_test_run), ("get_endpoint", crud.get_endpoint), - ("get_prompt", crud.get_prompt), + ("get_prompt", prompt_crud.get_prompt), ("get_model", model_crud.get_model), ("get_metric", get_metric), ]