Skip to content

Commit 0db4e67

Browse files
itallixjpggvilaca
andauthored
Simplify UUID validation with type annotations (#5027)
Co-authored-by: Joao Vilaca <[email protected]>
1 parent bb080ae commit 0db4e67

File tree

16 files changed

+196
-239
lines changed

16 files changed

+196
-239
lines changed

application/backend/app/api/dependencies.py

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
from collections.abc import Generator
55
from pathlib import Path
66
from typing import Annotated
7-
from uuid import UUID
87

98
from fastapi import Depends, HTTPException, Request, UploadFile, status
109
from sqlalchemy.orm import Session
1110

11+
from app.api.validators import ProjectID, SinkID, SourceID
1212
from app.core.jobs.control_plane import JobQueue
1313
from app.db import get_db_session
1414
from app.models import Sink, Source
1515
from app.scheduler import Scheduler
1616
from app.schemas import ProjectView
1717
from app.services import (
18+
BaseWeightsService,
1819
DatasetService,
20+
LabelService,
1921
MetricsService,
2022
ModelService,
2123
PipelineMetricsService,
@@ -26,41 +28,12 @@
2628
SourceUpdateService,
2729
SystemService,
2830
)
29-
from app.services.base_weights_service import BaseWeightsService
3031
from app.services.data_collect import DataCollector
3132
from app.services.event.event_bus import EventBus
32-
from app.services.label_service import LabelService
3333
from app.services.training_configuration_service import TrainingConfigurationService
3434
from app.webrtc.manager import WebRTCManager
3535

3636

37-
def is_valid_uuid(identifier: str) -> bool:
38-
"""
39-
Check if a given string identifier is formatted as a valid UUID.
40-
41-
Args:
42-
identifier: String to check for UUID validity.
43-
44-
Returns:
45-
bool: True if the string is a valid UUID, False otherwise.
46-
47-
Example:
48-
>>> is_valid_uuid("550e8400-e29b-41d4-a716-446655440000")
49-
True
50-
>>> is_valid_uuid("invalid-uuid")
51-
False
52-
53-
Note:
54-
This function only validates the UUID format, not whether the UUID
55-
actually exists in any system or database.
56-
"""
57-
try:
58-
UUID(identifier)
59-
except ValueError:
60-
return False
61-
return True
62-
63-
6437
def get_file_name_and_extension(file: UploadFile) -> tuple[str, str]:
6538
"""Return the file name and extension"""
6639
if not file.filename:
@@ -83,41 +56,6 @@ def get_file_size(file: UploadFile) -> int:
8356
return file.size
8457

8558

86-
def get_source_id(source_id: str) -> UUID:
87-
"""Initializes and validates a source ID"""
88-
if not is_valid_uuid(source_id):
89-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid source ID")
90-
return UUID(source_id)
91-
92-
93-
def get_project_id(project_id: str) -> UUID:
94-
"""Initializes and validates a project ID"""
95-
if not is_valid_uuid(project_id):
96-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project ID")
97-
return UUID(project_id)
98-
99-
100-
def get_sink_id(sink_id: str) -> UUID:
101-
"""Initializes and validates a sink ID"""
102-
if not is_valid_uuid(sink_id):
103-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid sink ID")
104-
return UUID(sink_id)
105-
106-
107-
def get_model_id(model_id: str) -> UUID:
108-
"""Initializes and validates a model ID"""
109-
if not is_valid_uuid(model_id):
110-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid model ID")
111-
return UUID(model_id)
112-
113-
114-
def get_dataset_item_id(dataset_item_id: str) -> UUID:
115-
"""Initializes and validates a dataset item ID"""
116-
if not is_valid_uuid(dataset_item_id):
117-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid dataset item ID")
118-
return UUID(dataset_item_id)
119-
120-
12159
def get_db() -> Generator[Session]:
12260
"""Provides a database session."""
12361
with get_db_session() as session:
@@ -231,7 +169,7 @@ def get_dataset_service(
231169

232170

233171
def get_project(
234-
project_id: Annotated[UUID, Depends(get_project_id)],
172+
project_id: ProjectID,
235173
project_service: Annotated[ProjectService, Depends(get_project_service)],
236174
) -> ProjectView:
237175
"""Provides a ProjectView instance for request scoped project."""
@@ -242,7 +180,7 @@ def get_project(
242180

243181

244182
def get_sink(
245-
sink_id: Annotated[UUID, Depends(get_sink_id)],
183+
sink_id: SinkID,
246184
sink_service: Annotated[SinkService, Depends(get_sink_service)],
247185
) -> Sink:
248186
"""Provides a Sink instance for request scoped sink."""
@@ -253,7 +191,7 @@ def get_sink(
253191

254192

255193
def get_source(
256-
source_id: Annotated[UUID, Depends(get_source_id)],
194+
source_id: SourceID,
257195
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
258196
) -> Source:
259197
"""Provides a Source instance for request scoped source."""

application/backend/app/api/routers/datasets.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
from fastapi.openapi.models import Example
99
from starlette.responses import FileResponse
1010

11-
from app.api.dependencies import get_dataset_item_id, get_dataset_service, get_file_name_and_extension, get_project
11+
from app.api.dependencies import get_dataset_service, get_file_name_and_extension, get_project
1212
from app.api.schemas.dataset_item import (
1313
DatasetItemAnnotations,
1414
DatasetItemAssignSubset,
1515
DatasetItemsWithPagination,
1616
DatasetItemView,
1717
SetDatasetItemAnnotations,
1818
)
19+
from app.api.validators import DatasetItemID
1920
from app.core.models import Pagination
2021
from app.models import DatasetItemAnnotationStatus, DatasetItemSubset
2122
from app.schemas import ProjectView
@@ -159,7 +160,7 @@ def list_dataset_items( # noqa: PLR0913
159160
)
160161
def get_dataset_item(
161162
project: Annotated[ProjectView, Depends(get_project)],
162-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
163+
dataset_item_id: DatasetItemID,
163164
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
164165
) -> DatasetItemView:
165166
"""Get information about a specific dataset item"""
@@ -180,7 +181,7 @@ def get_dataset_item(
180181
)
181182
def get_dataset_item_binary(
182183
project: Annotated[ProjectView, Depends(get_project)],
183-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
184+
dataset_item_id: DatasetItemID,
184185
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
185186
) -> FileResponse:
186187
"""Get dataset item binary content"""
@@ -203,7 +204,7 @@ def get_dataset_item_binary(
203204
)
204205
def get_dataset_item_thumbnail(
205206
project: Annotated[ProjectView, Depends(get_project)],
206-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
207+
dataset_item_id: DatasetItemID,
207208
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
208209
) -> FileResponse:
209210
"""Get dataset item thumbnail binary content"""
@@ -227,7 +228,7 @@ def get_dataset_item_thumbnail(
227228
)
228229
def delete_dataset_item(
229230
project: Annotated[ProjectView, Depends(get_project)],
230-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
231+
dataset_item_id: DatasetItemID,
231232
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
232233
) -> None:
233234
"""Delete an item from the dataset"""
@@ -248,7 +249,7 @@ def delete_dataset_item(
248249
)
249250
def set_dataset_item_annotations(
250251
project: Annotated[ProjectView, Depends(get_project)],
251-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
252+
dataset_item_id: DatasetItemID,
252253
dataset_item_annotations: Annotated[
253254
SetDatasetItemAnnotations, Body(openapi_examples=SET_DATASET_ITEM_ANNOTATIONS_BODY_EXAMPLES)
254255
],
@@ -283,7 +284,7 @@ def set_dataset_item_annotations(
283284
)
284285
def get_dataset_item_annotations(
285286
project: Annotated[ProjectView, Depends(get_project)],
286-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
287+
dataset_item_id: DatasetItemID,
287288
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
288289
) -> DatasetItemAnnotations:
289290
"""Get the dataset item annotations"""
@@ -313,7 +314,7 @@ def get_dataset_item_annotations(
313314
)
314315
def delete_dataset_item_annotation(
315316
project: Annotated[ProjectView, Depends(get_project)],
316-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
317+
dataset_item_id: DatasetItemID,
317318
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
318319
) -> None:
319320
"""Delete dataset item annotations"""
@@ -336,7 +337,7 @@ def delete_dataset_item_annotation(
336337
)
337338
def assign_dataset_item_subset(
338339
project: Annotated[ProjectView, Depends(get_project)],
339-
dataset_item_id: Annotated[UUID, Depends(get_dataset_item_id)],
340+
dataset_item_id: DatasetItemID,
340341
dataset_service: Annotated[DatasetService, Depends(get_dataset_service)],
341342
subset_config: Annotated[DatasetItemAssignSubset, Body()],
342343
) -> DatasetItemView:

application/backend/app/api/routers/models.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
from typing import Annotated
5-
from uuid import UUID
65

76
from fastapi import APIRouter, Depends, HTTPException, status
87

9-
from app.api.dependencies import get_model_id, get_model_service, get_project_id
10-
from app.schemas import Model
8+
from app.api.dependencies import get_model_service, get_project
9+
from app.api.validators import ModelID
10+
from app.schemas import Model, ProjectView
1111
from app.services import ModelService, ResourceInUseError, ResourceNotFoundError
1212

1313
router = APIRouter(prefix="/api/projects/{project_id}/models", tags=["Models"])
@@ -23,12 +23,12 @@
2323
},
2424
)
2525
def list_models(
26-
project_id: Annotated[UUID, Depends(get_project_id)],
26+
project: Annotated[ProjectView, Depends(get_project)],
2727
model_service: Annotated[ModelService, Depends(get_model_service)],
2828
) -> list[Model]:
2929
"""Get all models in a project."""
3030
try:
31-
return model_service.list_models(project_id)
31+
return model_service.list_models(project.id)
3232
except ResourceNotFoundError:
3333
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
3434

@@ -43,13 +43,13 @@ def list_models(
4343
},
4444
)
4545
def get_model(
46-
project_id: Annotated[UUID, Depends(get_project_id)],
47-
model_id: Annotated[UUID, Depends(get_model_id)],
46+
project: Annotated[ProjectView, Depends(get_project)],
47+
model_id: ModelID,
4848
model_service: Annotated[ModelService, Depends(get_model_service)],
4949
) -> Model:
5050
"""Get a specific model by ID."""
5151
try:
52-
return model_service.get_model_by_id(project_id=project_id, model_id=model_id)
52+
return model_service.get_model(project_id=project.id, model_id=model_id)
5353
except ResourceNotFoundError as e:
5454
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
5555

@@ -67,13 +67,13 @@ def get_model(
6767
},
6868
)
6969
def delete_model(
70-
project_id: Annotated[UUID, Depends(get_project_id)],
71-
model_id: Annotated[UUID, Depends(get_model_id)],
70+
project: Annotated[ProjectView, Depends(get_project)],
71+
model_id: ModelID,
7272
model_service: Annotated[ModelService, Depends(get_model_service)],
7373
) -> None:
7474
"""Delete a model from a project."""
7575
try:
76-
model_service.delete_model_by_id(project_id=project_id, model_id=model_id)
76+
model_service.delete_model(project_id=project.id, model_id=model_id)
7777
except ResourceNotFoundError as e:
7878
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
7979
except ResourceInUseError as e:

application/backend/app/api/routers/pipelines.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"""Endpoints for managing pipelines"""
55

66
from typing import Annotated
7-
from uuid import UUID
87

98
from fastapi import APIRouter, Body, Depends, status
109
from fastapi.exceptions import HTTPException
1110
from fastapi.openapi.models import Example
1211
from pydantic import ValidationError
1312

14-
from app.api.dependencies import get_pipeline_metrics_service, get_pipeline_service, get_project_id
13+
from app.api.dependencies import get_pipeline_metrics_service, get_pipeline_service
14+
from app.api.validators import ProjectID
1515
from app.schemas.metrics import PipelineMetrics
1616
from app.schemas.pipeline import DataCollectionPolicyAdapter, PipelineStatus, PipelineView
1717
from app.services import PipelineMetricsService, PipelineService, ResourceNotFoundError
@@ -70,7 +70,7 @@
7070
},
7171
)
7272
def get_pipeline(
73-
project_id: Annotated[UUID, Depends(get_project_id)],
73+
project_id: ProjectID,
7474
pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)],
7575
) -> PipelineView:
7676
"""Get info about a given pipeline"""
@@ -91,7 +91,7 @@ def get_pipeline(
9191
},
9292
)
9393
def update_pipeline(
94-
project_id: Annotated[UUID, Depends(get_project_id)],
94+
project_id: ProjectID,
9595
pipeline_config: Annotated[
9696
dict,
9797
Body(
@@ -128,7 +128,7 @@ def update_pipeline(
128128
},
129129
)
130130
def enable_pipeline(
131-
project_id: Annotated[UUID, Depends(get_project_id)],
131+
project_id: ProjectID,
132132
pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)],
133133
) -> None:
134134
"""
@@ -153,7 +153,7 @@ def enable_pipeline(
153153
},
154154
)
155155
def disable_pipeline(
156-
project_id: Annotated[UUID, Depends(get_project_id)],
156+
project_id: ProjectID,
157157
pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)],
158158
) -> None:
159159
"""Stop a pipeline. The pipeline will become idle, and it won't process any data until re-enabled."""
@@ -173,7 +173,7 @@ def disable_pipeline(
173173
},
174174
)
175175
def get_project_metrics(
176-
project_id: Annotated[UUID, Depends(get_project_id)],
176+
project_id: ProjectID,
177177
pipeline_metrics_service: Annotated[PipelineMetricsService, Depends(get_pipeline_metrics_service)],
178178
time_window: int = 60,
179179
) -> PipelineMetrics:

0 commit comments

Comments
 (0)