Example #1
0
    def _after_configure(self, sender: Celery, **kwargs: Any) -> None:
        """
        Hook to avoid circular imports.
        Providing the schedules and routes functions to the __init__ method and calling them there
        creates a circular import. This is because the Celery app (self) is not initialized yet when
        the function reaches the tasks code, which in turn requires the Celery app to decorate the
        task with `@celery_app.task`. Calling these functions here make it so that the Celery app
        has already been initialized upfront, yet we call them before the Celery app is actually
        started.
        """
        if self.__schedules_function:
            self.conf.beat_schedule = _overall_schedule(
                *self.__schedules_function())
        if self.__routes_function:
            self.conf.task_routes = self.__routes_function()

        monitoring_registry = initialize_monitoring()
        try:
            start_http_server(port=config.CELERY_PROMETHEUS_PORT,
                              registry=monitoring_registry)
        except OSError as exception:
            # More than one Celery is running on the same machine, so it is enough that one of them
            # starts the monitoring server.
            if "Address already in use" not in str(exception):
                raise
def create_app(
    api_title: str,
    api_description: str,
    blueprints: Iterable[Blueprint],
    managers: BaseManagers,
) -> Sanic:
    """
    Create the Sanic application.

    :param api_title: the title of the API in the /swagger endpoint.
    :param api_description: the description of the API in /swagger endpoint.
    :param blueprints: the Sanic blueprints to register.
    :param managers: the microservice's managers.
    :return: the created Sanic application.
    """
    app = Sanic(__name__,
                log_config=get_sanic_logger_config(config.LOG_JSON_INDENT))
    app.config.TESTING = config.ENV == Environment.TESTING

    swagger_config = dict(
        API_TITLE=api_title,
        API_DESCRIPTION=api_description,
        API_SCHEMES=["https"],
        API_VERSION="1.0.0",
        SWAGGER_UI_CONFIGURATION=dict(
            validatorUrl=None,
            displayRequestDuration=True,
            docExpansion="list",
        ),
    )
    app.config.update(swagger_config)

    monitoring_registry = initialize_monitoring()

    @app.route("/")
    @doc.summary("Health check.")
    @doc.response(HTTPStatus.OK.value,
                  "Ok",
                  description="The server is running ok.")
    @cache(no_store=True)
    async def health_check(request: Request) -> HTTPResponse:
        return HTTPResponse(status=HTTPStatus.OK.value)

    # TODO: Evaluate whether to expose it over another port.
    @app.route("/metrics")
    @doc.summary("Expose Prometheus metrics.")
    @cache(no_store=True)
    async def metrics(request: Request) -> HTTPResponse:
        latest_metrics = prometheus_client.generate_latest(monitoring_registry)
        return HTTPResponse(
            body=latest_metrics.decode("utf-8"),
            content_type=prometheus_client.CONTENT_TYPE_LATEST,
            headers={"Content-Length": str(len(latest_metrics))},
        )

    @app.listener("after_server_start")
    async def after_server_start(*args: Any, **kwargs: Any) -> None:
        await managers.initialize()

    @app.listener("before_server_stop")
    async def before_server_stop(*args: Any, **kwargs: Any) -> None:
        await managers.teardown()

    @app.listener("after_server_stop")
    async def after_server_stop(*args: Any, **kwargs: Any) -> None:
        multiprocess.mark_process_dead(os.getpid())

    @app.middleware("request")
    async def before_request(request: Request) -> None:
        sanic_before_request_handler(request)

    @app.middleware("response")
    async def after_response(request: Request, response: HTTPResponse) -> None:
        sanic_after_request_handler(request, response)

    @app.exception(ApiException)
    async def handle_exception(request: Request,
                               exception: ApiException) -> HTTPResponse:
        _LOGGER.exception(exception)
        return json_response(
            body={
                "error_code": exception.error_code,
                "message": exception.error_message
            },
            status=exception.status_code,
        )

    @app.exception(SanicException)
    async def handle_unknown_exception(
            request: Request, exception: SanicException) -> HTTPResponse:
        _LOGGER.exception(exception)
        return json_response(
            body={
                "error_code": ApiException.error_code,
                "message": ApiException.error_message
            },
            status=HTTPStatus(exception.status_code),
        )

    @app.exception(Exception)
    async def handle_bare_exception(request: Request,
                                    exception: Exception) -> HTTPResponse:
        _LOGGER.exception(exception)
        return json_response(
            body={
                "error_code": ApiException.error_code,
                "message": ApiException.error_message
            },
            status=ApiException.status_code,
        )

    for blueprint in blueprints:
        app.blueprint(blueprint)

    if config.ENV != Environment.RELEASE:
        # It's a good practice to avoid opening the swagger endpoints in a production environment.
        app.blueprint(swagger_blueprint)

    return app