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 unpack_object(data: MessageData) -> Any: """unpack data Parameters ---------- data : mprpc.message.MessageData data to unpack Returns ------- Any unpacked data Raises ------ mprpc.MPRPCException If msgpack package failed to unpack data. """ try: return unpackb(bytes(data)) except Exception as err: raise MPRPCException( ErrorInfo(ErrorCode.PARSE_ERROR, 'error in unpacking data', data)) from err
def pack_object(obj: Any) -> MessageData: """pack an object This function calls msgpack.packb function. Parameters ---------- obj : Any object to pack Returns ------- mprpc.message.MessageData packed bytes Raises ------ mprpc.MPRPCException If msgpack package failed to pack data. """ try: return MessageData(packb(obj)) except Exception as err: raise MPRPCException( ErrorInfo(ErrorCode.UNEXPECTED_ERROR, 'error in packing data: %s' % obj)) from err
def validate_data_list( data: Any, schemas: Optional[List[Optional[marshmallow.Schema]]] = None ) -> List[Any]: """validate a list of data This function repeately calls :py:func:`mprpc.message.validate_data` to validate a list of data, used for validation of parameters in request and notification messages. Parameters ---------- data : Any list of data to validate schemas : Optional[List[Optional[marshmallow.Schema]]], optional list of schemas, 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. Returns ------- List[Any] validated data Raises ------ mprpc.MPRPCException If data is invalid. """ if schemas is None: # no validation is needed return data if not isinstance(data, list): raise MPRPCException( ErrorInfo(ErrorCode.PARSE_ERROR, 'data is not a list: %s' % data)) if len(schemas) != len(data): raise MPRPCException( ErrorInfo(ErrorCode.PARSE_ERROR, 'invalid length of data: %s' % data)) return [ validate_data(data_elem, schema) for (data_elem, schema) in zip(data, schemas) ]
def test_error_info(): """test of ErrorInfo class """ err = ErrorInfo() assert err.code == 0 assert err.message == "" assert err.data == MessageData() assert not err expected_str = "mprpc.ErrorInfo()" assert str(err) == expected_str # pylint: disable=eval-used assert str(eval(repr(err))) == expected_str code = 37 message = 'test message' err = ErrorInfo(code, message) assert err.code == code assert err.message == message assert err.data == MessageData() assert err expected_str = "mprpc.ErrorInfo(code=37, message='test message')" assert str(err) == expected_str # pylint: disable=eval-used assert str(eval(repr(err))) == expected_str data = MessageData(b'\x93\x01\x02\x03') err = ErrorInfo(code, message, data) assert err.code == code assert err.message == message assert err.data == data assert err expected_str = "mprpc.ErrorInfo(code=37, message='test message', " + \ "data=<mprpc.message.MessageData: [1,2,3]>)" assert str(err) == expected_str # pylint: disable=eval-used assert str(eval(repr(err))) == expected_str
def _make_parse_error(message: str, data: Any): """raise an exception for a parse error Parameters ---------- message : str error message data : Any data Returns ------- MPRPCException exception object to raise """ return MPRPCException( ErrorInfo(ErrorCode.PARSE_ERROR, '%s: %s' % (message, data)))
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 validate_data(data: Any, schema: Optional[marshmallow.Schema] = None) -> Any: """validate data This function validate `data` with `schema` using `schema.load` function, and returns the result. Parameters ---------- data : Any data to validate schema : Optional[Schema], optional schema of data, by default None to specify no validation. If an object other than None is provided, it is assumed to be an object of a class inherited from marshmallow.Schema. Returns ------- Any validated data, `data` itself or an object of user-defined types (when `@post_load` decorator is used). Raises ------ mprpc.MPRPCException If data is invalid. """ if schema is None: # no validation is needed return data try: return schema.load(data) except marshmallow.ValidationError as err: raise MPRPCException( ErrorInfo(ErrorCode.PARSE_ERROR, 'failed to parse data: %s' % data)) from err
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)
def server_process_message( _, data: MessageData, handler: Callable[[ErrorInfo, bool, MessageData], Any]): nonlocal request_received_event request_received_event.set() handler(ErrorInfo(), True, data)