retuve.app.routes.api

API routes for the Retuve App.

  1# Copyright 2024 Adam McArthur
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14
 15"""
 16API routes for the Retuve App.
 17"""
 18
 19import json
 20import os
 21import shutil
 22
 23from fastapi import APIRouter, File, Request, UploadFile
 24from fastapi.responses import JSONResponse
 25
 26from retuve.app.classes import FeedbackRequest, SystemFiles
 27from retuve.app.helpers import RESULTS_DIR
 28from retuve.app.routes.live import dicom_processing_queue
 29from retuve.app.utils import validate_api_token
 30from retuve.keyphrases.config import Config
 31from retuve.logs import ulogger
 32from retuve.trak.data import extract_files
 33
 34router = APIRouter()
 35
 36
 37@router.post("/api/store_feedback/{keyphrase}")
 38async def store_feedback(
 39    request: Request, feedback_request: FeedbackRequest, keyphrase: str
 40):
 41    """
 42    Store feedback on a result.
 43
 44    :param feedback_request: The feedback request.
 45    :param keyphrase: The keyphrase to get right config.
 46
 47    :return: The status of the feedback storage.
 48    """
 49
 50    api_token = request.cookies.get("api_token")
 51    validate_api_token(api_token)
 52
 53    config = Config.get_config(keyphrase)
 54    file_id = feedback_request.file_id
 55    feedback = feedback_request.feedback
 56
 57    feedback_dir = f"{config.api.savedir}/{file_id}"
 58    feedback_file = f"{feedback_dir}/feedback.json"
 59    os.makedirs(feedback_dir, exist_ok=True)
 60    try:
 61        if os.path.exists(feedback_file):
 62            with open(feedback_file, "r") as file:
 63                feedback_list = json.load(file)
 64        else:
 65            feedback_list = []
 66        feedback_list.append({"comment": feedback})
 67        with open(feedback_file, "w") as file:
 68            json.dump(feedback_list, file)
 69        return {
 70            "status": "success",
 71            "message": "Feedback stored successfully.",
 72        }
 73    except Exception as e:
 74        ulogger.info(e)
 75        return {"status": "error", "message": str(e)}
 76
 77
 78@router.get("/api/get_feedback/{keyphrase}")
 79async def get_feedback(file_id: str, keyphrase: str, request: Request):
 80    """
 81    Get feedback on a result.
 82
 83    :param file_id: The file id.
 84    :param keyphrase: The keyphrase to get right config.
 85
 86    :return: The feedback on the result.
 87    """
 88
 89    api_token = request.cookies.get("api_token")
 90    validate_api_token(api_token)
 91
 92    config = Config.get_config(keyphrase)
 93    feedback_file = f"{config.api.savedir}/{file_id}/feedback.json"
 94    if os.path.exists(feedback_file):
 95        with open(feedback_file, "r") as file:
 96            feedback_list = json.load(file)
 97        return {"status": "success", "feedback": feedback_list}
 98    return {"status": "error", "message": "Feedback not found."}
 99
100
101@router.get("/api/get_metrics/{keyphrase}")
102async def get_metrics(file_id: str, keyphrase: str, request: Request):
103    """
104    Get metrics on a result.
105
106    :param file_id: The file id.
107    :param keyphrase: The keyphrase to get right config.
108    """
109
110    api_token = request.cookies.get("api_token")
111    validate_api_token(api_token)
112
113    config = Config.get_config(keyphrase)
114    metrics_file = f"{config.api.savedir}/{file_id}/metrics.json"
115    if os.path.exists(metrics_file):
116        with open(metrics_file, "r") as file:
117            metrics_list = json.load(file)
118            metrics_list["status"] = "success"
119        return metrics_list
120    return {"status": "error", "message": "metrics not found."}
121
122
123@router.post("/api/upload/{keyphrase}")
124async def handle_upload(
125    request: Request, keyphrase: str, file: UploadFile = File(...)
126):
127    """
128    Handle file uploads.
129
130    :param keyphrase: The keyphrase to get right config.
131    :param file: The file to upload.
132    """
133
134    api_token = request.cookies.get("api_token")
135    validate_api_token(api_token)
136
137    config = Config.get_config(keyphrase)
138
139    os.makedirs(config.api.upload_dir, exist_ok=True)
140    file_location = f"{config.api.upload_dir}/{file.filename}"
141    with open(file_location, "wb+") as file_object:
142        shutil.copyfileobj(file.file, file_object)
143
144    if config.api.zero_trust:
145        live_savedir = RESULTS_DIR
146    else:
147        live_savedir = config.api.savedir
148
149    # check if the file is a zip, and extract it
150    if file.filename.endswith(".zip"):
151        # create a temp folder in the upload dir
152        temp_dir = f"{config.api.upload_dir}/temp"
153
154        shutil.unpack_archive(file_location, temp_dir)
155
156        zip_name = file.filename.split(".")[0]
157
158        # add the zips name to each extracted file
159        for root, dirs, files in os.walk(temp_dir):
160            for _file in files:
161                os.rename(
162                    os.path.join(root, _file),
163                    os.path.join(root, f"{zip_name}_{_file}"),
164                )
165
166        # move the extracted files to the upload dir
167        for root, dirs, files in os.walk(temp_dir):
168            for _file in files:
169                shutil.move(
170                    os.path.join(root, _file),
171                    os.path.join(config.api.upload_dir, _file),
172                )
173
174                dicom_id = _file.split(".")[0]
175                await dicom_processing_queue.put(
176                    (dicom_id, None, config, live_savedir)
177                )
178
179        # delete the temp folder
180        shutil.rmtree(temp_dir)
181
182        # delete the zip file
183        os.remove(file_location)
184        return {
185            "info": f"file '{file.filename}' extracted at '{config.api.upload_dir}'"
186        }
187
188    dicom_id = file.filename.split(".")[0]
189    await dicom_processing_queue.put((dicom_id, None, config, live_savedir))
190
191    return {"info": f"file '{file.filename}' saved at '{file_location}'"}
192
193
194@router.post(
195    "/api/states/{keyphrase}",
196    response_model=SystemFiles,
197    responses={400: {"description": "Error"}},
198)
199@router.get(
200    "/api/states/{keyphrase}",
201    response_model=SystemFiles,
202    responses={400: {"description": "Error"}},
203)
204async def get_states(keyphrase: str, request: Request):
205    """
206    Get the states from the database.
207
208    :param keyphrase: The keyphrase to get right config.
209
210    :return: The states from the database.
211    """
212
213    api_token = request.cookies.get("api_token")
214    validate_api_token(api_token)
215
216    config = Config.get_config(keyphrase)
217    states = extract_files(config.api.db_path)
218    output_states = SystemFiles(states=states, length=len(states))
219
220    return output_states
221
222
223@router.get("/api/keyphrases")
224async def keyphrases(request: Request):
225    """
226    Get all valid keyphrases.
227
228    :return: The valid keyphrases.
229    """
230
231    api_token = request.cookies.get("api_token")
232    validate_api_token(api_token)
233
234    keyphrases = [keyphrase for keyphrase, _ in Config.get_configs()]
235    return JSONResponse(content={"keyphrases": keyphrases})
router = <fastapi.routing.APIRouter object>
@router.post('/api/store_feedback/{keyphrase}')
async def store_feedback( request: starlette.requests.Request, feedback_request: retuve.app.classes.FeedbackRequest, keyphrase: str):
38@router.post("/api/store_feedback/{keyphrase}")
39async def store_feedback(
40    request: Request, feedback_request: FeedbackRequest, keyphrase: str
41):
42    """
43    Store feedback on a result.
44
45    :param feedback_request: The feedback request.
46    :param keyphrase: The keyphrase to get right config.
47
48    :return: The status of the feedback storage.
49    """
50
51    api_token = request.cookies.get("api_token")
52    validate_api_token(api_token)
53
54    config = Config.get_config(keyphrase)
55    file_id = feedback_request.file_id
56    feedback = feedback_request.feedback
57
58    feedback_dir = f"{config.api.savedir}/{file_id}"
59    feedback_file = f"{feedback_dir}/feedback.json"
60    os.makedirs(feedback_dir, exist_ok=True)
61    try:
62        if os.path.exists(feedback_file):
63            with open(feedback_file, "r") as file:
64                feedback_list = json.load(file)
65        else:
66            feedback_list = []
67        feedback_list.append({"comment": feedback})
68        with open(feedback_file, "w") as file:
69            json.dump(feedback_list, file)
70        return {
71            "status": "success",
72            "message": "Feedback stored successfully.",
73        }
74    except Exception as e:
75        ulogger.info(e)
76        return {"status": "error", "message": str(e)}

Store feedback on a result.

Parameters
  • feedback_request: The feedback request.
  • keyphrase: The keyphrase to get right config.
Returns

The status of the feedback storage.

@router.get('/api/get_feedback/{keyphrase}')
async def get_feedback(file_id: str, keyphrase: str, request: starlette.requests.Request):
79@router.get("/api/get_feedback/{keyphrase}")
80async def get_feedback(file_id: str, keyphrase: str, request: Request):
81    """
82    Get feedback on a result.
83
84    :param file_id: The file id.
85    :param keyphrase: The keyphrase to get right config.
86
87    :return: The feedback on the result.
88    """
89
90    api_token = request.cookies.get("api_token")
91    validate_api_token(api_token)
92
93    config = Config.get_config(keyphrase)
94    feedback_file = f"{config.api.savedir}/{file_id}/feedback.json"
95    if os.path.exists(feedback_file):
96        with open(feedback_file, "r") as file:
97            feedback_list = json.load(file)
98        return {"status": "success", "feedback": feedback_list}
99    return {"status": "error", "message": "Feedback not found."}

Get feedback on a result.

Parameters
  • file_id: The file id.
  • keyphrase: The keyphrase to get right config.
Returns

The feedback on the result.

@router.get('/api/get_metrics/{keyphrase}')
async def get_metrics(file_id: str, keyphrase: str, request: starlette.requests.Request):
102@router.get("/api/get_metrics/{keyphrase}")
103async def get_metrics(file_id: str, keyphrase: str, request: Request):
104    """
105    Get metrics on a result.
106
107    :param file_id: The file id.
108    :param keyphrase: The keyphrase to get right config.
109    """
110
111    api_token = request.cookies.get("api_token")
112    validate_api_token(api_token)
113
114    config = Config.get_config(keyphrase)
115    metrics_file = f"{config.api.savedir}/{file_id}/metrics.json"
116    if os.path.exists(metrics_file):
117        with open(metrics_file, "r") as file:
118            metrics_list = json.load(file)
119            metrics_list["status"] = "success"
120        return metrics_list
121    return {"status": "error", "message": "metrics not found."}

Get metrics on a result.

Parameters
  • file_id: The file id.
  • keyphrase: The keyphrase to get right config.
@router.post('/api/upload/{keyphrase}')
async def handle_upload( request: starlette.requests.Request, keyphrase: str, file: fastapi.datastructures.UploadFile = File(PydanticUndefined)):
124@router.post("/api/upload/{keyphrase}")
125async def handle_upload(
126    request: Request, keyphrase: str, file: UploadFile = File(...)
127):
128    """
129    Handle file uploads.
130
131    :param keyphrase: The keyphrase to get right config.
132    :param file: The file to upload.
133    """
134
135    api_token = request.cookies.get("api_token")
136    validate_api_token(api_token)
137
138    config = Config.get_config(keyphrase)
139
140    os.makedirs(config.api.upload_dir, exist_ok=True)
141    file_location = f"{config.api.upload_dir}/{file.filename}"
142    with open(file_location, "wb+") as file_object:
143        shutil.copyfileobj(file.file, file_object)
144
145    if config.api.zero_trust:
146        live_savedir = RESULTS_DIR
147    else:
148        live_savedir = config.api.savedir
149
150    # check if the file is a zip, and extract it
151    if file.filename.endswith(".zip"):
152        # create a temp folder in the upload dir
153        temp_dir = f"{config.api.upload_dir}/temp"
154
155        shutil.unpack_archive(file_location, temp_dir)
156
157        zip_name = file.filename.split(".")[0]
158
159        # add the zips name to each extracted file
160        for root, dirs, files in os.walk(temp_dir):
161            for _file in files:
162                os.rename(
163                    os.path.join(root, _file),
164                    os.path.join(root, f"{zip_name}_{_file}"),
165                )
166
167        # move the extracted files to the upload dir
168        for root, dirs, files in os.walk(temp_dir):
169            for _file in files:
170                shutil.move(
171                    os.path.join(root, _file),
172                    os.path.join(config.api.upload_dir, _file),
173                )
174
175                dicom_id = _file.split(".")[0]
176                await dicom_processing_queue.put(
177                    (dicom_id, None, config, live_savedir)
178                )
179
180        # delete the temp folder
181        shutil.rmtree(temp_dir)
182
183        # delete the zip file
184        os.remove(file_location)
185        return {
186            "info": f"file '{file.filename}' extracted at '{config.api.upload_dir}'"
187        }
188
189    dicom_id = file.filename.split(".")[0]
190    await dicom_processing_queue.put((dicom_id, None, config, live_savedir))
191
192    return {"info": f"file '{file.filename}' saved at '{file_location}'"}

Handle file uploads.

Parameters
  • keyphrase: The keyphrase to get right config.
  • file: The file to upload.
@router.post('/api/states/{keyphrase}', response_model=SystemFiles, responses={400: {'description': 'Error'}})
@router.get('/api/states/{keyphrase}', response_model=SystemFiles, responses={400: {'description': 'Error'}})
async def get_states(keyphrase: str, request: starlette.requests.Request):
195@router.post(
196    "/api/states/{keyphrase}",
197    response_model=SystemFiles,
198    responses={400: {"description": "Error"}},
199)
200@router.get(
201    "/api/states/{keyphrase}",
202    response_model=SystemFiles,
203    responses={400: {"description": "Error"}},
204)
205async def get_states(keyphrase: str, request: Request):
206    """
207    Get the states from the database.
208
209    :param keyphrase: The keyphrase to get right config.
210
211    :return: The states from the database.
212    """
213
214    api_token = request.cookies.get("api_token")
215    validate_api_token(api_token)
216
217    config = Config.get_config(keyphrase)
218    states = extract_files(config.api.db_path)
219    output_states = SystemFiles(states=states, length=len(states))
220
221    return output_states

Get the states from the database.

Parameters
  • keyphrase: The keyphrase to get right config.
Returns

The states from the database.

@router.get('/api/keyphrases')
async def keyphrases(request: starlette.requests.Request):
224@router.get("/api/keyphrases")
225async def keyphrases(request: Request):
226    """
227    Get all valid keyphrases.
228
229    :return: The valid keyphrases.
230    """
231
232    api_token = request.cookies.get("api_token")
233    validate_api_token(api_token)
234
235    keyphrases = [keyphrase for keyphrase, _ in Config.get_configs()]
236    return JSONResponse(content={"keyphrases": keyphrases})

Get all valid keyphrases.

Returns

The valid keyphrases.