import ddtrace import flask_excel as excel import marshmallow import os import sentry_sdk from apollo_utils.service.exceptions import APIError from ddtrace import config as dd_config from ddtrace import patch_all from elasticsearch import Elasticsearch from flask import Flask, g, jsonify, request from flask.logging import default_handler from flask_apispec import ResourceMeta from flask_cors import CORS from flask_sqlalchemy_replication import Replication from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration from sentry_sdk.utils import BadDsn from src.api.charts.playlists.spotify import blueprint as charts_playlists_spotify_blueprint from src.api.core import blueprint as core_blueprint from src.api.playlists.apple import blueprint as playlists_apple_blueprint from src.api.playlists.blacklisted import blueprint as playlists_blacklisted_blueprint from src.api.playlists.nmf import blueprint as playlists_nmf_blueprint from src.api.playlists.spotify import blueprint as playlists_spotify_blueprint from src.api.tracks.playlists import blueprint as tracks_playlists_blueprint from src.api.users.emails import blueprint as users_emails_blueprint from src.api.users.favorites import blueprint as users_favorites_blueprint from src.api.users.search.dsp import blueprint as users_search_dsp_blueprint from src.api.users.search.recent import blueprint as recent_search_blueprint from src.api.users.visits import blueprint as users_visits_blueprint from src.api.values import blueprint as values_blueprint from src.config import base as base_config from src.db.base import db from src.legacy.apollo_api.blueprint import blueprint as apollo_api_blueprint from src.legacy.apple_music.blueprint import blueprint as apple_music_blueprint from src.legacy.apple_music.blueprint import v1_blueprint as apple_music_v1_blueprint from src.legacy.apple_music.blueprint import v2_blueprint as apple_music_v2_blueprint from src.legacy.charts.blueprint import blueprint as charts_blueprint from src.legacy.core.blueprint import blueprint as legacy_core_blueprint from src.legacy.core.logging import JsonFormatter from src.legacy.push_notifications.blueprint import blueprint as push_notifications_blueprint from src.legacy.spotify.blueprint import blueprint as spotify_blueprint from src.legacy.spotify.blueprint import v1_blueprint as spotify_v1_blueprint from src.legacy.spotify.blueprint import v2_blueprint as spotify_v2_blueprint from src.utils import auth as auth_util from src.utils.api_spec_ext import parser from src.utils.cache import get_cache_mode from src.utils.json import CustomJSONProvider patch_all() def init_ddog() -> None: """Initialize DataDog monitoring.""" dd_service_name = os.environ.get("DATADOG_SERVICE_NAME") if dd_service_name: ddtrace.config.flask["service_name"] = dd_service_name dd_config.http.trace_query_string = True dd_config.trace_headers(base_config.DD_TRACE_HEADERS) # Add sentry logging sentry_dsn = os.environ.get("SENTRY_DSN") sentry_env = os.environ.get("FLASK_ENV", "development") if sentry_dsn: try: sentry_sdk.init( dsn=sentry_dsn, environment=sentry_env, integrations=[FlaskIntegration(), RedisIntegration(), SqlalchemyIntegration()], ) except BadDsn: pass Flask.json_provider_class = CustomJSONProvider app = Flask(__name__) app.config.from_object("src.{}".format(os.environ["FLASK_CONFIG_MODULE"])) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["APISPEC_WEBARGS_PARSER"] = parser app.elasticsearch = Elasticsearch([app.config["ELASTICSEARCH_URL"]]) if app.config["ELASTICSEARCH_URL"] else None db.init_app(app) excel.init_excel(app) Replication(app) CORS(app, origins=app.config["ALLOWED_ORIGINS"]) default_handler.setFormatter(JsonFormatter()) init_ddog() app.register_blueprint(apollo_api_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(apple_music_blueprint, url_prefix=app.config["API_PREFIX"] + "/apple-music") app.register_blueprint(apple_music_v1_blueprint, url_prefix=app.config["API_PREFIX"] + "/v1/apple-music") app.register_blueprint(apple_music_v2_blueprint, url_prefix=app.config["API_PREFIX"] + "/v2/apple-music") app.register_blueprint(playlists_blacklisted_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(charts_blueprint, url_prefix=app.config["API_PREFIX"] + "/charts") app.register_blueprint(spotify_blueprint, url_prefix=app.config["API_PREFIX"] + "/spotify") app.register_blueprint(spotify_v1_blueprint, url_prefix=app.config["API_PREFIX"] + "/v1/spotify") app.register_blueprint(spotify_v2_blueprint, url_prefix=app.config["API_PREFIX"] + "/v2/spotify") app.register_blueprint(legacy_core_blueprint) app.register_blueprint(push_notifications_blueprint, url_prefix=app.config["API_PREFIX"] + "/push-notifications") app.register_blueprint(charts_playlists_spotify_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(users_visits_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(playlists_spotify_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(users_favorites_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(values_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(users_emails_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(core_blueprint) app.register_blueprint(playlists_apple_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(tracks_playlists_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(playlists_nmf_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(recent_search_blueprint, url_prefix=app.config["API_PREFIX"]) app.register_blueprint(users_search_dsp_blueprint, url_prefix=app.config["API_PREFIX"]) if app.config.get("DEBUG"): from flasgger import Swagger Swagger(app, template_file="swagger.yaml") auth_util.no_authorize(app.blueprints["flasgger"]) from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from flask_apispec.extension import FlaskApiSpec plugin = MarshmallowPlugin() 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 app.config.update( { "APISPEC_SPEC": APISpec( title="Apollo API", version="1.0.0", openapi_version="2.0", plugins=[plugin], securityDefinitions={ "apiKey": { "description": "Service API key", "in": "header", "name": "Authorization", "type": "apiKey", }, "userId": {"description": "Current user ID", "in": "header", "name": "X-User-Id", "type": "apiKey"}, }, schemes=["http", "https"], security=[{"apiKey": [], "userId": []}], ), "APISPEC_SWAGGER_URL": "/api/docs/swagger.json", "APISPEC_SWAGGER_UI_URL": "/api/doc/", } ) docs = FlaskApiSpec(app) auth_util.no_authorize(app.blueprints["flask-apispec"]) for name, rule in app.view_functions.items(): try: blueprint_name, endpoint_name = name.split(".") except ValueError: continue view_cls = getattr(rule, "view_class", None) if view_cls and isinstance(view_cls, ResourceMeta): docs.register(view_cls, endpoint=endpoint_name, blueprint=blueprint_name) else: if blueprint_name not in ("flasgger", "flask-apispec"): try: docs.register(rule, blueprint=blueprint_name) except TypeError: pass @app.errorhandler(APIError) def handle_api_error(error): """Handle API errors.""" response = jsonify(error.to_dict()) response.status_code = error.status_code return response @app.before_request def set_cache_mode(): """Set cache mode based on header and configuration.""" g._cache_mode = get_cache_mode() @app.before_request def set_user(): """Base user check.""" auth_util.authorize() g._user = auth_util.User(request.headers.get("X-User-Id")) @app.teardown_request def teardown_request(exception): if exception: db.session.rollback() else: try: db.session.commit() except Exception: db.session.rollback() if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) # nosec