import os from logging.config import dictConfig from typing import Any, Dict from authlib.integrations.requests_client import OAuthError from connexion import FlaskApp, RestyResolver from flask import redirect from flask_cors import CORS from swagger_ui_bundle import swagger_ui_3_path from slz_api_service.const import SWAGGER_SPEC_PATH from slz_api_service.core.tracing import configure_logging, log_request from slz_api_service.core.utility_views import HealthView from slz_api_service.errors import ( ApplicationError, AuthError, ConfigurationError, DynamoDBQueryError, InvalidInputError, RangeError, S3QueryError, ) from slz_api_service.errors.handlers import ( handle_auth_error, handle_generic_error, handle_invalid_input_error, ) def get_app(name: str = __name__.split('.')[0]): # see: https://flask.palletsprojects.com/en/1.1.x/api/#application-object # Logging config before app instantiation dictConfig({ 'version': 1, 'formatters': { 'default': { 'format': '%(message)s', } }, 'handlers': { 'wsgi': { 'class': 'logging.StreamHandler', 'stream': 'ext://flask.logging.wsgi_errors_stream', 'formatter': 'default' } }, 'root': { 'level': 'INFO', 'handlers': ['wsgi'] } }) options = { 'swagger_path': swagger_ui_3_path, } main = FlaskApp(name, options=options, resolver=RestyResolver('slz_api_service.v1.views')) main.add_api(SWAGGER_SPEC_PATH, strict_validation=True, validate_responses=True) main.app.config.from_object(os.getenv('FLASK_CONFIG_MODULE', 'slz_api_service.config.local')) # validate config check_config(main.app.config) # add CORS support CORS(main.app, origins=main.app.config['ALLOWED_ORIGINS']) # call config for structlog # don't cache the logger if app is in debug mode (for testing) configure_logging(cache_logger=main.app.config.get('DEBUG', True)) # log requests after completion main.app.after_request(log_request) # Register routes non-standard vs. the swagger paths main.add_url_rule('/', view_func=lambda: redirect('/v1/ui')) main.add_url_rule('/health', view_func=HealthView.get) # Register error handlers main.app.register_error_handler(AuthError, handle_auth_error) main.app.register_error_handler(OAuthError, handle_auth_error) main.app.register_error_handler(InvalidInputError, handle_invalid_input_error) main.app.register_error_handler(DynamoDBQueryError, handle_generic_error) main.app.register_error_handler(S3QueryError, handle_generic_error) main.app.register_error_handler(RangeError, handle_invalid_input_error) main.app.register_error_handler(ApplicationError, handle_generic_error) return main def check_config(config: Dict[str, Any]) -> None: if not config['SOURCE_DATA_TABLE_NAME']: raise ConfigurationError('Missing required configuration parameter: SOURCE_DATA_TABLE_NAME')