From 1861926947dc22c570d21bec407d7215ec0ce4aa Mon Sep 17 00:00:00 2001 From: Max Chis Date: Tue, 30 Sep 2025 07:36:59 -0400 Subject: [PATCH] Add filtering by URL ID --- .../annotate/all/get/queries/core.py | 6 ++- .../annotate/all/post/models/request.py | 4 +- src/api/endpoints/annotate/routes.py | 18 +++++--- src/core/core.py | 6 ++- src/db/client/async_.py | 6 ++- .../api/_helpers/RequestValidator.py | 16 +++++-- .../api/annotate/all/test_happy_path.py | 2 +- .../api/annotate/all/test_url_filtering.py | 44 +++++++++++++++++++ 8 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 tests/automated/integration/api/annotate/all/test_url_filtering.py diff --git a/src/api/endpoints/annotate/all/get/queries/core.py b/src/api/endpoints/annotate/all/get/queries/core.py index fccf4f84..d8684f59 100644 --- a/src/api/endpoints/annotate/all/get/queries/core.py +++ b/src/api/endpoints/annotate/all/get/queries/core.py @@ -37,10 +37,12 @@ class GetNextURLForAllAnnotationQueryBuilder(QueryBuilderBase): def __init__( self, batch_id: int | None, - user_id: int + user_id: int, + url_id: int | None = None ): super().__init__() self.batch_id = batch_id + self.url_id = url_id self.user_id = user_id async def run( @@ -65,6 +67,8 @@ async def run( ) if self.batch_id is not None: query = query.join(LinkBatchURL).where(LinkBatchURL.batch_id == self.batch_id) + if self.url_id is not None: + query = query.where(URL.id == self.url_id) query = ( query .where( diff --git a/src/api/endpoints/annotate/all/post/models/request.py b/src/api/endpoints/annotate/all/post/models/request.py index 9ff40f40..c4b3fde9 100644 --- a/src/api/endpoints/annotate/all/post/models/request.py +++ b/src/api/endpoints/annotate/all/post/models/request.py @@ -13,8 +13,8 @@ class AllAnnotationPostInfo(BaseModel): suggested_status: URLType record_type: RecordType | None = None - agency_info: AnnotationPostAgencyInfo - location_info: AnnotationPostLocationInfo + agency_info: AnnotationPostAgencyInfo = AnnotationPostAgencyInfo() + location_info: AnnotationPostLocationInfo = AnnotationPostLocationInfo() name_info: AnnotationPostNameInfo = AnnotationPostNameInfo() @model_validator(mode="after") diff --git a/src/api/endpoints/annotate/routes.py b/src/api/endpoints/annotate/routes.py index 682325e9..50798990 100644 --- a/src/api/endpoints/annotate/routes.py +++ b/src/api/endpoints/annotate/routes.py @@ -18,18 +18,24 @@ "If not specified, defaults to first qualifying URL", default=None ) - +url_id_query = Query( + description="The URL id to annotate. " + + "If not specified, defaults to first qualifying URL", + default=None +) @annotate_router.get("/all") async def get_next_url_for_all_annotations( access_info: AccessInfo = Depends(get_access_info), async_core: AsyncCore = Depends(get_async_core), - batch_id: int | None = batch_query + batch_id: int | None = batch_query, + anno_url_id: int | None = url_id_query ) -> GetNextURLForAllAnnotationResponse: return await async_core.get_next_url_for_all_annotations( batch_id=batch_id, - user_id=access_info.user_id + user_id=access_info.user_id, + url_id=anno_url_id ) @annotate_router.post("/all/{url_id}") @@ -38,7 +44,8 @@ async def annotate_url_for_all_annotations_and_get_next_url( all_annotation_post_info: AllAnnotationPostInfo, async_core: AsyncCore = Depends(get_async_core), access_info: AccessInfo = Depends(get_access_info), - batch_id: int | None = batch_query + batch_id: int | None = batch_query, + anno_url_id: int | None = url_id_query ) -> GetNextURLForAllAnnotationResponse: """ Post URL annotation and get next URL to annotate @@ -50,5 +57,6 @@ async def annotate_url_for_all_annotations_and_get_next_url( ) return await async_core.get_next_url_for_all_annotations( batch_id=batch_id, - user_id=access_info.user_id + user_id=access_info.user_id, + url_id=anno_url_id ) \ No newline at end of file diff --git a/src/core/core.py b/src/core/core.py index 0af67665..2875f8a8 100644 --- a/src/core/core.py +++ b/src/core/core.py @@ -174,11 +174,13 @@ async def get_next_source_for_review( async def get_next_url_for_all_annotations( self, user_id: int, - batch_id: int | None + batch_id: int | None, + url_id: int | None ) -> GetNextURLForAllAnnotationResponse: return await self.adb_client.get_next_url_for_all_annotations( batch_id=batch_id, - user_id=user_id + user_id=user_id, + url_id=url_id ) async def submit_url_for_all_annotations( diff --git a/src/db/client/async_.py b/src/db/client/async_.py index 6066a2e5..beb71375 100644 --- a/src/db/client/async_.py +++ b/src/db/client/async_.py @@ -894,11 +894,13 @@ async def delete_old_logs(self): async def get_next_url_for_all_annotations( self, user_id: int, - batch_id: int | None = None + batch_id: int | None = None, + url_id: int | None = None ) -> GetNextURLForAllAnnotationResponse: return await self.run_query_builder(GetNextURLForAllAnnotationQueryBuilder( batch_id=batch_id, - user_id=user_id + user_id=user_id, + url_id=url_id )) async def upload_manual_batch( diff --git a/tests/automated/integration/api/_helpers/RequestValidator.py b/tests/automated/integration/api/_helpers/RequestValidator.py index f2d68046..d7cfbf42 100644 --- a/tests/automated/integration/api/_helpers/RequestValidator.py +++ b/tests/automated/integration/api/_helpers/RequestValidator.py @@ -316,12 +316,16 @@ async def get_current_task_status(self) -> GetTaskStatusResponseInfo: async def get_next_url_for_all_annotations( self, - batch_id: Optional[int] = None + batch_id: int | None = None, + anno_url_id: int | None = None ) -> GetNextURLForAllAnnotationResponse: params = {} update_if_not_none( target=params, - source={"batch_id": batch_id} + source={ + "batch_id": batch_id, + "anno_url_id": anno_url_id + } ) data = self.get( url=f"/annotate/all", @@ -333,12 +337,16 @@ async def post_all_annotations_and_get_next( self, url_id: int, all_annotations_post_info: AllAnnotationPostInfo, - batch_id: Optional[int] = None, + batch_id: int | None = None, + anno_url_id: int | None = None ) -> GetNextURLForAllAnnotationResponse: params = {} update_if_not_none( target=params, - source={"batch_id": batch_id} + source={ + "batch_id": batch_id, + "anno_url_id": anno_url_id + } ) data = self.post( url=f"/annotate/all/{url_id}", diff --git a/tests/automated/integration/api/annotate/all/test_happy_path.py b/tests/automated/integration/api/annotate/all/test_happy_path.py index 38c958ad..48b60b8b 100644 --- a/tests/automated/integration/api/annotate/all/test_happy_path.py +++ b/tests/automated/integration/api/annotate/all/test_happy_path.py @@ -137,7 +137,7 @@ async def test_annotate_all( response: GetNextURLForAllAnnotationResponse = await adb_client.run_query_builder( GetNextURLForAllAnnotationQueryBuilder( batch_id=None, - user_id=99 + user_id=99, ) ) user_suggestions: list[LocationAnnotationUserSuggestion] = \ diff --git a/tests/automated/integration/api/annotate/all/test_url_filtering.py b/tests/automated/integration/api/annotate/all/test_url_filtering.py new file mode 100644 index 00000000..6ca36cb5 --- /dev/null +++ b/tests/automated/integration/api/annotate/all/test_url_filtering.py @@ -0,0 +1,44 @@ +import pytest + +from src.api.endpoints.annotate.all.post.models.request import AllAnnotationPostInfo +from src.db.client.async_ import AsyncDatabaseClient +from src.db.models.impl.flag.url_validated.enums import URLType +from tests.helpers.api_test_helper import APITestHelper +from tests.helpers.setup.final_review.core import setup_for_get_next_url_for_final_review + + +@pytest.mark.asyncio +async def test_annotate_all_post_batch_filtering(api_test_helper: APITestHelper): + """ + Test that URL filtering works when getting and posting annotations + """ + ath = api_test_helper + adb_client: AsyncDatabaseClient = ath.adb_client() + + setup_info_1 = await setup_for_get_next_url_for_final_review( + db_data_creator=ath.db_data_creator, include_user_annotations=False + ) + url_mapping_1 = setup_info_1.url_mapping + setup_info_2 = await setup_for_get_next_url_for_final_review( + db_data_creator=ath.db_data_creator, include_user_annotations=False + ) + setup_info_3 = await setup_for_get_next_url_for_final_review( + db_data_creator=ath.db_data_creator, include_user_annotations=False + ) + url_mapping_3 = setup_info_3.url_mapping + + get_response_2 = await ath.request_validator.get_next_url_for_all_annotations( + batch_id=setup_info_3.batch_id, + anno_url_id=url_mapping_3.url_id + ) + assert get_response_2.next_annotation.url_info.url_id == url_mapping_3.url_id + + post_response_3 = await ath.request_validator.post_all_annotations_and_get_next( + url_id=url_mapping_1.url_id, + anno_url_id=url_mapping_3.url_id, + all_annotations_post_info=AllAnnotationPostInfo( + suggested_status=URLType.NOT_RELEVANT, + ) + ) + + assert post_response_3.next_annotation.url_info.url_id == url_mapping_3.url_id \ No newline at end of file