"""Application. The API application is a `flask` application. It provides simple features such as registering a url for a specific handlers, access control, login a user, and performing simple tasks. """ from functools import wraps from unittest.mock import MagicMock from flask import Flask from flask_executor import Executor from owslogger import flask_logger from owsrequest import flask_request from snowflake_connector.snowflake_conn import set_default_sessionmaker from werkzeug import exceptions from analytics import config from analytics.connectors import datadog app = Flask(config.SERVICE_NAME) flask_logger.setup( app, config.environment, config.LOGGER_NAME, config.LOGGER_LEVEL, config.SERVICE_NAME, config.SERVICE_VERSION, exclude_paths=[config.HEALTH_CHECK]) try: import uwsgi # noqa uwsgiRunning = True except ImportError: uwsgiRunning = False flask_request.setup( app, config.environment, uwsgi_cache_enabled=uwsgiRunning, add_request_context=True, label_profile=True, verify_access=True, rules_file='analytics/access_rules.yml', access_log_only=config.ONLY_LOG_ACCESS_ERRORS, exclude_paths=[config.HEALTH_CHECK]) executor = Executor(app) sf_config = { 'role': config.SNOWFLAKE_ROLE, 'account': config.SNOWFLAKE_ACCOUNT, 'user': config.SNOWFLAKE_USER, 'password': "dummy_password", # we're using key pair auth, pk is passed in config.SNOWFLAKE_CONNECT_ARGS # noqa 'database': config.SNOWFLAKE_DATABASE, 'schema': config.SNOWFLAKE_SCHEMA, 'warehouse': config.SNOWFLAKE_WAREHOUSE, } if config.environment == config.TEST_ENVIRONMENT: set_default_sessionmaker = MagicMock() else: # service-wide Snowflake connection pool settings set_default_sessionmaker( pool_size=config.SNOWFLAKE_POOL_SIZE, pool_recycle=config.SNOWFLAKE_POOL_RECYCLE, pool_pre_ping=config.SNOWFLAKE_POOL_PRE_PING, pool_reset_on_return=config.SNOWFLAKE_POOL_RESET_ON_RETURN, max_overflow=config.SNOWFLAKE_POOL_MAX_OVERFLOW, connect_args=config.SNOWFLAKE_CONNECT_ARGS, sf_config=sf_config ) def route(rule, **options): """Define a decorator to register a view function for a specific url. This route decorator introduces the ability to apply specific response format. The usage is similar to the Flask route, with some extra options: * **format** (``ResponseFormat``): Representation of the response format, by default: ``format_json``. * **metric** (``ResponseFormat``): Name of the metric when we send the information to datadog. Example: Add a new endpoint ``/user/create`` only allowed for POST requests:: @api.route('/user/create', methods=['POST']) def add_user(): pass Args: rule (string): The rule that corresponds to the resource. It will be converted into a regex. options (dict): The remaining options. They are mapping the flask options for the route (e.g. endpoint), and add some extra such as `format`. Returns: `callable`: The function decorator. """ def route_decorator(fn): metric = options.get('metric') @wraps(fn) def wrapper(*args, **kwargs): """Wrap the handler. The wrapper orchestrate the different steps of the request in our system: perform requirements checks, call the handler, and return the response. If the flag `metric` is set, send the metrics to datadog. Args: args (list): the list of arguments. kwargs (dict): the dictionary of arguments Raises: HTTPException: If an exception has been raised by using abort, this method will catch it. If the metric flag is set, we also count the number of failures. Returns: flask.Response: the request response. """ try: response = fn(*args, **kwargs) route_metric(response, metric) return response except exceptions.HTTPException as e: route_metric(e.get_response(), metric) raise e endpoint = options.pop('endpoint', fn.__name__) app.add_url_rule( rule, endpoint, wrapper, methods=options.get('methods', [])) return wrapper return route_decorator def route_metric(response, key, count=1): """Define a special metric handler for response. The response metrics need to be split by status (so we know how many times an endpoint returns a 200, 403, 404, etc.) Args: response (flask.Response): the response object. key (str): the key. count (int): the count to send. """ if not response or not key: return metric_name = datadog.key('analytics.handler.{key}.{status}'.format( key=key, status=response.status_code) ) datadog.publish_metric(metric_name, count) def run(debug=False): """Run the application. Args: debug (bool): If the application needs to be (or not) in debug mode. """ app.debug = debug app.run()