def __init__(self, logger: Logger, config: ClientConfig): self.__impl = PythonClientHelper(logger, config, self._process_message) self.__logger = LabeledLogger(logger, 'mprpc.client') self.__sync_request_timeout_ms = config.sync_request_timeout_ms self.__lock = Lock() self.__requests = dict() self.__next_msgid = 0
def test_labeled_logger_from_labeled_logger_without_label(): """test of LabeledLogger created from LabeledLogger object without label """ origin = MockLogger() label = 'test_logger' logger = LabeledLogger(LabeledLogger(origin, label)) assert logger.logger == origin assert logger.label == label logger.trace('test %s', 'log') assert origin.label == label
def __init__( self, logger: Logger, name: str, function: Callable, *, param_schemas: Optional[List[Optional[marshmallow.Schema]]] = None, result_schema: Optional[marshmallow.Schema] = None): super().__init__(name) self.__function = function self.__param_schemas = param_schemas self.__result_schema = result_schema self.__logger = LabeledLogger(logger, 'mprpc.method.' + name)
def test_labeled_logger_from_labeled_logger_with_label(): """test of LabeledLogger created from LabeledLogger object with label """ origin = MockLogger() label1 = 'test_logger' label2 = 'test' logger = LabeledLogger(LabeledLogger(origin, label1), label2) label = label1 + '.' + label2 assert logger.logger == origin assert logger.label == label logger.trace('test %s', 'log') assert origin.label == label
class Client: """class of clients logger : Logger logger config : ClientConfig client configuration """ def __init__(self, logger: Logger, config: ClientConfig): self.__impl = PythonClientHelper(logger, config, self._process_message) self.__logger = LabeledLogger(logger, 'mprpc.client') self.__sync_request_timeout_ms = config.sync_request_timeout_ms self.__lock = Lock() self.__requests = dict() self.__next_msgid = 0 def start(self): """start process """ self.__impl.start() def stop(self): """stop process """ self.__impl.stop() def async_request(self, method: str, *args) -> Future: """asynchronously request Parameters ---------- method : str method, remaining arguments are parameters to the method Returns ------- Future future """ with self.__lock: msgid = self.__next_msgid data = pack_request(msgid, method, args) future = Future() self.__impl.async_send(data, self._on_request_sent) future.set_running_or_notify_cancel() self.__next_msgid += 1 self.__requests[msgid] = future return future def _on_request_sent(self, err: ErrorInfo): """process on a request sent Parameters ---------- err : ErrorInfo error """ if not err: return self.__logger.error('%s', err) with self.__lock: for future in self.__requests.values(): future.set_exception(MPRPCException(err)) self.__requests = dict() def _process_message(self, err: ErrorInfo, data: MessageData): """process a message Parameters ---------- err : ErrorInfo error data : MessageData message data """ try: if err: raise MPRPCException(err) msg = validate_message(unpack_object(data)) if not isinstance(msg, Response): raise MPRPCException( ErrorInfo(ErrorCode.INVALID_MESSAGE, 'received a message which is not a response')) msgid = msg.msgid with self.__lock: if msgid not in self.__requests: self.__logger.warn( 'invalid msgid in received response: %d', msgid) future = self.__requests[msgid] del self.__requests[msgid] if msg.error: future.set_exception(ServerError(msg.error)) return future.set_result(msg.result) except MPRPCException as exc: self.__logger.error('%s', exc) with self.__lock: for future in self.__requests.values(): future.set_exception(MPRPCException(err)) self.__requests = dict() return def request(self, method: str, *args) -> Any: """request Parameters ---------- method : str method, remaining arguments are parameters to the method Returns ------- Any result """ timeout = self.__sync_request_timeout_ms * 1e-3 return self.async_request(method, *args).result(timeout=timeout) def notify(self, method: str, *args): """notify Parameters ---------- method : str method, remaining arguments are parameters to the method """ data = pack_notification(method, args) self.__impl.async_send(data, self._on_notification_sent) def _on_notification_sent(self, err: ErrorInfo): """process on notification sent Parameters ---------- err : ErrorInfo error """ if not err: return self.__logger.error('%s', err)
def __init__(self, logger: Logger, methods: List[MethodExecutor]): self.__logger = LabeledLogger(logger, 'mprpc.simple_method_server') self.__methods = {method.name: method for method in methods} self.__threads = ThreadPoolExecutor(1)
class SimpleMethodServer(MethodServer): """class of a simple server implementation to execute methods Parameters ---------- logger : Logger logger num_threads : int number of threads methods : List[MethodExecutor] list of methods """ def __init__(self, logger: Logger, methods: List[MethodExecutor]): self.__logger = LabeledLogger(logger, 'mprpc.simple_method_server') self.__methods = {method.name: method for method in methods} self.__threads = ThreadPoolExecutor(1) def async_process_message(self, session: Session, msg: MessageData, handler: Callable[[ErrorInfo, bool, MessageData], Any]): """asynchronously process message Parameters ---------- session : Session session msg : MessageData message handler : Callable[[ErrorInfo, bool, MessageData], Any] handler on message processed Parameters are error, flag whether a response exists, and response data (if exists). """ def done_callback(future: Future): """callback called after message is precessed Parameters ---------- future : Future future """ nonlocal handler error = ErrorInfo() response_data = None try: response_data = future.result() except MPRPCException as err: error = err.args[0] has_response = response_data is not None if not has_response: response_data = MessageData() handler(error, has_response, response_data) self.__threads.submit(self.process_message, session, msg).add_done_callback(done_callback) def process_message(self, session: Session, data: MessageData) -> MessageData: """process a message Parameters ---------- session : Session session data : MessageData message Returns ------- MessageData response data (if exists). """ try: msg = validate_message(unpack_object(data)) if isinstance(msg, Request): if msg.method not in self.__methods: self.__logger.error('method %s not found', msg.method) return pack_response(msg.msgid, error='method {} not found'.format( msg.method)) return self.__methods[msg.method].process_request(session, msg) if isinstance(msg, Notification): if msg.method not in self.__methods: self.__logger.error('method %s not found', msg.method) return None self.__methods[msg.method].process_notification(session, msg) return None raise MPRPCException( ErrorInfo(ErrorCode.INVALID_MESSAGE, 'message must be a request or response')) except MPRPCException as err: self.__logger.error('%s', err) raise except Exception as err: self.__logger.error('%s', err) raise MPRPCException( ErrorInfo(ErrorCode.UNEXPECTED_ERROR, 'error in server: {}'.format(err))) from err
def test_labeled_logger_from_logger_without_label(): """test of LabeledLogger created from logger without label """ origin = MockLogger() logger = LabeledLogger(origin) assert logger.logger == origin assert logger.label == 'mprpc' logger.trace('test %s', 'log') assert origin.label == 'mprpc' assert origin.filename == __file__ assert origin.line == 45 assert origin.function == 'test_labeled_logger_from_logger_without_label' assert origin.log_level == LogLevel.TRACE assert origin.message == 'test log' logger.debug('test %s', 'log') assert origin.log_level == LogLevel.DEBUG logger.info('test %s', 'log') assert origin.log_level == LogLevel.INFO logger.warn('test %s', 'log') assert origin.log_level == LogLevel.WARN logger.error('test %s', 'log') assert origin.log_level == LogLevel.ERROR logger.fatal('test %s', 'log') assert origin.log_level == LogLevel.FATAL
class FunctionMethodExecutor(MethodExecutor): """class of executors of methods written in functions Parameters ---------- logger : Logger logger name : str method name function : Callable function to call for method execution param_schemas : Optional[List[Optional[marshmallow.Schema]]], optional list of schemas for parameters, by default None to specify no validation. If a list is given, each element in the list is assumed to be None (no validation) or an object of a class inherited from marshmallow.Schema to use for validation. result_schema : Optional[marshmallow.Schema] schema for result of method, by default None to specify no serialization. If a schema is given, results of method executions for requests are serialized using this schema. """ def __init__( self, logger: Logger, name: str, function: Callable, *, param_schemas: Optional[List[Optional[marshmallow.Schema]]] = None, result_schema: Optional[marshmallow.Schema] = None): super().__init__(name) self.__function = function self.__param_schemas = param_schemas self.__result_schema = result_schema self.__logger = LabeledLogger(logger, 'mprpc.method.' + name) @property def function(self) -> Callable: """get function Returns ------- Callable function to call for method execution """ return self.__function def process_request(self, session: Session, msg: Request) -> MessageData: """process a request Parameters ---------- session : Session session msg : Request request message Returns ------- MessageData response message data """ try: params = validate_data_list(msg.params, self.__param_schemas) result = self.__function(*params) if self.__result_schema: result = self.__result_schema.dump(result) return pack_response(msg.msgid, result=result) except Exception as err: # pylint: disable=broad-except # all exceptions, which may be thrown by user cades, must be processed. self.__logger.error('exception thrown in method: %s', err) return pack_response(msg.msgid, error=str(err)) def process_notification(self, session: Session, msg: Notification): """process a notification Parameters ---------- session : Session session msg : Notification notification message """ try: params = validate_data_list(msg.params, self.__param_schemas) self.__function(*params) except Exception as err: # pylint: disable=broad-except # all exceptions, which may be thrown by user cades, must be processed. self.__logger.error('exception thrown in method: %s', err)