-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
478 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# flake8: noqa | ||
from fugue.rpc.base import RPCClient, RPCServer, make_rpc_server | ||
from fugue.rpc.collections import RPCFuncDict, to_rpc_func_dict |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from abc import ABC, abstractmethod | ||
from threading import RLock | ||
from typing import Any, Callable, Dict | ||
from uuid import uuid4 | ||
|
||
from fugue.rpc.collections import RPCFuncDict, to_rpc_func_dict | ||
from triad import ParamDict, assert_or_throw | ||
from triad.utils.convert import to_type | ||
import pickle | ||
|
||
|
||
class RPCClient(object): | ||
def __call__(self, method: str, value: str) -> str: # pragma: no cover | ||
raise NotImplementedError | ||
|
||
|
||
class RPCServer(ABC): | ||
def __init__(self, conf: Any): | ||
self._lock = RLock() | ||
self._conf = ParamDict(conf) | ||
self._services: Dict[str, RPCFuncDict] = {} | ||
self._running = 0 | ||
|
||
@property | ||
def conf(self) -> ParamDict: | ||
return self._conf | ||
|
||
@abstractmethod | ||
def make_client(self, methods: Dict[str, Callable[[str], str]]) -> RPCClient: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@abstractmethod | ||
def start_server(self) -> None: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@abstractmethod | ||
def stop_server(self) -> None: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
def invoke(self, key: str, method: str, value: str) -> str: | ||
with self._lock: | ||
handler = self._services[key][method] | ||
return handler(value) | ||
|
||
def add_methods(self, methods: Dict[str, Callable[[str], str]]) -> str: | ||
with self._lock: | ||
key = "_" + str(uuid4()).split("-")[-1] | ||
self._services[key] = to_rpc_func_dict(methods) | ||
return key | ||
|
||
def start(self) -> "RPCServer": | ||
with self._lock: | ||
if self._running == 0: | ||
self.start_server() | ||
self._running += 1 | ||
return self | ||
|
||
def stop(self) -> None: | ||
with self._lock: | ||
if self._running == 1: | ||
self.stop_server() | ||
self._running -= 1 | ||
if self._running < 0: | ||
self._running = 0 | ||
|
||
def __enter__(self) -> "RPCServer": | ||
with self._lock: | ||
assert_or_throw(self._running, "use `with <instance>.start():` instead") | ||
return self | ||
|
||
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: | ||
self.stop() | ||
|
||
def __getstate__(self): | ||
raise pickle.PicklingError(f"{self} is not serializable") | ||
|
||
|
||
class NativeRPCClient(RPCClient): | ||
def __init__(self, server: "NativeRPCServer", key: str): | ||
self._key = key | ||
self._server = server | ||
|
||
def __call__(self, method: str, value: str) -> str: | ||
return self._server.invoke(self._key, method, value) | ||
|
||
def __getstate__(self): | ||
raise pickle.PicklingError(f"{self} is not serializable") | ||
|
||
|
||
class NativeRPCServer(RPCServer): | ||
def __init__(self, conf: Any): | ||
super().__init__(conf) | ||
|
||
def make_client(self, methods: Dict[str, Callable[[str], str]]) -> RPCClient: | ||
key = self.add_methods(methods) | ||
return NativeRPCClient(self, key) | ||
|
||
def start_server(self) -> None: | ||
return | ||
|
||
def stop_server(self) -> None: | ||
return | ||
|
||
|
||
def make_rpc_server(conf: Any) -> RPCServer: | ||
conf = ParamDict(conf) | ||
tp = conf.get_or_none("fugue.rpc.server", str) | ||
t_server = NativeRPCServer if tp is None else to_type(tp, RPCServer) | ||
return t_server(conf) # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from typing import Any, Callable, Iterable, Tuple, Dict | ||
from uuid import uuid4 | ||
|
||
from triad import IndexedOrderedDict, assert_or_throw | ||
|
||
|
||
class RPCFuncDict(IndexedOrderedDict[str, Callable[[str], str]]): | ||
def __init__(self, data: Dict[str, Callable[[str], str]]): | ||
if isinstance(data, RPCFuncDict): | ||
super().__init__(data) | ||
self._uuid = data.__uuid__() | ||
else: | ||
super().__init__(self.get_tuples(data)) | ||
self._uuid = "" if len(self) == 0 else str(uuid4()) | ||
self.set_readonly() | ||
|
||
def __uuid__(self) -> str: | ||
return self._uuid | ||
|
||
def get_tuples( | ||
self, data: Dict[str, Callable[[str], str]] | ||
) -> Iterable[Tuple[str, Callable[[str], str]]]: | ||
for k, v in sorted([(k, v) for k, v in data.items()], key=lambda p: p[0]): | ||
assert_or_throw(callable(v), ValueError(k, v)) | ||
yield k, v | ||
|
||
def __copy__(self) -> "RPCFuncDict": | ||
return self | ||
|
||
def __deepcopy__(self, memo: Any) -> "RPCFuncDict": | ||
return self | ||
|
||
|
||
def to_rpc_func_dict(obj: Any) -> RPCFuncDict: | ||
if isinstance(obj, RPCFuncDict): | ||
return obj | ||
return RPCFuncDict(obj) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from threading import Thread | ||
from typing import Any, Callable, Dict, Optional | ||
|
||
import requests | ||
from fugue.rpc.base import RPCClient, RPCServer | ||
from triad.utils.convert import to_timedelta | ||
from werkzeug.serving import make_server | ||
|
||
from flask import Flask, request | ||
|
||
|
||
class FlaskRPCServer(RPCServer): | ||
class _Thread(Thread): | ||
def __init__(self, app: Flask, host: str, port: int): | ||
super().__init__() | ||
self._srv = make_server(host, port, app) | ||
self._ctx = app.app_context() | ||
self._ctx.push() | ||
|
||
def run(self) -> None: | ||
self._srv.serve_forever() | ||
|
||
def shutdown(self) -> None: | ||
self._srv.shutdown() | ||
|
||
def __init__(self, conf: Any): | ||
super().__init__(conf) | ||
self._host = conf.get_or_throw("fugue.rpc.flask_server.host", str) | ||
self._port = conf.get_or_throw("fugue.rpc.flask_server.port", int) | ||
timeout = conf.get_or_none("fugue.rpc.flask_server.timeout", object) | ||
self._timeout_sec = ( | ||
-1.0 if timeout is None else to_timedelta(timeout).total_seconds() | ||
) | ||
self._server: Optional[FlaskRPCServer._Thread] = None | ||
|
||
def _invoke(self) -> str: | ||
key = request.form.get("key") | ||
method = request.form.get("method") | ||
value = request.form.get("value") | ||
return self.invoke(key, method, value) # type: ignore | ||
|
||
def make_client(self, methods: Dict[str, Callable[[str], str]]) -> RPCClient: | ||
key = self.add_methods(methods) | ||
return FlaskRPCClient( | ||
key, | ||
self._host, | ||
self._port, | ||
self._timeout_sec, | ||
) | ||
|
||
def start_server(self) -> None: | ||
app = Flask("FlaskRPCServer") | ||
app.route("/invoke", methods=["POST"])(self._invoke) | ||
self._server = FlaskRPCServer._Thread(app, self._host, self._port) | ||
self._server.start() | ||
|
||
def stop_server(self) -> None: | ||
if self._server is not None: | ||
self._server.shutdown() | ||
self._server.join() | ||
|
||
|
||
class FlaskRPCClient(RPCClient): | ||
def __init__(self, key: str, host: str, port: int, timeout_sec: float): | ||
self._url = f"http://{host}:{port}/invoke" | ||
self._timeout_sec = timeout_sec | ||
self._key = key | ||
|
||
def __call__(self, method: str, value: str) -> str: | ||
timeout: Any = None if self._timeout_sec <= 0 else self._timeout_sec | ||
res = requests.post( | ||
self._url, | ||
data=dict(key=self._key, method=method, value=value), | ||
timeout=timeout, | ||
) | ||
res.raise_for_status() | ||
return res.text |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.