Skip to content

Commit c5adb63

Browse files
committed
file handler and error handling
1 parent 80df41c commit c5adb63

22 files changed

Lines changed: 255 additions & 178 deletions

lf_toolkit/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import importlib.metadata as importlib_metadata
22

3-
from .rpc import IPCServer
4-
from .rpc import StdioServer
5-
from .rpc import create_server
6-
from .rpc import run
7-
from .rpc import serve
3+
from .io import FileServer
4+
from .io import IPCServer
5+
from .io import StdioServer
6+
from .io import create_server
7+
from .io import run
8+
from .io import serve
89

910

1011
__version__ = importlib_metadata.version("lf_toolkit")

lf_toolkit/evaluation/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from ..internal.models import Params
2-
from ..internal.models import SymbolDict
1+
from ..shared import Params
2+
from ..shared import SymbolDict
33
from .result import Result

lf_toolkit/internal/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
from .file_server import FileServer
12
from .ipc_server import IPCServer
23
from .serve import create_server
34
from .serve import run
4-
from .serve import serve
55
from .stdio_server import StdioServer

lf_toolkit/io/base_server.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from abc import ABC
2+
from abc import abstractmethod
3+
from functools import wraps
4+
from typing import Any
5+
from typing import Awaitable
6+
from typing import Callable
7+
from typing import Optional
8+
from typing import Union
9+
10+
from ..evaluation import Result as EvaluationResult
11+
from ..preview import Result as PreviewResult
12+
from ..shared import Params
13+
from .handler import Handler
14+
from .handler import JsonRpcHandler
15+
16+
17+
EvaluationFunction = Callable[
18+
[Any, Any, Params], Union[EvaluationResult, Awaitable[EvaluationResult]]
19+
]
20+
21+
PreviewFunction = Callable[
22+
[Any, Params], Union[PreviewResult, Awaitable[PreviewResult]]
23+
]
24+
25+
26+
class BaseServer(ABC):
27+
28+
_handler: Handler
29+
30+
def __init__(self, handler: Optional[Handler] = None):
31+
self._handler = handler if handler is not None else JsonRpcHandler()
32+
33+
@abstractmethod
34+
async def run(self):
35+
pass
36+
37+
async def dispatch(self, req: str) -> str:
38+
return await self._handler.dispatch(req)
39+
40+
def eval(self, fn: EvaluationFunction):
41+
return handler_decorator(self._handler, "eval", fn)
42+
43+
def preview(self, fn: PreviewFunction):
44+
return handler_decorator(self._handler, "preview", fn)
45+
46+
47+
def handler_decorator(registry: Handler, name: str, fn):
48+
@wraps(fn)
49+
def decorator():
50+
registry.register(name, fn)
51+
return fn
52+
53+
return decorator()

lf_toolkit/io/file_server.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from typing import Optional
2+
3+
import ujson
4+
5+
from anyio import open_file
6+
7+
from .base_server import BaseServer
8+
from .handler import Handler
9+
10+
11+
class FileHandler(Handler):
12+
13+
async def dispatch(self, req: str) -> str:
14+
request = ujson.loads(req)
15+
command = request.get("command", None)
16+
try:
17+
result = await self.handle(command, request)
18+
response = {"command": command, "result": result}
19+
except Exception as e:
20+
response = {"command": command, "error": str(e)}
21+
return ujson.dumps(response)
22+
23+
24+
class FileServer(BaseServer):
25+
26+
_request_file_path: str
27+
_response_file_path: str
28+
29+
def __init__(
30+
self,
31+
request_file_path: str,
32+
response_file_path: str,
33+
handler: Optional[Handler] = None,
34+
):
35+
super().__init__(handler if handler is not None else FileHandler())
36+
self._request_file_path = request_file_path
37+
self._response_file_path = response_file_path
38+
39+
async def run(self):
40+
async with await open_file(self._request_file_path, "r") as f:
41+
request = await f.read()
42+
43+
response = await self.dispatch(request)
44+
45+
async with await open_file(self._response_file_path, "w") as f:
46+
await f.write(response)

lf_toolkit/io/handler.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import asyncio
2+
3+
from abc import ABC
4+
from abc import abstractmethod
5+
from typing import Callable
6+
from typing import Dict
7+
8+
import anyio
9+
10+
from ..evaluation import Result as EvaluationResult
11+
from ..shared import Command
12+
from ..shared import Params
13+
14+
15+
class Handler(ABC):
16+
17+
_handlers: Dict[str, Callable] = {}
18+
19+
@abstractmethod
20+
async def dispatch(self, req: str) -> str:
21+
pass
22+
23+
def register(self, name: str, fn: Callable):
24+
self._handlers[name] = fn
25+
26+
async def _call_user_handler(self, req: str, *args, **kwargs):
27+
handler = self._handlers.get(req)
28+
29+
if handler is None:
30+
raise ValueError(f"No user handler for '{req}'")
31+
32+
try:
33+
if asyncio.iscoroutinefunction(handler):
34+
return await handler(*args, **kwargs)
35+
else:
36+
return await anyio.to_thread.run_sync(handler, *args, **kwargs)
37+
except Exception as e:
38+
raise ValueError(f"Error calling user handler for '{req}': {e}")
39+
40+
async def handle_eval(self, req: dict):
41+
response = req.get("response", None)
42+
answer = req.get("answer", None)
43+
params = Params(req.get("params", {}))
44+
45+
return await self._call_user_handler("eval", response, answer, params)
46+
47+
async def handle_preview(self, req: dict):
48+
response = req.get("response", None)
49+
params = Params(req.get("params", {}))
50+
51+
result = await self._call_user_handler("preview", response, params)
52+
53+
if isinstance(result, EvaluationResult):
54+
return result.to_dict()
55+
56+
return result
57+
58+
async def handle(self, name: Command, req: dict) -> dict:
59+
handler = getattr(self, f"handle_{name}", None)
60+
61+
if handler is None:
62+
raise ValueError(f"No handler for '{name}'")
63+
64+
return await handler(req)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def listen(self) -> AsyncGenerator[IPCClient, None]:
2222
while True:
2323
try:
2424
stream = await listener.accept()
25-
print("Client connected")
25+
# print("Client connected")
2626
yield SocketClient(stream)
2727
except Exception as e:
2828
print(f"Exception: {e}")

0 commit comments

Comments
 (0)