Пример #1
0
 def handle_exception(request, exception):
     exception_handler = utils.import_from_str(
         settings.get('EXCEPTION_HANDLER'))
     response = exception_handler(request, exception)
     # set exception to response convenience for `log_response`
     setattr(response, 'exception', exception)
     return response
Пример #2
0
    async def start_mq_servers(sanic, loop):
        # init and start mq_server
        for server_name, params in settings.get('MQ_SERVERS').items():
            server_cls = utils.import_from_str(params.pop('server'))
            if not issubclass(server_cls, BaseMqServer):
                raise Exception(
                    f'{utils.cls_str_of_cls(server_cls)} is not subclass of {utils.cls_str_of_cls(BaseMqServer)}'
                )
            server = server_cls(loop=loop, **params)
            registry.set(server_name, server, 'mq_servers')
            loop.create_task(server.start())

        # subscribe handlers
        for server_name, handlers in registry.get_group(
                'event_handlers').items():
            server = registry.get(server_name, 'mq_servers')
            if not server:
                raise Exception(f'cannot find named "{server_name}" mq_server')
            for event_name, handler, msg_type, timeout, max_retry, subscribe in handlers:
                await server.subscribe(routing_key=event_name,
                                       handler=handler,
                                       msg_type=msg_type,
                                       timeout=timeout,
                                       max_retry=max_retry,
                                       subscribe=subscribe)
Пример #3
0
    def patch(self, uri, host=None,
              strict_slashes=True,
              version=None,
              name=None,
              success_code=200,
              header_params: serializers.BaseSerializer = None,
              path_params: serializers.BaseSerializer = None,
              query_params: serializers.BaseSerializer = None,
              body_serializer: serializers.BaseSerializer = None,
              response_serializer: serializers.BaseSerializer = None,
              tags: list = None,
              context: dict = None,
              swagger_exclude: bool = False):
        context = context or {k: v for k, v in settings.get('DEFAULT_CONTEXT').items()}
        context['partial'] = True

        return self.wings_route(uri, method='PATCH', host=host,
                                strict_slashes=strict_slashes, version=version,
                                name=name,
                                tags=tags,
                                success_code=success_code,
                                header_params=header_params,
                                path_params=path_params,
                                query_params=query_params,
                                body_serializer=body_serializer,
                                response_serializer=response_serializer,
                                context=context,
                                swagger_exclude=swagger_exclude)
Пример #4
0
    def start(self):
        """ 启动
        """
        loop = registry.get('event_loop') or asyncio.get_event_loop()
        report_interval = settings.get('INSPECTOR_REPORT_INTERVAL')
        if self._count % report_interval == 0:
            logger.info('inspector working, count: %s', self._count)
        loop.call_later(self._interval, self.start)

        self._count += 1
        if self._count > 9999999:
            self._count = 1

        for task in self.tasks:
            func = task['func']
            args = task['args']
            if self._count % task['interval'] != 0 or task['times'] == 0:
                continue
            if asyncio.iscoroutinefunction(func):
                loop.create_task(func(*args))
            else:
                loop.call_soon(func, *args)

            if task['times'] > 0:
                task['times'] -= 1
Пример #5
0
def exception_handler(request, exception):
    context = {k: v for k, v in settings.get('DEFAULT_CONTEXT').items()}
    response_shape = get_response_shape(context)

    result = utils.get_value(exception, 'message', str(exception))
    code = utils.get_value(exception, 'status_code', 500)
    result, code = response_shape.create_body(result, code)
    return json(body=result, status=code)
Пример #6
0
def init_context(request):
    context_var.set({
        'trace_id':
        request.headers.get('X-TRACE-ID', '') or str(uuid.uuid4().hex),
        'messages': [],
        'request_at':
        int(datetime_helper.timestamp() * 1000)
    })
    context_var.get().update(settings.get('DEFAULT_CONTEXT'))
Пример #7
0
    async def __handle_message(self, channel, body, envelope, properties):
        encoding = properties.content_encoding or 'utf-8'
        content = body.decode(encoding)
        logger.debug('receive message from mq: %s', content)

        data = utils.instance_from_json(content)
        headers = utils.get_value(data, 'headers', {})
        ctx = {'trace_id': utils.get_value(headers, 'X-TRACE-ID', '')}
        # 用户定制传递的消息头
        for k in settings.get('CONTEXT_WHEN_DELIVERY'):
            h_k = 'X-' + k.upper().replace('_', '-')
            ctx[k] = utils.get_value(headers, h_k)

        context_var.set(ctx)

        message = utils.get_value(data, 'payload', data)
        message = utils.instance_from_json(message, cls=self.msg_type)

        retried_count = (properties.headers or {}).get('x-retry-count', 0)
        retried_count = retried_count if retried_count >= 0 else 0

        try:
            if asyncio.iscoroutinefunction(self.handler):
                fu = asyncio.ensure_future(self.handler(message),
                                           loop=self.mq_server.loop)
            else:
                context = contextvars.copy_context()
                fu = self.mq_server.loop.run_in_executor(
                    None, context.run, self.handler, message)
            await asyncio.wait_for(fu, self.timeout, loop=self.mq_server.loop)
            event.commit_events()
            logger.info(
                f'handle message success. event_name:{self.routing_key}, handler:{utils.meth_str(self.handler)}, '
                f'retried_count: {retried_count}')
        except Exception as ex:
            if self.max_retry < 0 or retried_count < self.max_retry:
                await self.__publish_to_retry_queue(body, retried_count)

            error_info = f"event_name: {self.routing_key}, handler: {utils.meth_str(self.handler)}, " \
                         f"retried_count: {retried_count}, messages: \n{content}"
            logger.error(error_info, exc_info=ex)

        finally:
            await channel.basic_client_ack(envelope.delivery_tag)
            context_var.set(None)
Пример #8
0
    def start(self):
        """ 启动
        """
        loop = registry.get('event_loop') or asyncio.get_event_loop()
        report_interval = settings.get('INSPECTOR_REPORT_INTERVAL')
        if self._count % report_interval == 0:
            wings_logger.info('inspector working, count: %s', self._count)
        loop.call_later(self._interval, self.start)

        self._count += 1
        if self._count > 9999999:
            self._count = 1

        def done_callback(future):
            from wings_sanic import event
            if not future.exception():
                event.commit_events()
            context_var.set(None)

        for task in self.tasks:
            func = task['func']
            args = task['args']
            if self._count % task['interval'] != 0 or task['times'] == 0:
                continue

            context_var.set({
                'trace_id': str(uuid.uuid4().hex),
                'messages': []
            })

            if asyncio.iscoroutinefunction(func):
                fu = asyncio.ensure_future(func(*args), loop=loop)
            else:
                context = contextvars.copy_context()
                fu = loop.run_in_executor(None, context.run, func, *args)
            fu.add_done_callback(done_callback)

            if task['times'] > 0:
                task['times'] -= 1
Пример #9
0
    def wings_route(self,
                    uri: str,
                    method: str = 'GET',
                    host=None,
                    strict_slashes=True,
                    version=None,
                    name=None,
                    success_code=200,
                    header_params: serializers.BaseSerializer = None,
                    path_params: serializers.BaseSerializer = None,
                    query_params: serializers.BaseSerializer = None,
                    body_serializer: serializers.BaseSerializer = None,
                    response_serializer: serializers.BaseSerializer = None,
                    tags: list = None,
                    context: dict = None,
                    swagger_exclude: bool = False):
        """ extension for Sanic's route. Contains parameters for wings framework.

        :param uri: the path for the handler to represent.

        :param host:

        :param strict_slashes:

        :param version:

        :param name:

        :param method: the method this function should respond to. Default GET.

        :param success_code:

        :param header_params: the arguments that should be passed into the header.

        :param path_params: the arguments that are specified by the path. By default, arguments
               that are found in the path are used first before the query_parameters and body_parameters.

        :param query_params: the arguments that should be query parameters.
               By default, all arguments are query_or path parameters for a GET request.

        :param body_serializer: the arguments that should be body parameters.
               By default, all arguments are either body or path parameters for a non-GET request.
               the whole body is validated against a single object.

        :param response_serializer: response spec

        :param tags: used for swagger tags

        :param context: maybe contains 'response_shape' to custom final response format.

        :param swagger_exclude: if True, Swagger Document will exclude this api.
        
        :return: decorated function
        """
        method = method.upper()

        context = context or {
            k: v
            for k, v in settings.get('DEFAULT_CONTEXT').items()
        }

        metadata = HandlerMetaData(uri=uri,
                                   method=method,
                                   tags=tags,
                                   success_code=success_code,
                                   header_params=header_params,
                                   path_params=path_params,
                                   query_params=query_params,
                                   body_serializer=body_serializer,
                                   response_serializer=response_serializer,
                                   context=context,
                                   swagger_exclude=swagger_exclude)

        def decorator(raw_handler):
            @wraps(raw_handler)
            async def handler(request, *args, **kwargs):
                if isinstance(metadata.context, dict):
                    from wings_sanic import context_var
                    context_var.get().update(metadata.context)
                kwargs = await views.extract_params(request, metadata)
                result = await raw_handler(request,
                                           metadata=metadata,
                                           **kwargs)
                response = await views.process_result(result, metadata)
                return response

            handler.metadata = metadata

            self.add_route(handler=handler,
                           uri=uri,
                           methods=frozenset({method}),
                           host=host,
                           strict_slashes=strict_slashes,
                           version=version,
                           name=name)

            return handler

        return decorator
Пример #10
0
def build_spec(app, loop):
    for uri, route in app.router.routes_all.items():
        if uri.startswith(f"{settings.GLOBAL_URL_PREFIX}/swagger"):
            continue

        # Build list of methods and their handler functions
        handler_type = type(route.handler)
        if handler_type is CompositionView:
            method_handlers = route.handler.handlers.items()
        else:
            method_handlers = zip(route.methods, repeat(route.handler))

        # format path parameters '<param>' to '{param}'
        uri_parsed = uri
        for parameter in route.parameters:
            uri_parsed = re.sub('<' + parameter.name + '.*?>',
                                '{' + parameter.name + '}', uri_parsed)

        for _method, _handler in method_handlers:
            metadata = utils.get_value(_handler, 'metadata')
            if _method == 'OPTIONS' or not metadata or metadata.swagger_exclude:
                continue

            # ensure group info
            group = metadata.swagger_group or 'default'
            if not isinstance(group, dict):
                group = {'title': str(group)}
            group_title = group.get('title', None) or 'default'
            if group_title not in _spec:
                _spec[group_title] = copy.deepcopy(settings.get('SWAGGER'))
                _spec[group_title].update({
                    'swagger': '2.0',
                    'definitions': {},
                    'paths': {},
                })
                _spec[group_title]['info'].update(group)

            group_spec = _spec[group_title]
            if uri_parsed not in group_spec['paths']:
                group_spec['paths'][uri_parsed] = {}

            # generate path
            parameters = []
            # header
            for name, field in (utils.get_value(metadata.header_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'header'})
                parameters.append(parameter)
                __update_definitions(group_spec, metadata.header_serializer)

            # path
            for name, field in (utils.get_value(metadata.path_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'path', 'required': True})
                parameters.append(parameter)
                __update_definitions(group_spec, metadata.path_serializer)

            # query
            for name, field in (utils.get_value(metadata.query_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'query'})
                parameters.append(parameter)
                __update_definitions(group_spec, metadata.query_serializer)

            # body
            has_file_field = False
            if metadata.body_serializer:
                body_spec = metadata.body_serializer.openapi_spec()
                import wings_sanic
                for _, f in metadata.body_serializer.fields.items():
                    if isinstance(f, wings_sanic.FileField):
                        has_file_field = True
                        break

                if not has_file_field:
                    parameters.append({
                        'in': 'body',
                        'name': 'body',
                        'required': True,
                        'schema': body_spec
                    })
                else:
                    cls_str = utils.cls_str_of_obj(metadata.body_serializer)
                    if cls_str in serializers.definitions:
                        body_spec = serializers.definitions[cls_str]
                    for name, field_spec in body_spec.get('properties',
                                                          {}).items():
                        # 忽略read_only字段
                        if utils.get_value(field_spec, 'readOnly', False):
                            continue
                        parameters.append({
                            'in':
                            'formData',
                            'name':
                            name,
                            'required':
                            name in utils.get_value(body_spec, 'required', []),
                            **field_spec
                        })

                __update_definitions(group_spec, metadata.body_serializer)

            # response
            response_spec = None
            if metadata.response_serializer:
                response_spec = metadata.response_serializer.openapi_spec()

                __update_definitions(group_spec, metadata.response_serializer)

            response_shape = get_response_shape(metadata.context)
            response_spec = response_shape.swagger(response_spec)

            summary, description = __summary_description(
                inspect.cleandoc(_handler.__doc__ or ""))

            endpoint = {
                'operationId':
                utils.meth_str(_handler),
                'summary':
                summary,
                'description':
                description,
                'consumes': ['multipart/form-data']
                if has_file_field else ['application/json'],
                'produces': ['application/json'],
                'tags':
                metadata.tags,
                'parameters':
                parameters,
                'responses': {
                    "200": {
                        "description": None,
                        "examples": None,
                        "schema": response_spec
                    }
                },
            }

            group_spec['paths'][uri_parsed][_method.lower()] = endpoint
Пример #11
0
def start(host='0.0.0.0',
          port=None,
          debug=False,
          ssl=None,
          sock=None,
          workers=None,
          protocol=None,
          backlog=100,
          stop_event=None,
          register_sys_signals=True,
          access_log=False,
          auto_reload=False,
          **kwargs):
    """
    before start application, you need load your user_settings 
    by `wings_sanic.settings.load(**user_settings)`
    :return: 
    """
    logging.config.dictConfig(settings.get('LOGGING_CONFIG'))

    if settings.get('CORS'):
        CORS(app, automatic_options=True, supports_credentials=True)
    app.config.update(settings.working_settings)

    try:
        import uvloop
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    except ImportError:
        pass
    # when DEBUG==True, swagger document is available
    if settings.get('DEBUG'):
        app.blueprint(
            WingsBluePrint.group(swagger_blueprint,
                                 url_prefix=settings.GLOBAL_URL_PREFIX))

        @app.exception(NotFound)
        def handle_404_redirect(request, exception):
            import re
            if re.match('.*url.*not found', str(exception).lower()):
                return redirect(app.url_for('swagger.static'))
            return handle_exception(request, exception)

    bps = [
        utils.import_from_str(bp_str) for bp_str in settings.get('BLUEPRINTS')
    ]
    app.blueprint(
        WingsBluePrint.group(bps, url_prefix=settings.GLOBAL_URL_PREFIX))

    @app.exception(Exception)
    def handle_exception(request, exception):
        exception_handler = utils.import_from_str(
            settings.get('EXCEPTION_HANDLER'))
        response = exception_handler(request, exception)
        # set exception to response convenience for `log_response`
        setattr(response, 'exception', exception)
        return response

    @app.get(f'{settings.GLOBAL_URL_PREFIX}/ping/',
             response_serializer={
                 'ping': serializers.StringField('Ping-Pong', required=True)
             })
    async def ping(request, *args, **kwargs):
        return {'ping': 'pong'}

    @app.listener('after_server_start')
    async def inspector_start_working(sanic, loop):
        registry.set('event_loop', loop)
        loop.call_soon(inspector.start)

    @app.listener('after_server_start')
    async def start_mq_servers(sanic, loop):
        # init and start mq_server
        for server_name, params in settings.get('MQ_SERVERS').items():
            server_cls = utils.import_from_str(params.pop('server'))
            if not issubclass(server_cls, BaseMqServer):
                raise Exception(
                    f'{utils.cls_str_of_cls(server_cls)} is not subclass of {utils.cls_str_of_cls(BaseMqServer)}'
                )
            server = server_cls(loop=loop, **params)
            registry.set(server_name, server, 'mq_servers')
            loop.create_task(server.start())

        # subscribe handlers
        for server_name, handlers in registry.get_group(
                'event_handlers').items():
            server = registry.get(server_name, 'mq_servers')
            if not server:
                raise Exception(f'cannot find named "{server_name}" mq_server')
            for event_name, handler, msg_type, timeout, max_retry, subscribe in handlers:
                await server.subscribe(routing_key=event_name,
                                       handler=handler,
                                       msg_type=msg_type,
                                       timeout=timeout,
                                       max_retry=max_retry,
                                       subscribe=subscribe)

    app.run(host=host,
            port=port or settings.get('HTTP_PORT'),
            debug=debug,
            ssl=ssl,
            sock=sock,
            workers=workers or settings.get('WORKERS'),
            protocol=protocol,
            backlog=backlog,
            stop_event=stop_event,
            register_sys_signals=register_sys_signals,
            access_log=access_log,
            auto_reload=auto_reload,
            **kwargs)
Пример #12
0
def start():
    """
    before start application, you need load your user_settings 
    by `wings_sanic.settings.load(**user_settings)`
    :return: 
    """
    if not settings.get('DEV'):
        logging.config.dictConfig(settings.get('LOG'))

    if settings.get('CORS'):
        CORS(app, automatic_options=True, supports_credentials=True)

    app.config.update(settings.working_settings)

    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

    # when DEBUG==True, swagger document is available
    if settings.get('DEBUG'):
        app.blueprint(swagger_blueprint)

        @app.exception(NotFound)
        def handle_404_redirect(request, exception):
            return redirect('/swagger/')

    for bp_str in settings.get('BLUEPRINTS'):
        bp = utils.import_from_str(bp_str)
        app.blueprint(bp)

    @app.middleware('response')
    def log_response(request, response):
        logger = logging.getLogger('wings_sanic')
        full_path = request.path + ('?%s' % request.query_string
                                    if request.query_string else '')
        request_url = '{0} {1}'.format(request.method, full_path)
        extra = {'remote_ip': request.remote_addr, 'request_uri': request_url}

        exception = getattr(response, 'exception', None)

        if exception:
            message = "request_url: {request_url}" \
                      "\nrequest_body: {body}" \
                      "\nstatus_code: {status_code}" \
                      "\nresponse_content: {response}" \
                .format(request_url=request_url, body=request.body,
                        response=response.body, status_code=response.status)
            if getattr(exception, 'code', 500) / 100 == 5:
                logger.error(message,
                             extra=extra,
                             exc_info=exception,
                             stack_info=True)
            else:
                logger.warning(message, extra=extra, exc_info=exception)
        # 正常请求,记录少量信息
        else:
            message = '%s %s' % (request_url, response.status)
            logger.info(message, extra=extra)

    @app.exception(Exception)
    def handle_exception(request, exception):
        exception_handler = utils.import_from_str(
            settings.get('EXCEPTION_HANDLER'))
        response = exception_handler(request, exception)
        # set exception to response convenience for `log_response`
        setattr(response, 'exception', exception)
        return response

    @app.get('/ping/',
             response_serializer={
                 'ping': serializers.StringField('Ping-Pong', required=True)
             })
    async def ping(request, *args, **kwargs):
        return {'ping': 'pong'}

    @app.listener('after_server_start')
    async def inspector_start_working(sanic, loop):
        registry.set('event_loop', loop)
        loop.call_soon(inspector.start)

    app.run(host="0.0.0.0",
            port=settings.get('HTTP_PORT'),
            workers=settings.get('WORKERS'),
            debug=settings.get('DEBUG'),
            access_log=settings.get('DEV'))
Пример #13
0
def build_spec(app, loop):
    _spec['swagger'] = '2.0'
    _spec.update(settings.get('SWAGGER'))

    # --------------------------------------------------------------- #
    # Paths
    # --------------------------------------------------------------- #
    paths = {}
    # tags =
    for uri, route in app.router.routes_all.items():
        if uri.startswith("/swagger"):
            continue

        # Build list of methods and their handler functions
        handler_type = type(route.handler)
        if handler_type is CompositionView:
            method_handlers = route.handler.handlers.items()
        else:
            method_handlers = zip(route.methods, repeat(route.handler))

        methods = {}
        for _method, _handler in method_handlers:
            metadata = _handler.metadata
            if _method == 'OPTIONS' or metadata.swagger_exclude:
                continue
            parameters = []
            # header
            for name, field in (utils.get_value(metadata.header_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'header'})
                parameters.append(parameter)

            # path
            for name, field in (utils.get_value(metadata.path_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'path', 'required': True})
                parameters.append(parameter)

            # query
            for name, field in (utils.get_value(metadata.query_serializer,
                                                'fields') or {}).items():
                parameter = field.openapi_spec()
                parameter.update({'in': 'query'})
                parameters.append(parameter)

            # body
            if metadata.body_serializer:
                spec = metadata.body_serializer.openapi_spec()
                parameters.append({
                    'in': 'body',
                    'name': 'body',
                    'required': True,
                    'schema': spec
                })

            # response
            response_spec = None
            if metadata.response_serializer:
                response_spec = metadata.response_serializer.openapi_spec()
            response_shape = get_response_shape(metadata.context)
            response_spec = response_shape.swagger(response_spec)

            summary, description = __summary_description(
                inspect.cleandoc(_handler.__doc__ or ""))

            endpoint = {
                'operationId': utils.meth_str(_handler),
                'summary': summary,
                'description': description,
                'consumes': ['application/json'],
                'produces': ['application/json'],
                'tags': metadata.tags,
                'parameters': parameters,
                'responses': {
                    "200": {
                        "description": None,
                        "examples": None,
                        "schema": response_spec
                    }
                },
            }

            methods[_method.lower()] = endpoint

        uri_parsed = uri
        for parameter in route.parameters:
            uri_parsed = re.sub('<' + parameter.name + '.*?>',
                                '{' + parameter.name + '}', uri_parsed)

        paths[uri_parsed] = methods

    # --------------------------------------------------------------- #
    # Definitions
    # --------------------------------------------------------------- #

    _spec['definitions'] = {
        cls.__name__: definition
        for cls, (obj, definition) in serializers.definitions.items()
    }

    _spec['paths'] = paths