Skip to content

Commit b2bda89

Browse files
Deprecate Service module
1 parent 7aee31e commit b2bda89

File tree

17 files changed

+224
-38
lines changed

17 files changed

+224
-38
lines changed

healthchain/gateway/api/app.py

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88
import logging
99
import importlib
1010
import inspect
11+
import os
12+
import signal
1113

1214
from datetime import datetime
1315
from fastapi import FastAPI, APIRouter, HTTPException, Request
1416
from fastapi.middleware.cors import CORSMiddleware
1517
from fastapi.middleware.wsgi import WSGIMiddleware
1618
from fastapi.exceptions import RequestValidationError
1719
from fastapi.responses import JSONResponse
20+
from contextlib import asynccontextmanager
21+
from termcolor import colored
1822

19-
from typing import Dict, Optional, Type, Union, Set, ForwardRef
23+
from typing import Dict, Optional, Type, Union, Set
2024

2125
from healthchain.gateway.core.base import BaseGateway
2226
from healthchain.gateway.events.dispatcher import EventDispatcher
@@ -25,10 +29,6 @@
2529
logger = logging.getLogger(__name__)
2630

2731

28-
# Forward reference for type hints
29-
HealthChainAPIRef = ForwardRef("HealthChainAPI")
30-
31-
3232
class HealthChainAPI(FastAPI):
3333
"""
3434
HealthChainAPI wraps FastAPI to provide healthcare-specific integrations.
@@ -83,6 +83,10 @@ def __init__(
8383
event_dispatcher: Optional event dispatcher to use (for testing/DI)
8484
**kwargs: Additional keyword arguments to pass to FastAPI
8585
"""
86+
# Set up the lifespan
87+
if "lifespan" not in kwargs:
88+
kwargs["lifespan"] = self.lifespan
89+
8690
super().__init__(
8791
title=title, description=description, version=version, **kwargs
8892
)
@@ -122,6 +126,13 @@ def __init__(
122126
# Register self as a dependency for get_app
123127
self.dependency_overrides[get_app] = lambda: self
124128

129+
# Add a shutdown route
130+
shutdown_router = APIRouter()
131+
shutdown_router.add_api_route(
132+
"/shutdown", self._shutdown, methods=["GET"], include_in_schema=False
133+
)
134+
self.include_router(shutdown_router)
135+
125136
def get_event_dispatcher(self) -> Optional[EventDispatcher]:
126137
"""Get the event dispatcher instance.
127138
@@ -233,7 +244,7 @@ def _add_gateway_routes(
233244
self.gateway_endpoints[gateway_name].add(
234245
f"{method}:{route_path}"
235246
)
236-
logger.info(
247+
logger.debug(
237248
f"Registered {method} route {route_path} for {gateway_name}"
238249
)
239250

@@ -257,7 +268,7 @@ def _add_gateway_routes(
257268
# Mount the WSGI app
258269
self.mount(mount_path, WSGIMiddleware(wsgi_app))
259270
self.gateway_endpoints[gateway_name].add(f"WSGI:{mount_path}")
260-
logger.info(f"Registered WSGI gateway {gateway_name} at {mount_path}")
271+
logger.debug(f"Registered WSGI gateway {gateway_name} at {mount_path}")
261272

262273
# Case 3: Gateway instances that are also APIRouters (like FHIRGateway)
263274
elif isinstance(gateway, APIRouter):
@@ -269,11 +280,11 @@ def _add_gateway_routes(
269280
self.gateway_endpoints[gateway_name].add(
270281
f"{method}:{route.path}"
271282
)
272-
logger.info(
283+
logger.debug(
273284
f"Registered {method} route {route.path} from {gateway_name} router"
274285
)
275286
else:
276-
logger.info(f"Registered {gateway_name} as router (routes unknown)")
287+
logger.debug(f"Registered {gateway_name} as router (routes unknown)")
277288

278289
elif not (
279290
hasattr(gateway, "get_routes")
@@ -282,15 +293,23 @@ def _add_gateway_routes(
282293
):
283294
logger.warning(f"Gateway {gateway_name} does not provide any routes")
284295

285-
def register_router(self, router: Union[APIRouter, Type, str], **options) -> None:
296+
def register_router(
297+
self, router: Union[APIRouter, Type, str, list], **options
298+
) -> None:
286299
"""
287-
Register a router with the API.
300+
Register one or more routers with the API.
288301
289302
Args:
290-
router: The router to register (can be an instance, class, or import path)
303+
router: The router(s) to register (can be an instance, class, import path, or list of any of these)
291304
**options: Options to pass to the router constructor or include_router
292305
"""
293306
try:
307+
# Handle list of routers
308+
if isinstance(router, list):
309+
for r in router:
310+
self.register_router(r, **options)
311+
return
312+
294313
# Case 1: Direct APIRouter instance
295314
if isinstance(router, APIRouter):
296315
self.include_router(router, **options)
@@ -403,6 +422,47 @@ async def _general_exception_handler(
403422
content={"detail": "Internal server error"},
404423
)
405424

425+
@asynccontextmanager
426+
async def lifespan(self, app: FastAPI):
427+
"""Lifecycle manager for the application."""
428+
self._startup()
429+
yield
430+
self._shutdown()
431+
432+
def _startup(self) -> None:
433+
"""Display startup information and log registered endpoints."""
434+
healthchain_ascii = r"""
435+
436+
__ __ ____ __ ________ _
437+
/ / / /__ ____ _/ / /_/ /_ / ____/ /_ ____ _(_)___
438+
/ /_/ / _ \/ __ `/ / __/ __ \/ / / __ \/ __ `/ / __ \
439+
/ __ / __/ /_/ / / /_/ / / / /___/ / / / /_/ / / / / /
440+
/_/ /_/\___/\__,_/_/\__/_/ /_/\____/_/ /_/\__,_/_/_/ /_/
441+
442+
""" # noqa: E501
443+
444+
colors = ["red", "yellow", "green", "cyan", "blue", "magenta"]
445+
for i, line in enumerate(healthchain_ascii.split("\n")):
446+
color = colors[i % len(colors)]
447+
print(colored(line, color))
448+
449+
# Log registered gateways and endpoints
450+
for name, gateway in self.gateways.items():
451+
endpoints = self.gateway_endpoints.get(name, set())
452+
for endpoint in endpoints:
453+
print(f"{colored('HEALTHCHAIN', 'green')}: {endpoint}")
454+
455+
print(
456+
f"{colored('HEALTHCHAIN', 'green')}: See more details at {colored(self.docs_url, 'magenta')}"
457+
)
458+
459+
def _shutdown(self):
460+
"""
461+
Shuts down server by sending a SIGTERM signal.
462+
"""
463+
os.kill(os.getpid(), signal.SIGTERM)
464+
return JSONResponse(content={"message": "Server is shutting down..."})
465+
406466

407467
def create_app(
408468
config: Optional[Dict] = None,

healthchain/gateway/protocols/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from .fhirgateway import FHIRGateway
1212
from .cdshooks import CDSHooksGateway
1313
from .notereader import NoteReaderGateway
14+
from .apiprotocol import ApiProtocol
1415

1516
__all__ = [
1617
"FHIRGateway",
1718
"CDSHooksGateway",
1819
"NoteReaderGateway",
20+
"ApiProtocol",
1921
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from enum import Enum
2+
3+
4+
class ApiProtocol(Enum):
5+
"""
6+
Enum defining the supported API protocols.
7+
8+
Available protocols:
9+
- soap: SOAP protocol
10+
- rest: REST protocol
11+
"""
12+
13+
soap = "SOAP"
14+
rest = "REST"

healthchain/gateway/protocols/notereader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
from healthchain.gateway.events.dispatcher import EHREvent, EHREventType
1818
from healthchain.gateway.core.base import BaseGateway
1919
from healthchain.gateway.events.dispatcher import EventDispatcher
20-
from healthchain.service.soap.epiccdsservice import CDSServices
20+
from healthchain.gateway.soap.epiccdsservice import CDSServices
2121
from healthchain.models.requests import CdaRequest
2222
from healthchain.models.responses.cdaresponse import CdaResponse
23-
from healthchain.service.soap.model.epicclientfault import ClientFault
24-
from healthchain.service.soap.model.epicserverfault import ServerFault
23+
from healthchain.gateway.soap.model.epicclientfault import ClientFault
24+
from healthchain.gateway.soap.model.epicserverfault import ServerFault
2525
from healthchain.gateway.api.protocols import SOAPGatewayProtocol
2626

2727
logger = logging.getLogger(__name__)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from typing import Callable
66

7-
from healthchain.service.soap.epiccdsservice import CDSServices
8-
from healthchain.service.soap.model import ClientFault, ServerFault
7+
from healthchain.gateway.soap.epiccdsservice import CDSServices
8+
from healthchain.gateway.soap.model import ClientFault, ServerFault
99

1010

1111
def start_wsgi(

0 commit comments

Comments
 (0)