class Rows(LogicElement): """ Defines a collection of rows in a [tag tables]table[/tag]. A [tag tables]rows[/tag] tag may contain either [tag tables]row[/tag] tags [i]or[/i] -- if the [c]src[/c] attribute is set -- [tag tables]cell[/tag] tags. When the [c]src[/c] attribute is set, it should be a sequence of values to be iterated over (like [tag]for[/tag]). Each item in the sequence will generate a row containing the enclosed [tag tables]cell[/tag] tags. """ class Help: synopsis = "a collection of rows in a table" xmlns = namespaces.tables template = Attribute("Template", required=False, default="rows.html") row_template = Attribute("Template", required=False, default="row.html") row_class = Attribute("Extra class for rows", required=False, default=None) style = Attribute("Optional CSS style", required=False) src = Attribute("Sequence of row data", required=False, type="expression") dst = Attribute("Destination", required=False, type="reference") _id = Attribute("Table ID", required=False, default=None) _from = Attribute("Application", type="application", required=False, default='moya.tables') def logic(self, context): params = self.get_parameters(context) content = context['.content'] app = self.get_app(context) default_col = {'align': 'left'} cols = context['_moyatable._cols'] or [] if self.has_parameter('src'): objects, dst = self.get_parameters(context, 'src', 'dst') try: iter_objects = iter(objects) except: self.throw( 'bad-value.src', "row objects attribute {} is not a sequence".format( context.to_expr(objects)), diagnosis= "Check the value of the 'objects' attribute is a valid sequence." ) with content.template_node('rows', app.resolve_template(params.template)): for obj in iter_objects: _cols = itertools.chain(cols, itertools.repeat(default_col)) if dst: context[dst] = obj td = { 'id': self.id(context), 'class': self.row_class(context), 'style': self.style(context) } with content.template_node( "row", app.resolve_template(params.row_template), td): for _col, child in zip( _cols, self.get_children(element_type=( 'http://moyaproject.com/tables', 'cell'))): if not _col.get('hide', False): with context.data_scope(_col): yield logic.DeferNode(child) else: with content.template_node('rows', app.resolve_template(params.template)): _cols = itertools.chain(cols, itertools.repeat(default_col)) for _col, child in zip( _cols, self.get_children( element_type=('http://moyaproject.com/tables', 'cell'))): if not _col.get('hide', False): with context.data_scope(_col): yield logic.DeferNode(child)
class Interface(LogicElement): """Creates a JSON RPC interface.""" xmlns = namespaces.jsonrpc preserve_attributes = ['_methods'] errors = Attribute("Optional <enum> of error codes", type="elementref", required=False, default=None) class Meta: trap_exceptions = True class Help: synopsis = "create an interface for remote methods" def post_build(self, context): self._methods = {} error_enum_libid = self.errors(context) if error_enum_libid is not None: enum_element = self.get_element(self.errors(context)).element self.errors = self.archive.get_enum(enum_element.libid) else: self.error = None def logic(self, context): pass def process_request(self, context, req): """Process an individual request""" def error_response(*args, **kwargs): if notification: return None else: return ErrorResponse(*args, **kwargs) if isinstance(req, dict): requests = [req] else: requests = req if not isinstance(requests, list): yield ErrorResponse(ErrorCode.invalid_request, "Invalid request - list or object expected", id=None) return if not requests: yield ErrorResponse(ErrorCode.invalid_request, "Invalid request", id=None) return jsonrpc = context['.jsonrpc'] for req in requests: jsonrpc['request'] = req if not isinstance(req, dict): yield ErrorResponse(ErrorCode.invalid_request, "Invalid request - object expected", id=None) continue notification = 'id' not in req if notification: req_id = None else: req_id = req['id'] if 'jsonrpc' not in req: yield error_response( ErrorCode.invalid_request, "Invalid request - value for 'jsonrpc' expected", id=req_id) continue jsonrpc_version = req['jsonrpc'] if jsonrpc_version != '2.0': yield error_response( ErrorCode.invalid_request, "Invalid request - this server supports only JSON-RPC specification 2.0 (see http://www.jsonrpc.org/specification)", id=req_id) continue params = req.get('params', {}) if not isinstance(params, dict): yield error_response( ErrorCode.invalid_params, "Invalid params - this server only supports parameters by-name", id=req_id) continue try: method_name = req['method'] except KeyError: yield error_response( ErrorCode.invalid_request, "Invalid request - value for 'method' expected", id=req_id) continue if not isinstance(method_name, string_types): yield error_response( ErrorCode.invalid_request, "Invalid request - 'method' should be a string") continue if method_name not in self._methods: yield error_response( ErrorCode.method_not_found, "Method not found - no method called '{}'".format( method_name), id=req_id) continue method = self._methods[method_name] yield CallMethod(method, params, req_id, notification) @classmethod def log_result(cls, context, result): log.debug("↪ %s", lazystr(to_expression, context, result, max_size=120)) def run(self, context): """Generate a response for either a GET or a POST""" request = context['.request'] app = context.get('.app', None) interface_id = "<interface {}#{}>".format(app.name, self.libname) if request.method == "GET": render_container = RenderContainer.create( app, template="moya.jsonrpc/interface.html") render_container['interface'] = self context['_return'] = render_container return if request.method != "POST": return context['.jsonrpc'] = {"request": {}} try: req = json.loads(request.body) except Exception as e: log.debug("%s badly formatted JSONRPC request: %s", interface_id, e) response = self.make_error(None, False, code=ErrorCode.parse_error, message=text_type(e)) raise logic.EndLogic(response) batch = isinstance(req, list) responses = [] for response in self.process_request(context, req): if response is None: # Notification continue elif isinstance(response, ErrorResponse): # Problem with request responses.append(response) elif isinstance(response, CallMethod): # Request good, do call method, params, req_id, notification = response log.debug("%s %s '%s' with %s", interface_id, 'notify' if notification else 'call', method.name, lazystr(to_expression, context, params, 80)) try: params = response.method.process_params(params) except ParamError as e: response = ErrorResponse(ErrorCode.invalid_params, text_type(e), id=req_id) self.log_result(context, response) responses.append(response) continue def do_call(element, app, params): try: return_value = self.archive.call( element.libid, context, app, **params) except Exception as e: if isinstance(e.original, MoyaException): moya_exc = e.original if moya_exc.type == "jsonrpc.error": return RPCErrorReturn(moya_exc.info['code'], moya_exc.info['message'], moya_exc.info['data']) error_message = "exception '{}' in rpc call to {}".format( e, method) if hasattr(e, 'moya_trace'): log.error(error_message) if context['.debug'] and context['.console']: context['.console'].obj(context, e) else: error_message = "{}\n{}".format( error_message, e.moya_trace) log.error(error_message) else: context['.console'].obj(context, e) log.exception(error_message) response = ErrorResponse( ErrorCode.internal_error, 'internal error -- this error has been logged', id=req_id) return response else: return return_value return_value = do_call(method.element, app, params) if method.macro is not None and not isinstance( return_value, (RPCResponse, RPCErrorReturn)): try: macro_app, macro_element = self.get_element( method.macro, app) except Exception as e: log.error("%s no macro called '%s'", interface_id, method.macro) return_value = ErrorResponse( ErrorCode.internal_error, "internal error -- this error has been logged", id=req_id) else: return_value = do_call(macro_element, app, params) if isinstance(return_value, RPCResponse): self.log_result(context, return_value) responses.append(return_value) continue if notification: continue if isinstance(return_value, RPCErrorReturn): code, message, data = return_value if code.isdigit(): code = int(code) else: try: code = int(self.errors[code]) if not message: message = self.errors[code].description except Exception as e: log.error( "invalid error code '{}' -- defaulting to 'internal_error'" .format(code)) code = ErrorCode.internal_error message = ErrorCode.to_str[code] return_value = RPCErrorReturn(code, message, data) response = ErrorResponse(code, message, data=data, id=req_id) self.log_result(context, response) else: # Check the response is serializable try: moyajson.dumps(return_value) except Exception as e: log.error(text_type(e)) response = ErrorResponse( ErrorCode.internal_error, 'internal error -- server was unable to serialize the response', id=req_id) self.log_result(context, response) else: response = SuccessResponse(return_value, req_id) self.log_result(context, return_value) responses.append(response) if not responses: raise logic.EndLogic( Response(content_type=b'application/json' if PY2 else 'application/json')) try: if batch: response_json = moyajson.dumps(responses, indent=4) else: response_json = moyajson.dumps(responses[0], indent=4) except Exception as e: log.exception("error serializing response") error_response = ErrorResponse( ErrorCode.internal_error, "server was unable to generate a response -- this error has been logged", id=None) response_json = moyajson.dumps(error_response) response = Response( content_type=b'application/json' if PY2 else 'application/json', body=response_json) raise logic.EndLogic(response) yield # Because this method should be a generator def register_method(self, name, element, macro=None, group=None, params=None, doc=None, description=None, **kwargs): """Register an exposed method""" method = Method(name, element=element, group=group, macro=macro, params=params, doc=doc, description=description, **kwargs) self._methods[method.name] = method @property def methods(self): """Get a list of exposed methods""" return [self._methods[name] for name in sorted(self._methods.keys())] @property def methods_by_group(self): """Get a list of methods arranged by group""" methods = sorted(self._methods.values(), key=lambda m: (m.group, m.base_name.lower()) or '') return [(group, list(_methods)) for group, _methods in itertools.groupby(methods, key=lambda m: m.group)] def make_error(self, call_id, notification, code=ErrorCode.internal_error, message=None): """Make a JSON-RPC error response""" if notification: return Response() if message is None: message = ErrorCode.to_str.get(code, None) if message is None: message = "unknown error" error = {"code": code, "message": message} response = {"jsonrpc": "2.0", "error": error, "id": call_id} response_json = json.dumps(response, indent=4) return Response( content_type=b'application/json' if PY2 else 'application/json', body=response_json)
class Table(LogicElement): """ Begins a table content definition. Must be inside a [tag]content[/tag] tag. """ class Help: synopsis = "add a table to content" example = """ <table class="table table-striped" xmlns="http://moyaproject.com/tables" caption="An example table"> <columns> <header>Product</header> <header>Stock</header> </columns> <rows src="products" dst="product"> <cell>${product.name}</cell> <cell>${product.stock}</cell> </rows> </table> """ xmlns = namespaces.tables template = Attribute("Template", type="template", required=False, default="table.html") _class = Attribute("Extra class", required=False, map_to="class", default=None) style = Attribute("Option CSS style", required=False) _id = Attribute("Table ID", required=False, default=None) _from = Attribute("Application", type="application", required=False, default='moya.tables') caption = Attribute("Table caption", type="text", required=False, default=None) class Meta: text_nodes = "text" def logic(self, context): params = self.get_parameters(context) content = context['.content'] table = { "class": params['class'], "id": params.id, "caption": params.caption, "_cols": [], } app = self.get_app(context) css_path = self.archive.get_media_url(context, app, 'media', 'css/tables.css') content.include_css(css_path) with context.data_scope(): context['_moyatable'] = table with content.template_node("table", app.resolve_template(params.template), {'table': table}): yield logic.DeferNodeContents(self)