Repository navigation
Expand file tree
/
Copy pathsample.py
More file actions
187 lines (165 loc) · 5.6 KB
/
Copy pathsample.py
File metadata and controls
187 lines (165 loc) · 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# ===============================================================================
# Copyright 2025 ross
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
from fastapi import APIRouter, Query, Response
from sqlalchemy.exc import IntegrityError, ProgrammingError
from starlette.status import HTTP_201_CREATED, HTTP_409_CONFLICT
from api.pagination import CustomPage
from core.dependencies import (
session_dependency,
admin_dependency,
editor_dependency,
viewer_dependency,
)
from db.sample import Sample
from schemas import ResourceNotFoundResponse
from schemas.sample import CreateSample, SampleResponse, UpdateSample
from services.crud_helper import model_patcher, model_deleter, model_adder
from services.exceptions_helper import PydanticStyleException
from services.sample_helper import (
get_db_samples,
get_sample_by_id_with_relationships,
)
router = APIRouter(
prefix="/sample",
tags=["sample"],
)
# TODO: add the following database validation handlers
# invalid sample_id
# invalid lexicon terms
# sample_date of the Sample model cannot be before the
# event_date of the FieldEvent model
def database_error_handler(
payload: CreateSample | UpdateSample,
error: IntegrityError | ProgrammingError,
) -> None:
"""
Handle errors raised by the database when adding or updating a sample.
"""
orig = getattr(error, "orig", None)
if hasattr(orig, "args") and orig.args and isinstance(orig.args[0], dict):
error_message = orig.args[0].get("M", "")
else:
error_message = str(orig or error)
if 'constraint "sample_sample_name_key"' in error_message:
detail = {
"loc": ["body", "sample_name"],
"msg": (
f"Sample with sample_name {payload.sample_name} " "already exists."
),
"type": "value_error",
"input": {"sample_name": payload.sample_name},
}
elif 'constraint "sample_field_activity_id_fkey"' in error_message:
detail = {
"loc": ["body", "field_activity_id"],
"msg": (
f"FieldActivity with ID {payload.field_activity_id} " "does not exist."
),
"type": "value_error",
"input": {"field_activity_id": payload.field_activity_id},
}
else:
detail = {
"loc": ["body"],
"msg": error_message,
"type": "value_error",
"input": {},
}
raise PydanticStyleException(
status_code=HTTP_409_CONFLICT,
detail=[detail],
)
# ============= Post =============================================
@router.post("", status_code=HTTP_201_CREATED)
def add_sample(
sample_data: CreateSample,
session: session_dependency,
user: admin_dependency,
) -> SampleResponse:
"""
Endpoint to add a sample.
"""
try:
# since this is only one instance N+1 is not a concern for
# FieldActivity, FieldEvent, and Thing
return model_adder(session, Sample, sample_data, user=user)
except (IntegrityError, ProgrammingError) as e:
database_error_handler(sample_data, e)
# ============= Update =============================================
@router.patch("/{sample_id}", summary="Update Sample")
def update_sample(
sample_id: int,
sample_data: UpdateSample,
session: session_dependency,
user: editor_dependency,
) -> SampleResponse | ResourceNotFoundResponse:
"""
Endpoint to update a sample.
"""
try:
# since this is only one instance N+1 is not a concern for
# FieldActivity, FieldEvent, and Thing
return model_patcher(
session,
Sample,
sample_id,
sample_data,
user=user,
)
except (IntegrityError, ProgrammingError) as e:
database_error_handler(sample_data, e)
# ============= Get =============================================
@router.get("", summary="Get Samples")
def get_samples(
session: session_dependency,
user: viewer_dependency,
thing_id: int | None = None,
sort: str = None,
order: str = None,
filter_: str = Query(alias="filter", default=None),
) -> CustomPage[SampleResponse]:
"""
Endpoint to retrieve samples.
"""
return get_db_samples(
session,
thing_id,
sort=sort,
order=order,
filter_=filter_,
)
@router.get("/{sample_id}", summary="Get Sample by ID")
def get_sample_by_id(
sample_id: int,
session: session_dependency,
user: viewer_dependency,
) -> SampleResponse | ResourceNotFoundResponse:
"""
Endpoint to retrieve a sample by its ID.
"""
return get_sample_by_id_with_relationships(session, sample_id)
# ======= DELETE ===============================================================
@router.delete(
"/{sample_id}",
summary="Delete Sample by ID",
)
def delete_sample_by_id(
sample_id: int,
session: session_dependency,
user: admin_dependency,
) -> Response:
return model_deleter(session, Sample, sample_id)
# # ============= EOF =============================================