예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
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)