Skip to content

Commit 713f13f

Browse files
authored
Feature/health (#7)
* Added healthcheck handler support to RPC framework * Updated healthcheck handler to return detailed status * Added standalone healthcheck runner with JSON output support * Simplified healthcheck handler by returning the result directly
1 parent 312746e commit 713f13f

3 files changed

Lines changed: 101 additions & 1 deletion

File tree

lf_toolkit/io/handler.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ async def handle_preview(self, req: dict):
5757

5858
return await self._call_user_handler("preview", response, request_params)
5959

60+
async def handle_healthcheck(self, req: dict):
61+
from .healthcheck import run_healthcheck
62+
return await anyio.to_thread.run_sync(run_healthcheck)
63+
6064
async def handle(self, name: Command, req: dict) -> dict:
6165
handler = getattr(self, f"handle_{name}", None)
6266

lf_toolkit/io/healthcheck.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import os
2+
import re
3+
import sys
4+
import time
5+
import unittest
6+
from typing import Any, List, TypedDict
7+
8+
from typing_extensions import NotRequired
9+
10+
11+
class JsonTestResult(TypedDict):
12+
name: str
13+
time: NotRequired[int]
14+
15+
16+
JsonTestResults = List[JsonTestResult]
17+
18+
19+
class HealthcheckJsonTestResult(TypedDict):
20+
tests_passed: bool
21+
successes: JsonTestResults
22+
failures: JsonTestResults
23+
errors: JsonTestResults
24+
25+
26+
class HealthcheckResult(unittest.TextTestResult):
27+
28+
def __init__(self, *args, **kwargs) -> None:
29+
super().__init__(*args, **kwargs)
30+
self.__path_re = re.compile(r"^[\.\/\w]+\.(\w+\.\w+)$")
31+
self.__successes_json: JsonTestResults = []
32+
self.__failures_json: JsonTestResults = []
33+
self.__errors_json: JsonTestResults = []
34+
35+
def _get_name(self, path: str) -> str:
36+
match = self.__path_re.match(path)
37+
return match.group(1) if match else "Unknown"
38+
39+
def startTest(self, test: unittest.TestCase) -> None:
40+
self._start_time = time.time()
41+
super().startTest(test)
42+
43+
def addSuccess(self, test: unittest.TestCase) -> None:
44+
elapsed = time.time() - self._start_time
45+
self.__successes_json.append(
46+
JsonTestResult(name=self._get_name(test.id()), time=round(1e6 * elapsed))
47+
)
48+
super().addSuccess(test)
49+
50+
def addFailure(self, test: unittest.TestCase, err: Any) -> None:
51+
self.__failures_json.append(JsonTestResult(name=self._get_name(test.id())))
52+
super().addFailure(test, err)
53+
54+
def addError(self, test: unittest.TestCase, err: Any) -> None:
55+
self.__errors_json.append(JsonTestResult(name=self._get_name(test.id())))
56+
super().addError(test, err)
57+
58+
def get_successes_json(self) -> JsonTestResults:
59+
return self.__successes_json
60+
61+
def get_failures_json(self) -> JsonTestResults:
62+
return self.__failures_json
63+
64+
def get_errors_json(self) -> JsonTestResults:
65+
return self.__errors_json
66+
67+
68+
class HealthcheckRunner(unittest.TextTestRunner):
69+
70+
def __init__(self, *args, **kwargs) -> None:
71+
super().__init__(resultclass=HealthcheckResult, *args, **kwargs)
72+
73+
def run(self, test) -> HealthcheckJsonTestResult:
74+
result: HealthcheckResult = super().run(test) # type: ignore
75+
return HealthcheckJsonTestResult(
76+
tests_passed=result.wasSuccessful(),
77+
successes=result.get_successes_json(),
78+
failures=result.get_failures_json(),
79+
errors=result.get_errors_json(),
80+
)
81+
82+
83+
def run_healthcheck() -> HealthcheckJsonTestResult:
84+
no_stream = open(os.devnull, "w")
85+
sys.stderr = no_stream
86+
87+
try:
88+
loader = unittest.TestLoader()
89+
suite = loader.discover(start_dir=".", pattern="*test*.py")
90+
runner = HealthcheckRunner(verbosity=0)
91+
result = runner.run(suite)
92+
finally:
93+
sys.stderr = sys.__stderr__
94+
no_stream.close()
95+
96+
return result

lf_toolkit/io/rpc_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class JsonRpcHandler(Handler):
1212

1313
def __init__(self):
1414
self._methods = {
15-
name: jsonrpc_handler(self, name) for name in ["eval", "preview"]
15+
name: jsonrpc_handler(self, name) for name in ["eval", "preview", "healthcheck"]
1616
}
1717

1818
async def dispatch(self, req: str) -> str:

0 commit comments

Comments
 (0)