Skip to content

Commit 18d9645

Browse files
authored
Merge pull request #257 from Police-Data-Accessibility-Project/mc_171_annotate_same_url_rt_rel
feat(app): Add special error message for annotation user conflict
2 parents 41ca2ef + e090cad commit 18d9645

File tree

6 files changed

+142
-13
lines changed

6 files changed

+142
-13
lines changed

core/AsyncCore.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional
22

33
from pydantic import BaseModel
4+
from sqlalchemy.exc import IntegrityError
45

56
from collector_db.AsyncDatabaseClient import AsyncDatabaseClient
67
from collector_db.DTOs.BatchInfo import BatchInfo
@@ -27,7 +28,8 @@
2728
from core.DTOs.MessageResponse import MessageResponse
2829
from core.DTOs.SearchURLResponse import SearchURLResponse
2930
from core.TaskManager import TaskManager
30-
from core.enums import BatchStatus, RecordType
31+
from core.classes.ErrorManager import ErrorManager
32+
from core.enums import BatchStatus, RecordType, AnnotationType
3133

3234
from security_manager.SecurityManager import AccessInfo
3335

@@ -149,11 +151,17 @@ async def submit_url_relevance_annotation(
149151
url_id: int,
150152
relevant: bool
151153
):
152-
return await self.adb_client.add_user_relevant_suggestion(
153-
user_id=user_id,
154-
url_id=url_id,
155-
relevant=relevant
156-
)
154+
try:
155+
return await self.adb_client.add_user_relevant_suggestion(
156+
user_id=user_id,
157+
url_id=url_id,
158+
relevant=relevant
159+
)
160+
except IntegrityError as e:
161+
return await ErrorManager.raise_annotation_exists_error(
162+
annotation_type=AnnotationType.RELEVANCE,
163+
url_id=url_id
164+
)
157165

158166
async def get_next_url_for_relevance_annotation(
159167
self,
@@ -187,11 +195,17 @@ async def submit_url_record_type_annotation(
187195
url_id: int,
188196
record_type: RecordType,
189197
):
190-
await self.adb_client.add_user_record_type_suggestion(
191-
user_id=user_id,
192-
url_id=url_id,
193-
record_type=record_type
194-
)
198+
try:
199+
return await self.adb_client.add_user_record_type_suggestion(
200+
user_id=user_id,
201+
url_id=url_id,
202+
record_type=record_type
203+
)
204+
except IntegrityError as e:
205+
return await ErrorManager.raise_annotation_exists_error(
206+
annotation_type=AnnotationType.RECORD_TYPE,
207+
url_id=url_id
208+
)
195209

196210

197211
async def get_next_url_agency_for_annotation(

core/classes/ErrorManager.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from enum import Enum
2+
from http import HTTPStatus
3+
4+
from fastapi import HTTPException
5+
from pydantic import BaseModel
6+
7+
from core.enums import AnnotationType
8+
9+
10+
class ErrorTypes(Enum):
11+
ANNOTATION_EXISTS = "ANNOTATION_EXISTS"
12+
13+
class ErrorFormat(BaseModel):
14+
code: ErrorTypes
15+
message: str
16+
17+
18+
class ErrorManager:
19+
20+
@staticmethod
21+
async def raise_error(
22+
error_type: ErrorTypes,
23+
message: str,
24+
status_code: HTTPStatus = HTTPStatus.BAD_REQUEST
25+
):
26+
raise HTTPException(
27+
status_code=status_code,
28+
detail=ErrorFormat(
29+
code=error_type,
30+
message=message
31+
).model_dump(mode='json')
32+
)
33+
34+
@staticmethod
35+
async def raise_annotation_exists_error(
36+
annotation_type: AnnotationType,
37+
url_id: int
38+
):
39+
await ErrorManager.raise_error(
40+
error_type=ErrorTypes.ANNOTATION_EXISTS,
41+
message=f"Annotation of type {annotation_type.value} already exists"
42+
f" for url {url_id}",
43+
status_code=HTTPStatus.CONFLICT
44+
)

core/enums.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from enum import Enum
22

3+
class AnnotationType(Enum):
4+
RELEVANCE = "RELEVANCE"
5+
RECORD_TYPE = "RECORD_TYPE"
6+
AGENCY = "AGENCY"
7+
38

49
class BatchStatus(Enum):
510
READY_TO_LABEL = "ready to label"

core/helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from http import HTTPStatus
2+
3+
from fastapi import HTTPException
4+
15
from core.DTOs.URLAgencySuggestionInfo import URLAgencySuggestionInfo
26
from core.enums import SuggestionType
37
from core.exceptions import MatchAgencyError

tests/test_automated/integration/api/helpers/RequestValidator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def post_record_type_annotation_and_get_next(
246246
url_id: int,
247247
record_type_annotation_post_info: RecordTypeAnnotationPostInfo
248248
) -> GetNextRecordTypeAnnotationResponseOuterInfo:
249-
data = self.post(
249+
data = self.post_v2(
250250
url=f"/annotate/record-type/{url_id}",
251251
json=record_type_annotation_post_info.model_dump(mode='json')
252252
)
@@ -257,7 +257,7 @@ def post_relevance_annotation_and_get_next(
257257
url_id: int,
258258
relevance_annotation_post_info: RelevanceAnnotationPostInfo
259259
) -> GetNextRelevanceAnnotationResponseOuterInfo:
260-
data = self.post(
260+
data = self.post_v2(
261261
url=f"/annotate/relevance/{url_id}",
262262
json=relevance_annotation_post_info.model_dump(mode='json')
263263
)

tests/test_automated/integration/api/test_annotate.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from http import HTTPStatus
22

33
import pytest
4+
from fastapi import HTTPException
45

56
from collector_db.DTOs.InsertURLsInfo import InsertURLsInfo
67
from collector_db.DTOs.URLMapping import URLMapping
@@ -11,6 +12,7 @@
1112
from core.DTOs.GetNextURLForAgencyAnnotationResponse import URLAgencyAnnotationPostInfo
1213
from core.DTOs.RecordTypeAnnotationPostInfo import RecordTypeAnnotationPostInfo
1314
from core.DTOs.RelevanceAnnotationPostInfo import RelevanceAnnotationPostInfo
15+
from core.classes.ErrorManager import ErrorTypes
1416
from core.enums import RecordType, SuggestionType
1517
from core.exceptions import FailedValidationException
1618
from tests.helpers.complex_test_data_functions import AnnotateAgencySetupInfo, setup_for_annotate_agency, \
@@ -130,6 +132,36 @@ async def test_annotate_relevancy(api_test_helper):
130132
assert results[0].relevant is True
131133

132134

135+
@pytest.mark.asyncio
136+
async def test_annotate_relevancy_already_annotated_by_different_user(
137+
api_test_helper
138+
):
139+
ath = api_test_helper
140+
141+
creation_info: BatchURLCreationInfo = await ath.db_data_creator.batch_and_urls(
142+
url_count=1
143+
)
144+
145+
await ath.db_data_creator.user_relevant_suggestion(
146+
url_id=creation_info.url_ids[0],
147+
user_id=2,
148+
relevant=True
149+
)
150+
151+
# Annotate with different user (default is 1) and get conflict error
152+
try:
153+
response = await ath.request_validator.post_relevance_annotation_and_get_next(
154+
url_id=creation_info.url_ids[0],
155+
relevance_annotation_post_info=RelevanceAnnotationPostInfo(
156+
is_relevant=False
157+
)
158+
)
159+
except HTTPException as e:
160+
assert e.status_code == HTTPStatus.CONFLICT
161+
assert e.detail["detail"]["code"] == ErrorTypes.ANNOTATION_EXISTS.value
162+
assert e.detail["detail"]["message"] == f"Annotation of type RELEVANCE already exists for url {creation_info.url_ids[0]}"
163+
164+
133165
@pytest.mark.asyncio
134166
async def test_annotate_relevancy_no_html(api_test_helper):
135167
ath = api_test_helper
@@ -250,6 +282,36 @@ async def test_annotate_record_type(api_test_helper):
250282
if result.url_id == inner_info_1.url_info.url_id:
251283
assert result.record_type == RecordType.BOOKING_REPORTS.value
252284

285+
@pytest.mark.asyncio
286+
async def test_annotate_record_type_already_annotated_by_different_user(
287+
api_test_helper
288+
):
289+
ath = api_test_helper
290+
291+
creation_info: BatchURLCreationInfo = await ath.db_data_creator.batch_and_urls(
292+
url_count=1
293+
)
294+
295+
await ath.db_data_creator.user_record_type_suggestion(
296+
url_id=creation_info.url_ids[0],
297+
user_id=2,
298+
record_type=RecordType.ACCIDENT_REPORTS
299+
)
300+
301+
# Annotate with different user (default is 1) and get conflict error
302+
try:
303+
response = await ath.request_validator.post_record_type_annotation_and_get_next(
304+
url_id=creation_info.url_ids[0],
305+
record_type_annotation_post_info=RecordTypeAnnotationPostInfo(
306+
record_type=RecordType.ANNUAL_AND_MONTHLY_REPORTS
307+
)
308+
)
309+
except HTTPException as e:
310+
assert e.status_code == HTTPStatus.CONFLICT
311+
assert e.detail["detail"]["code"] == ErrorTypes.ANNOTATION_EXISTS.value
312+
assert e.detail["detail"]["message"] == f"Annotation of type RECORD_TYPE already exists for url {creation_info.url_ids[0]}"
313+
314+
253315
@pytest.mark.asyncio
254316
async def test_annotate_record_type_no_html_info(api_test_helper):
255317
ath = api_test_helper

0 commit comments

Comments
 (0)