Skip to content

Commit a891f33

Browse files
CU-8699wvj3d refactor(medcat-service): Rewrite Service - Replace Flask with FastAPI framework (#57)
* CU-8699wvj3d refactor(medcat-service): Add FastAPI framework * CU-8699wvj3d refactor(medcat-service): Change existing tests to hit fastapi. Add Info API * CU-8699wvj3d refactor(medcat-service): Add retrain API though may delete later * refactor(medcat-service): Switch to FastAPI in startup scripts * refactor(medcat-service): Add /process API examples * CU-8699wvj3d refactor(medcat-service): Delete Flask code
1 parent 4b2b0b5 commit a891f33

25 files changed

+336
-396
lines changed

medcat-service/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The application can be run either as a standalone Python application or as runni
2929
Please note that prior running the application a number of requirements need to installed (see: `requirements.txt`).
3030

3131
There are two scripts provided implementing starting the application:
32-
- `start_service_debug.sh` - starts the application in the development mode and using `werkzeug` server for serving Flask applications,
32+
- `start_service_debug.sh` - starts the application in the development mode
3333
- `start_service_production.sh` - starts the application in 'production' mode and using `gunicorn` server.
3434

3535
## Running in a Docker container

medcat-service/medcat_service/api/__init__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

medcat-service/medcat_service/api/api.py

Lines changed: 0 additions & 147 deletions
This file was deleted.

medcat-service/medcat_service/app/__init__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

medcat-service/medcat_service/app/app.py

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from functools import lru_cache
2+
from typing import Annotated
3+
4+
from fastapi import Depends
5+
6+
from medcat_service.nlp_processor.medcat_processor import MedCatProcessor
7+
8+
9+
@lru_cache
10+
def get_medcat_processor() -> MedCatProcessor:
11+
return MedCatProcessor()
12+
13+
14+
MedCatProcessorDep = Annotated[MedCatProcessor, Depends(get_medcat_processor)]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import uvicorn
2+
from fastapi import FastAPI, Request
3+
from fastapi.responses import JSONResponse
4+
5+
from medcat_service.routers import admin, health, legacy, process
6+
from medcat_service.types import HealthCheckFailedException
7+
8+
app = FastAPI(
9+
title="MedCAT Service",
10+
summary="MedCAT Service",
11+
contact={
12+
"name": "CogStack Org",
13+
"url": "https://cogstack.org/",
14+
"email": "[email protected]",
15+
},
16+
license_info={
17+
"name": "Apache 2.0",
18+
"identifier": "Apache-2.0",
19+
},
20+
)
21+
22+
app.include_router(admin.router)
23+
app.include_router(health.router)
24+
app.include_router(process.router)
25+
app.include_router(legacy.router)
26+
27+
28+
@app.exception_handler(HealthCheckFailedException)
29+
async def healthcheck_failed_exception_handler(request: Request, exc: HealthCheckFailedException):
30+
return JSONResponse(status_code=503, content=exc.reason.model_dump())
31+
32+
33+
if __name__ == "__main__":
34+
uvicorn.run(app, host="0.0.0.0", port=8000)

medcat-service/medcat_service/nlp_processor/medcat_processor.py

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
from medcat_service.types import HealthCheckResponse, ModelCardInfo, ProcessErrorsResult, ProcessResult, ServiceInfo
1818

1919

20-
class NlpProcessor:
21-
"""
22-
This class defines an interface for NLP Processor
20+
class MedCatProcessor():
21+
""""
22+
MedCAT Processor class is wrapper over MedCAT that implements annotations extractions functionality
23+
(both single and bulk processing) that can be easily exposed for an API.
2324
"""
2425

2526
def __init__(self):
@@ -32,39 +33,6 @@ def __init__(self):
3233
self.log.debug("APP log level set to : " + str(app_log_level))
3334
self.log.debug("MedCAT log level set to : " + str(medcat_log_level))
3435

35-
def get_app_info(self):
36-
pass
37-
38-
def process_content(self, content, *args, **kwargs):
39-
pass
40-
41-
def process_content_bulk(self, content, *args, **kwargs):
42-
pass
43-
44-
def is_ready(self) -> HealthCheckResponse:
45-
return HealthCheckResponse(
46-
name="MedCAT",
47-
status="DOWN"
48-
)
49-
50-
@staticmethod
51-
def _get_timestamp():
52-
"""
53-
Returns the current timestamp in ISO 8601 format. Formatted as "yyyy-MM-dd"T"HH:mm:ss.SSSXXX".
54-
:return: timestamp string
55-
"""
56-
return datetime.now(tz=timezone.utc).isoformat(timespec="milliseconds")
57-
58-
59-
class MedCatProcessor(NlpProcessor):
60-
""""
61-
MedCAT Processor class is wrapper over MedCAT that implements annotations extractions functionality
62-
(both single and bulk processing) that can be easily exposed for an API.
63-
"""
64-
65-
def __init__(self):
66-
super().__init__()
67-
6836
self.log.info("Initializing MedCAT processor ...")
6937
self._is_ready_flag = False
7038

@@ -96,6 +64,14 @@ def __init__(self):
9664

9765
self._is_ready_flag = self._check_medcat_readiness()
9866

67+
@staticmethod
68+
def _get_timestamp():
69+
"""
70+
Returns the current timestamp in ISO 8601 format. Formatted as "yyyy-MM-dd"T"HH:mm:ss.SSSXXX".
71+
:return: timestamp string
72+
"""
73+
return datetime.now(tz=timezone.utc).isoformat(timespec="milliseconds")
74+
9975
def _check_medcat_readiness(self) -> bool:
10076
readiness_text = "MedCAT is ready and can get_entities"
10177
try:
@@ -171,7 +147,7 @@ def process_content(self, content, *args, **kwargs):
171147
nlp_result = ProcessErrorsResult(
172148
success=False,
173149
errors=[error_msg],
174-
timestamp=NlpProcessor._get_timestamp(),
150+
timestamp=self._get_timestamp(),
175151
)
176152

177153
return nlp_result
@@ -206,7 +182,7 @@ def process_content(self, content, *args, **kwargs):
206182
text=str(text),
207183
annotations=entities,
208184
success=True,
209-
timestamp=NlpProcessor._get_timestamp(),
185+
timestamp=self._get_timestamp(),
210186
elapsed_time=elapsed_time,
211187
footer=content.get("footer"),
212188
)
@@ -442,7 +418,7 @@ def _generate_result(self, in_documents, annotations, elapsed_time):
442418
text=str(in_ct["text"]),
443419
annotations=entities,
444420
success=True,
445-
timestamp=NlpProcessor._get_timestamp(),
421+
timestamp=self._get_timestamp(),
446422
elapsed_time=elapsed_time,
447423
footer=in_ct.get("footer"),
448424
)
@@ -452,7 +428,7 @@ def _generate_result(self, in_documents, annotations, elapsed_time):
452428
text=str(in_ct["text"]),
453429
annotations=[],
454430
success=True,
455-
timestamp=NlpProcessor._get_timestamp(),
431+
timestamp=self._get_timestamp(),
456432
elapsed_time=elapsed_time,
457433
footer=in_ct.get("footer"),
458434
)
@@ -463,7 +439,7 @@ def _generate_result(self, in_documents, annotations, elapsed_time):
463439
text=str(in_ct["text"]),
464440
annotations=[],
465441
success=True,
466-
timestamp=NlpProcessor._get_timestamp(),
442+
timestamp=self._get_timestamp(),
467443
elapsed_time=elapsed_time,
468444
footer=in_ct.get("footer"),
469445
)

0 commit comments

Comments
 (0)