"""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 import logging import os from flask import Flask from owslogger import flask_logger from owslogger.logger import DSNHandler 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 statsd app = Flask(config.SERVICE_NAME) flask_logger.setup( app, config.LOGGER_DSN, config.environment, config.LOGGER_NAME, config.LOGGER_LEVEL, config.SERVICE_NAME, config.SERVICE_VERSION, exclude_paths=[config.HEALTH_CHECK]) flask_request.setup(app, config.environment) sf_config = {} if os.getenv('SNOWFLAKE_INSECURE_MODE'): sf_config['insecure_mode'] = 'True' # service-wide Snowflake connection pool settings set_default_sessionmaker( sf_config=sf_config, 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 ) # todo: (@vbogatyrev) need to investigate performance issues debug_handler = DSNHandler( config.LOGGER_DSN, config.environment, config.SERVICE_NAME, config.SERVICE_VERSION) debug_handler.setLevel(logging.DEBUG) con_logger = logging.getLogger('snowflake.connector.connection') con_logger.setLevel(logging.DEBUG) con_logger.addHandler(debug_handler) cur_logger = logging.getLogger('snowflake.connector.cursor') cur_logger.setLevel(logging.DEBUG) cur_logger.addHandler(debug_handler) 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 statsd. 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 statsd. 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 statsd_key = statsd.key('analytics.handler.{key}.{status}'.format( key=key, status=response.status_code)) statsd.send_metric(statsd_key, count=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()