import logging import marshmallow import os import sentry_sdk from aiohttp import ClientSession, TCPConnector, TraceConfig, web from aiohttp_apispec import setup_aiohttp_apispec, validation_middleware from aiohttp_apispec.aiohttp_apispec import resolver from apispec.ext.marshmallow import MarshmallowPlugin from apollo_utils.service.exceptions import APIError, BadGateway from asyncio import CancelledError from ddtrace import config as dd_config from ddtrace import tracer from ddtrace.contrib.aiohttp import trace_app from sentry_sdk import capture_exception from sentry_sdk.integrations.aiohttp import AioHttpIntegration from sentry_sdk.utils import BadDsn from typing import Callable from server import config from server.api import router as routers from server.client.clients import DelphiClient, DelphiConfig from server.legacy.analytics.routes import init_routes as init_analytics_routes from server.legacy.consumer_analytics.routes import init_routes as init_consumer_routes from server.legacy.core.middlewares import auth_middleware from server.legacy.core.routes import init_routes as init_core_routes from server.legacy.delphi.routes import init_routes as init_delphi_routes logger = logging.getLogger("app") def init_sentry(): """Init sentry for project""" if config.SENTRY_DSN: try: sentry_sdk.init(environment=config.ENVIRONMENT, dsn=config.SENTRY_DSN, integrations=[AioHttpIntegration()]) except BadDsn: pass @web.middleware async def error_middleware(request: web.Request, handler: Callable) -> web.Response: """Callback for handling exceptions""" try: return await handler(request) except (CancelledError, APIError) as ex: if isinstance(ex, CancelledError): ex = BadGateway(detail="Request was cancelled.") if ex.status_code not in config.SENTRY_IGNORE_STATUS_CODES: capture_exception(ex) return web.json_response(data=ex.to_dict(), status=ex.status_code) @web.middleware async def logging_middleware(request: web.Request, handler: Callable) -> web.Response: """Callback for logging requests""" response = await handler(request) logger.info(f"{request.method} - {request.rel_url} - {response.status}") return response async def on_request_end(session, trace_config_ctx, params): req = params.response logger.info(f"Request {req.method} {req.url} {req.status}") async def context(app: web.Application) -> None: if config.DEBUG: trace_config = TraceConfig() trace_config.on_request_end.append(on_request_end) trace_configs = [trace_config] else: trace_configs = None session = ClientSession(connector=TCPConnector(ssl=False), trace_configs=trace_configs) app["delphi_java_api"] = DelphiClient(session=session, config=DelphiConfig(**config.DELPHI_JAVA_API_CONFIG)) yield await session.close() def create_app() -> web.Application: """Application factory""" logging.basicConfig(level=logging.INFO) init_sentry() app = web.Application( middlewares=[error_middleware, logging_middleware, auth_middleware, validation_middleware], handler_args={"max_line_size": config.MAX_LINE_SIZE, "max_field_size": config.MAX_FIELD_SIZE}, ) app.cleanup_ctx.append(context) plugin = MarshmallowPlugin(schema_name_resolver=resolver) try: """ We extend default apispec DEFAULT_FIELD_MAPPING mapping with fields.Method and field.Fucntion to string representation so that our swagger displayed this fields as "string" as default (by default this fields have no representation and aren`t displayed in swagger) To use different data type representation you can add an example of the response to this fields right inside response schema by using ApispecTypes of apollo_utils.swagger.utils pkg """ plugin.Converter.field_mapping[marshmallow.fields.Method] = ("string", None) plugin.Converter.field_mapping[marshmallow.fields.Function] = ("string", None) except NameError: pass setup_aiohttp_apispec( app=app, title="DSP API", version="1.0.0", swagger_path="/api/doc/", securityDefinitions={ "apiKey": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "For example: Bearer test", } }, schemes=["http", "https"], security=[{"apiKey": []}], ) setup_aiohttp_apispec.plugin = plugin init_consumer_routes(app) init_delphi_routes(app) init_analytics_routes(app) init_core_routes(app) for router in routers: app.add_routes(router) if os.environ.get("DATADOG_SERVICE_NAME"): dd_config.http.trace_query_string = True dd_config.trace_headers(config.DD_TRACE_HEADERS) trace_app(app, tracer) return app async def main() -> web.Application: """Main entry point""" return create_app() if __name__ == "__main__": web.run_app(main())