Skip to content

Commit 804292b

Browse files
authored
Refactoring: Entities - Sources (#4978)
1 parent 917cfe4 commit 804292b

34 files changed

+734
-669
lines changed

application/backend/app/api/dependencies.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from app.core.jobs.control_plane import JobQueue
1313
from app.db import get_db_session
14-
from app.models import Sink
14+
from app.models import Sink, Source
1515
from app.scheduler import Scheduler
1616
from app.schemas import ProjectView
1717
from app.services import (
@@ -244,6 +244,17 @@ def get_sink(
244244
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
245245

246246

247+
def get_source(
248+
source_id: Annotated[UUID, Depends(get_source_id)],
249+
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
250+
) -> Source:
251+
"""Provides a Source instance for request scoped source."""
252+
try:
253+
return source_update_service.get_by_id(source_id)
254+
except ResourceNotFoundError as e:
255+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
256+
257+
247258
def get_base_weights_service(data_dir: Annotated[Path, Depends(get_data_dir)]) -> BaseWeightsService:
248259
"""Provides a BaseWeightsService instance for managing base weights."""
249260
return BaseWeightsService(data_dir)

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

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import logging
77
from typing import Annotated
8-
from uuid import UUID
98

109
import yaml
1110
from fastapi import APIRouter, Body, Depends, File, UploadFile, status
@@ -14,9 +13,9 @@
1413
from fastapi.responses import FileResponse, Response
1514
from pydantic import ValidationError
1615

17-
from app.api.dependencies import get_source_id, get_source_update_service
18-
from app.schemas import Source, SourceCreate
19-
from app.schemas.source import SourceCreateAdapter
16+
from app.api.dependencies import get_source, get_source_update_service
17+
from app.api.schemas.source import SourceCreate, SourceCreateAdapter, SourceView, SourceViewAdapter
18+
from app.models import Source
2019
from app.services import (
2120
ResourceInUseError,
2221
ResourceNotFoundError,
@@ -98,7 +97,7 @@
9897
@router.post(
9998
"",
10099
status_code=status.HTTP_201_CREATED,
101-
response_model=Source,
100+
response_model=SourceView,
102101
responses={
103102
status.HTTP_201_CREATED: {"description": "Source created"},
104103
status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or request body"},
@@ -110,57 +109,58 @@ def create_source(
110109
SourceCreate, Body(description=CREATE_SOURCE_BODY_DESCRIPTION, openapi_examples=CREATE_SOURCE_BODY_EXAMPLES)
111110
],
112111
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
113-
) -> Source:
112+
) -> SourceView:
114113
"""Create and configure a new source"""
115114
try:
116-
return source_update_service.create(source_create)
115+
source = source_update_service.create_source(
116+
name=source_create.name,
117+
source_type=source_create.source_type,
118+
config_data=source_create.config_data,
119+
source_id=source_create.id,
120+
)
121+
return SourceViewAdapter.validate_python(source, from_attributes=True)
117122
except (ResourceWithNameAlreadyExistsError, ResourceWithIdAlreadyExistsError) as e:
118123
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
119124

120125

121126
@router.get(
122127
"",
123128
responses={
124-
status.HTTP_200_OK: {"description": "List of available source configurations", "model": list[Source]},
129+
status.HTTP_200_OK: {"description": "List of available source configurations", "model": list[SourceView]},
125130
},
126131
)
127132
def list_sources(
128133
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
129-
) -> list[Source]:
134+
) -> list[SourceView]:
130135
"""List the available sources"""
131-
return source_update_service.list_all()
136+
sources = source_update_service.list_all()
137+
return [SourceViewAdapter.validate_python(source, from_attributes=True) for source in sources]
132138

133139

134140
@router.get(
135141
"/{source_id}",
136142
responses={
137-
status.HTTP_200_OK: {"description": "Source found", "model": Source},
143+
status.HTTP_200_OK: {"description": "Source found", "model": SourceView},
138144
status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID"},
139145
status.HTTP_404_NOT_FOUND: {"description": "Source not found"},
140146
},
141147
)
142-
def get_source(
143-
source_id: Annotated[UUID, Depends(get_source_id)],
144-
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
145-
) -> Source:
148+
def get_source_view(source: Annotated[Source, Depends(get_source)]) -> SourceView:
146149
"""Get info about a source"""
147-
try:
148-
return source_update_service.get_by_id(source_id)
149-
except ResourceNotFoundError as e:
150-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
150+
return SourceViewAdapter.validate_python(source, from_attributes=True)
151151

152152

153153
@router.patch(
154154
"/{source_id}",
155155
responses={
156-
status.HTTP_200_OK: {"description": "Source successfully updated", "model": Source},
156+
status.HTTP_200_OK: {"description": "Source successfully updated", "model": SourceView},
157157
status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or request body"},
158158
status.HTTP_404_NOT_FOUND: {"description": "Source not found"},
159159
status.HTTP_409_CONFLICT: {"description": "Source already exists"},
160160
},
161161
)
162162
def update_source(
163-
source_id: Annotated[UUID, Depends(get_source_id)],
163+
source: Annotated[Source, Depends(get_source)],
164164
source_config: Annotated[
165165
dict,
166166
Body(
@@ -169,13 +169,20 @@ def update_source(
169169
),
170170
],
171171
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
172-
) -> Source:
172+
) -> SourceView:
173173
"""Reconfigure an existing source"""
174174
if "source_type" in source_config:
175175
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="The 'source_type' field cannot be changed")
176+
176177
try:
177-
source = source_update_service.get_by_id(source_id)
178-
return source_update_service.update(source, source_config)
178+
updated_source: Source = source.model_copy(update=source_config)
179+
updated_source.config_data = updated_source.config_data.model_copy(update=source_config)
180+
source = source_update_service.update_source(
181+
source=source,
182+
new_name=updated_source.name,
183+
new_config_data=updated_source.config_data,
184+
)
185+
return SourceViewAdapter.validate_python(source, from_attributes=True)
179186
except ResourceNotFoundError as e:
180187
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
181188
except ResourceWithNameAlreadyExistsError as e:
@@ -196,29 +203,22 @@ def update_source(
196203
status.HTTP_404_NOT_FOUND: {"description": "Source not found"},
197204
},
198205
)
199-
def export_source(
200-
source_id: Annotated[UUID, Depends(get_source_id)],
201-
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
202-
) -> Response:
206+
def export_source(source: Annotated[Source, Depends(get_source)]) -> Response:
203207
"""Export a source to file"""
204-
source = source_update_service.get_by_id(source_id)
205-
if not source:
206-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Source with ID {source_id} not found")
207-
208208
yaml_content = yaml.safe_dump(source.model_dump(mode="json", exclude={"id"}))
209209

210210
return Response(
211211
content=yaml_content.encode("utf8"),
212212
media_type="application/x-yaml",
213-
headers={"Content-Disposition": f"attachment; filename=source_{source_id}.yaml"},
213+
headers={"Content-Disposition": f"attachment; filename=source_{source.id}.yaml"},
214214
)
215215

216216

217217
@router.post(
218218
":import",
219219
status_code=status.HTTP_201_CREATED,
220220
responses={
221-
status.HTTP_201_CREATED: {"description": "Source imported successfully", "model": Source},
221+
status.HTTP_201_CREATED: {"description": "Source imported successfully", "model": SourceView},
222222
status.HTTP_400_BAD_REQUEST: {"description": "Invalid YAML format "},
223223
status.HTTP_409_CONFLICT: {"description": "Source already exists"},
224224
status.HTTP_422_UNPROCESSABLE_ENTITY: {"description": "Validation error(s)"},
@@ -227,13 +227,19 @@ def export_source(
227227
def import_source(
228228
yaml_file: Annotated[UploadFile, File(description="YAML file containing the source configuration")],
229229
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
230-
) -> Source:
230+
) -> SourceView:
231231
"""Import a source from file"""
232232
try:
233233
yaml_content = yaml_file.file.read()
234234
source_data = yaml.safe_load(yaml_content)
235235
source_create = SourceCreateAdapter.validate_python(source_data)
236-
return source_update_service.create(source_create)
236+
source = source_update_service.create_source(
237+
name=source_create.name,
238+
source_type=source_create.source_type,
239+
config_data=source_create.config_data,
240+
source_id=source_create.id,
241+
)
242+
return SourceViewAdapter.validate_python(source, from_attributes=True)
237243
except yaml.YAMLError as e:
238244
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid YAML format: {str(e)}")
239245
except (ResourceWithNameAlreadyExistsError, ResourceWithIdAlreadyExistsError) as e:
@@ -255,12 +261,12 @@ def import_source(
255261
},
256262
)
257263
def delete_source(
258-
source_id: Annotated[UUID, Depends(get_source_id)],
264+
source: Annotated[Source, Depends(get_source)],
259265
source_update_service: Annotated[SourceUpdateService, Depends(get_source_update_service)],
260266
) -> None:
261267
"""Remove a source"""
262268
try:
263-
source_update_service.delete_by_id(source_id)
269+
source_update_service.delete_source(source)
264270
except ResourceNotFoundError as e:
265271
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
266272
except ResourceInUseError as e:

application/backend/app/api/schemas/sink.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@
1515
SinkType,
1616
WebhookSinkConfig,
1717
)
18-
from app.models.sink import FolderConfig, HttpHeaders, HttpMethod, MqttConfig, RosConfig, WebhookConfig
18+
from app.models.sink import (
19+
DisconnectedConfig,
20+
FolderConfig,
21+
HttpHeaders,
22+
HttpMethod,
23+
MqttConfig,
24+
RosConfig,
25+
WebhookConfig,
26+
)
1927

2028

2129
class DisconnectedSinkConfigView(DisconnectedSinkConfig):
30+
config_data: DisconnectedConfig = Field(exclude=True)
2231
model_config = {
2332
"json_schema_extra": {
2433
"example": {
@@ -34,6 +43,7 @@ class FolderSinkConfigView(FolderSinkConfig):
3443
config_data: FolderConfig = Field(exclude=True)
3544

3645
@computed_field
46+
@property
3747
def folder_path(self) -> str:
3848
return self.config_data.folder_path
3949

@@ -55,18 +65,22 @@ class MqttSinkConfigView(MqttSinkConfig):
5565
config_data: MqttConfig = Field(exclude=True)
5666

5767
@computed_field
68+
@property
5869
def broker_host(self) -> str:
5970
return self.config_data.broker_host
6071

6172
@computed_field
73+
@property
6274
def broker_port(self) -> int:
6375
return self.config_data.broker_port
6476

6577
@computed_field
78+
@property
6679
def topic(self) -> str:
6780
return self.config_data.topic
6881

6982
@computed_field
83+
@property
7084
def auth_required(self) -> bool:
7185
return self.config_data.auth_required
7286

@@ -90,6 +104,7 @@ class RosSinkConfigView(RosSinkConfig):
90104
config_data: RosConfig = Field(exclude=True)
91105

92106
@computed_field
107+
@property
93108
def topic(self) -> str:
94109
return self.config_data.topic
95110

@@ -110,18 +125,22 @@ class WebhookSinkConfigView(WebhookSinkConfig):
110125
config_data: WebhookConfig = Field(exclude=True)
111126

112127
@computed_field
128+
@property
113129
def webhook_url(self) -> str:
114130
return self.config_data.webhook_url
115131

116132
@computed_field
133+
@property
117134
def http_method(self) -> HttpMethod:
118135
return self.config_data.http_method
119136

120137
@computed_field
138+
@property
121139
def headers(self) -> HttpHeaders | None:
122140
return self.config_data.headers
123141

124142
@computed_field
143+
@property
125144
def timeout(self) -> int:
126145
return self.config_data.timeout
127146

0 commit comments

Comments
 (0)