def client_get(self, ufs, secure=False): CDN(self.app) client = self.app.test_client() if secure: return client.get('/%s' % ufs, base_url='https://localhost') else: return client.get('/%s' % ufs)
def create_app(config_class=Config): static_folder = 'static' if os.environ.get('ENVIRONMENT') and os.environ.get('ENVIRONMENT') != "local": static_folder = os.environ.get('STATIC_PATH', "static") app = Flask(__name__, static_folder=static_folder) app.config.from_object(config_class) login.init_app(app) babel.init_app(app) csrf.init_app(app) if static_folder != 'static': CDN(app) app.register_blueprint(admin_blueprint, url_prefix='/admin') app.register_blueprint(rsvp_blueprint, url_prefix='/rsvp') login.login_message = None login.blueprint_login_views = { 'admin': 'admin.login', 'rsvp': 'rsvp.rsvp_captcha', } @app.errorhandler(404) def page_not_found(e): print(str(e)) return render_template('errors/404.html'), 404 return app
def setUp(self): self.app = Flask(__name__) self.app.testing = True self.app.config['CDN_DOMAIN'] = 'mycdnname.cloudfront.net' self.app.config['CDN_TIMESTAMP'] = True CDN(self.app) test_bp = Blueprint('test_bp', 'test') @test_bp.route('/without_static/<url_for_string>') def a(url_for_string): return render_template_string(url_for_string) self.app.register_blueprint(test_bp) test2_bp = Blueprint('test2_bp', 'test2', static_folder=self.app.static_folder + '_bp', static_url_path='/test2_bp/static') @test2_bp.route('/with_static/<url_for_string>') def b(url_for_string): return render_template_string(url_for_string) self.app.register_blueprint(test2_bp)
def init_app(app, views=None): views = views or VIEWS init_markdown(app) from . import helpers, error_handlers # noqa for view in views: _load_views(app, 'udata.{}.views'.format(view)) # Load all plugins views and blueprints for module in entrypoints.get_enabled('udata.views', app).values(): _load_views(app, module) # Load core manifest with app.app_context(): assets.register_manifest('udata') for dist in entrypoints.get_plugins_dists(app, 'udata.views'): if assets.has_manifest(dist.project_name): assets.register_manifest(dist.project_name) # Optionally register debug views if app.config.get('DEBUG'): @front.route('/403/') def test_403(): abort(403) @front.route('/404/') def test_404(): abort(404) @front.route('/500/') def test_500(): abort(500) # Load front only views and helpers app.register_blueprint(front) # Enable CDN if required if app.config['CDN_DOMAIN'] is not None: from flask_cdn import CDN CDN(app) # Load debug toolbar if enabled if app.config.get('DEBUG_TOOLBAR'): from flask_debugtoolbar import DebugToolbarExtension DebugToolbarExtension(app)
def init_app(app): from udata_gouvfr import theme nav.init_app(app) theme.init_app(app) from . import helpers, error_handlers # noqa if app.config['RESOURCES_SCHEMAGOUVFR_ENABLED']: VIEWS.append('schema') for view in VIEWS: _load_views(app, 'udata_gouvfr.views.{}'.format(view)) # Load all plugins views and blueprints for module in entrypoints.get_enabled('udata.views', app).values(): _load_views(app, module) # Optionally register debug views if app.config.get('DEBUG'): @front.route('/403/') def test_403(): abort(403) @front.route('/404/') def test_404(): abort(404) @front.route('/500/') def test_500(): abort(500) # Load front only views and helpers app.register_blueprint(front) # Enable CDN if required if app.config['CDN_DOMAIN'] is not None: from flask_cdn import CDN CDN(app) # Load debug toolbar if enabled if app.config.get('DEBUG_TOOLBAR'): from flask_debugtoolbar import DebugToolbarExtension DebugToolbarExtension(app)
def create_web_app(app: App) -> Flask: wsgi = Flask(__name__) wsgi.register_blueprint(ep) wsgi.register_blueprint(admin) wsgi.teardown_request(close_session) wsgi.errorhandler(NoResultFound)(handle_no_result_found) wsgi.jinja_env.filters['kst'] = to_kst wsgi.config.update(app.web_config) wsgi.config['APP'] = app wsgi.secret_key = app.secret_key wsgi.wsgi_app = SassMiddleware(wsgi.wsgi_app, { 'pycon2018': ('static/css', 'static/css', 'static/css') }) login_manager.init_app(wsgi) if wsgi.config.get('CDN_DOMAIN'): cdn = CDN() cdn.init_app(wsgi) if app.sentry_dsn: Sentry(wsgi, dsn=app.sentry_dsn) return wsgi
def create_app() -> Flask: """创建 flask app""" from everyclass.server.db.dao import new_user_id_sequence from everyclass.server.consts import MSG_INTERNAL_ERROR from everyclass.server.utils import plugin_available app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='', template_folder="../../frontend/templates") # load app config from everyclass.server.config import get_config _config = get_config() app.config.from_object(_config) # noqa: T484 """ 每课统一日志机制 规则如下: - DEBUG 模式下会输出 DEBUG 等级的日志,否则输出 INFO 及以上等级的日志 - 日志为 JSON 格式,会被节点上的 agent 采集并发送到 datadog,方便结合 metrics 和 APM 数据分析 - WARNING 以上级别的输出到 Sentry 做错误聚合 日志等级: critical – for errors that lead to termination error – for errors that occur, but are handled warning – for exceptional circumstances that might not be errors notice – for non-error messages you usually want to see info – for messages you usually don’t want to see debug – for debug messages Sentry: https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage - stack 默认是 False """ if app.config['DEBUG']: logging.getLogger().setLevel(logging.DEBUG) else: logging.getLogger().setLevel(logging.INFO) # CDN CDN(app) # moment Moment(app) # encrypted session app.session_interface = EncryptedSessionInterface() # 导入并注册 blueprints from everyclass.server.calendar.views import cal_blueprint from everyclass.server.query import query_blueprint from everyclass.server.views import main_blueprint as main_blueprint from everyclass.server.user.views import user_bp from everyclass.server.course_review.views import cr_blueprint app.register_blueprint(cal_blueprint) app.register_blueprint(query_blueprint) app.register_blueprint(main_blueprint) app.register_blueprint(user_bp, url_prefix='/user') # 初始化 RPC 模块 from everyclass.server.utils.resource_identifier_encrypt import encrypt from everyclass.rpc import init as init_rpc from everyclass.rpc.entity import Entity from everyclass.rpc.auth import Auth init_rpc(resource_id_encrypt_function=encrypt ) # 为 everyclass.rpc 模块注入 encrypt 函数 if 'API_SERVER_BASE_URL' in app.config: Entity.set_base_url(app.config['API_SERVER_BASE_URL']) if 'API_SERVER_TOKEN' in app.config: Entity.set_request_token(app.config['API_SERVER_TOKEN']) if 'AUTH_BASE_URL' in app.config: Auth.set_base_url(app.config['AUTH_BASE_URL']) # course review feature gating if app.config['FEATURE_GATING']['course_review']: app.register_blueprint(cr_blueprint, url_prefix='/course_review') @app.before_request def set_user_id(): """在请求之前设置 session uid,方便 APM 标识用户""" from everyclass.server.consts import SESSION_CURRENT_USER if not session.get('user_id', None) and request.endpoint not in ( "main.health_check", "static"): logger.info( f"Give a new user ID for new user. endpoint: {request.endpoint}" ) session['user_id'] = new_user_id_sequence() if session.get('user_id', None): tracer.current_root_span().set_tag("user_id", session['user_id']) # 唯一用户 ID if session.get(SESSION_CURRENT_USER, None): tracer.current_root_span().set_tag( "username", session[SESSION_CURRENT_USER]) # 学号 @app.before_request def log_request(): """日志中记录请求""" logger.info(f'Request received: {request.method} {request.path}') @app.before_request def delete_old_session(): """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)""" if request.cookies.get("session") and len( request.cookies.get("session")) > 50: session.clear() return redirect(request.url) @app.after_request def response_minify(response): """用 htmlmin 压缩 HTML,减轻带宽压力""" if app.config[ 'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8': response.set_data(minify(response.get_data(as_text=True))) return response @app.template_filter('versioned') def version_filter(filename): """ 模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css' :param filename: 文件名 :return: 新的文件名 """ if app.config['STATIC_VERSIONED']: if filename[:4] == 'css/': new_filename = app.config['STATIC_MANIFEST'][filename[4:]] return 'css/' + new_filename elif filename[:3] == 'js/': new_filename = app.config['STATIC_MANIFEST'][filename[3:]] return new_filename else: return app.config['STATIC_MANIFEST'][filename] return filename @app.errorhandler(500) def internal_server_error(error): if plugin_available("sentry"): return render_template( 'common/error.html', message=MSG_INTERNAL_ERROR, event_id=g.sentry_event_id, public_dsn=sentry.client.get_public_dsn('https')) return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format( repr(error)) global __app __app = app return app
login_manager = LoginManager() login_manager.init_app(app) # init flask principal principals = Principal(app) # init sqlmigration manager migrate = Migrate(app, db) manager = Manager(app) manager.add_command("db", MigrateCommand) # init SeaSurf seasurf = SeaSurf(app) # init flask CDN CDN(app) # init flask HTMLMIN HTMLMIN(app) # init assets environment assets = Environment(app) register_filter(BabiliFilter) class MiniJSONEncoder(JSONEncoder): """Minify JSON output.""" item_separator = ',' key_separator = ':' def default(self, obj):
def setUp(self): self.app = Flask(__name__) self.app.testing = True CDN(self.app)
def create_app() -> Flask: """创建 flask app""" from everyclass.server.db.dao import new_user_id_sequence from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING from everyclass.server.consts import MSG_INTERNAL_ERROR from everyclass.server.utils import plugin_available print("Creating app...") app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='', template_folder="../../frontend/templates") # load app config from everyclass.server.config import get_config _config = get_config() app.config.from_object(_config) # noqa: T484 """ 每课统一日志机制 规则如下: - WARNING 以下 log 输出到 stdout - WARNING 以上输出到 stderr - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心 - WARNING 以上级别的输出到 Sentry 日志等级: critical – for errors that lead to termination error – for errors that occur, but are handled warning – for exceptional circumstances that might not be errors notice – for non-error messages you usually want to see info – for messages you usually don’t want to see debug – for debug messages Sentry: https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage - stack 默认是 False """ if app.config['CONFIG_NAME'] in app.config['DEBUG_LOG_AVAILABLE_IN']: stdout_handler = logbook.StreamHandler( stream=sys.stdout, bubble=True, filter=lambda r, h: r.level < 13) else: # ignore debug when not in debug stdout_handler = logbook.StreamHandler( stream=sys.stdout, bubble=True, filter=lambda r, h: 10 < r.level < 13) stdout_handler.format_string = LOG_FORMAT_STRING logger.handlers.append(stdout_handler) stderr_handler = logbook.StreamHandler(stream=sys.stderr, bubble=True, level='WARNING') stderr_handler.format_string = LOG_FORMAT_STRING logger.handlers.append(stderr_handler) # CDN CDN(app) # moment Moment(app) # 导入并注册 blueprints from everyclass.server.calendar.views import cal_blueprint from everyclass.server.query import query_blueprint from everyclass.server.views import main_blueprint as main_blueprint from everyclass.server.user.views import user_bp from everyclass.server.course_review.views import cr_blueprint app.register_blueprint(cal_blueprint) app.register_blueprint(query_blueprint) app.register_blueprint(main_blueprint) app.register_blueprint(user_bp, url_prefix='/user') # course review feature gating if app.config['FEATURE_GATING']['course_review']: app.register_blueprint(cr_blueprint, url_prefix='/course_review') @app.before_request def set_user_id(): """在请求之前设置 session uid,方便 Elastic APM 记录用户请求""" if not session.get('user_id', None) and request.endpoint != "main.health_check": session['user_id'] = new_user_id_sequence() @app.before_request def delete_old_session(): """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)""" if request.cookies.get("session") and len( request.cookies.get("session")) > 50: session.clear() return redirect(request.url) @app.after_request def response_minify(response): """用 htmlmin 压缩 HTML,减轻带宽压力""" if app.config[ 'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8': response.set_data(minify(response.get_data(as_text=True))) return response @app.template_filter('versioned') def version_filter(filename): """ 模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css' :param filename: 文件名 :return: 新的文件名 """ if app.config['STATIC_VERSIONED']: if filename[:4] == 'css/': new_filename = app.config['STATIC_MANIFEST'][filename[4:]] return 'css/' + new_filename elif filename[:3] == 'js/': new_filename = app.config['STATIC_MANIFEST'][filename[3:]] return new_filename else: return app.config['STATIC_MANIFEST'][filename] return filename @app.errorhandler(500) def internal_server_error(error): if plugin_available("sentry"): return render_template( 'common/error.html', message=MSG_INTERNAL_ERROR, event_id=g.sentry_event_id, public_dsn=sentry.client.get_public_dsn('https')) return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format( repr(error)) global __app __app = app return app
def create_app(config_import="config.Config"): app = Flask(__name__) app.config.from_object(config_import) app.add_url_rule('/textures/<path:filename>', endpoint='textures', view_func=send_texture) from .models import db, SecretSanity db.app = app db.init_app(app) cdn = CDN(app) raygun = raygunprovider.RaygunSender(app.config['RAYGUN_APIKEY']) @app.errorhandler(500) def on_internal_server_error(e): raygun.send_exception(e.original_exception, sys.exc_info()) return e def fix_externals(func): """Fixes an issue where undesired values are send to the url by flask_cdn. Should probably be fixed in the library """ @functools.wraps(func) def decorator(self, endpoint, values=None, **kwargs): if values: values.pop('_external', None) return func(self, endpoint, values=values, **kwargs) return decorator MapAdapter.build = fix_externals(MapAdapter.build) app.wsgi_app = ProxyFix(app.wsgi_app) from .util import UserConverter app.url_map.converters['user'] = UserConverter from .api.v0 import apiv0 from .api.v1 import apiv1 app.register_blueprint(apiv0) app.register_blueprint(apiv1) from . import cli cli.init_app(app) @app.before_first_request def init_auth(): app.config['server_id'] = random_string(20) @app.before_request def secret_sanity_check(): secret = app.config['SECRET_KEY'] try: saved_secret = SecretSanity.query.one() if saved_secret.secret != secret: abort( 500, "Sanity error! Secret does not match, did the secret change?" ) except NoResultFound: db.session.add(SecretSanity(secret=secret)) db.session.commit() except MultipleResultsFound: abort( 500, "Multiple secrets found. Something has gone terribly wrong.") return app
def create_app(outside_container=False) -> Flask: """创建 flask app @param outside_container: 是否不在容器内运行 """ from everyclass.server.db.dao import new_user_id_sequence from everyclass.server.db.mysql import init_pool from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='', template_folder="../../frontend/templates") # load app config from everyclass.server.config import get_config _config = get_config() app.config.from_object(_config) """ 每课统一日志机制 规则如下: - WARNING 以下 log 输出到 stdout - WARNING 以上输出到 stderr - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心 - WARNING 以上级别的输出到 Sentry 日志等级: critical – for errors that lead to termination error – for errors that occur, but are handled warning – for exceptional circumstances that might not be errors notice – for non-error messages you usually want to see info – for messages you usually don’t want to see debug – for debug messages Sentry: https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage - stack 默认是 False """ stdout_handler = logbook.StreamHandler(stream=sys.stdout, bubble=True, filter=lambda r, h: r.level < 13) stdout_handler.format_string = LOG_FORMAT_STRING logger.handlers.append(stdout_handler) stderr_handler = logbook.StreamHandler(stream=sys.stderr, bubble=True, level='WARNING') stderr_handler.format_string = LOG_FORMAT_STRING logger.handlers.append(stderr_handler) # CDN CDN(app) # 容器外运行(无 uWSGI)时初始化数据库 if outside_container and (app.config['CONFIG_NAME'] == "development"): init_pool(app) # 导入并注册 blueprints from everyclass.server.calendar.views import cal_blueprint from everyclass.server.query import query_blueprint from everyclass.server.views import main_blueprint as main_blueprint from everyclass.server.api import api_v1 as api_blueprint app.register_blueprint(cal_blueprint) app.register_blueprint(query_blueprint) app.register_blueprint(main_blueprint) app.register_blueprint(api_blueprint, url_prefix='/api/v1') @app.before_request def set_user_id(): """在请求之前设置 session uid,方便 Elastic APM 记录用户请求""" if not session.get('user_id', None): session['user_id'] = new_user_id_sequence() @app.after_request def response_minify(response): """用 htmlmin 压缩 HTML,减轻带宽压力""" if app.config[ 'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8': response.set_data(minify(response.get_data(as_text=True))) return response @app.template_filter('versioned') def version_filter(filename): """ 模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css' :param filename: 文件名 :return: 新的文件名 """ if app.config['STATIC_VERSIONED']: if filename[:4] == 'css/': new_filename = app.config['STATIC_MANIFEST'][filename[4:]] return 'css/' + new_filename elif filename[:3] == 'js/': new_filename = app.config['STATIC_MANIFEST'][filename[3:]] return new_filename else: return app.config['STATIC_MANIFEST'][filename] return filename @app.errorhandler(500) def internal_server_error(error): return render_template( '500.html', event_id=g.sentry_event_id, public_dsn=sentry.client.get_public_dsn('https')) global __app __app = app return app
import logging from datetime import timedelta from cidc_utils.loghandler import StackdriverJsonFormatter from flask import Flask, session from flask_cdn import CDN from constants import FLASK_SECRET_KEY, SESSION_TIMEOUT_MINUTES APP = Flask(__name__, static_folder='static') APP.config["SECRET_KEY"] = FLASK_SECRET_KEY APP.config['CDN_DOMAIN'] = "https://storage.googleapis.com/cidc-js-build/" APP.url_map.strict_slashes = False CDN_INSTANCE = CDN() def configure_logging(): """ Configures the loghandler to send formatted logs to stackdriver. """ # Configure Stackdriver logging. logger = logging.getLogger() logger.setLevel("INFO") log_handler = logging.StreamHandler() log_handler.setFormatter(StackdriverJsonFormatter()) logger.addHandler(log_handler) logging.info({"message": "LOGGER CONFIGURED", "category": "INFO-EVE-LOGGING"}) @APP.before_request def make_session_permanent():
def create_app() -> Flask: """创建 flask app""" from everyclass.server.utils.web_consts import MSG_INTERNAL_ERROR from everyclass.server import plugin_available from everyclass.server.utils import generate_error_response, api_helpers, base_exceptions from everyclass.common.env import is_production from everyclass.server.utils.web_consts import MSG_404 app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='', template_folder="../../frontend/templates") # load app config from everyclass.server.utils.config import get_config _config = get_config() app.config.from_object(_config) # noqa: T484 """ 每课统一日志机制 规则如下: - DEBUG 模式下会输出 DEBUG 等级的日志,否则输出 INFO 及以上等级的日志 - 日志为 JSON 格式,会被节点上的 agent 采集并发送到 datadog,方便结合 metrics 和 APM 数据分析 - WARNING 以上级别的输出到 Sentry 做错误聚合 日志等级: critical – for errors that lead to termination error – for errors that occur, but are handled warning – for exceptional circumstances that might not be errors notice – for non-error messages you usually want to see info – for messages you usually don’t want to see debug – for debug messages Sentry: https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage - stack 默认是 False """ if app.config['DEBUG']: logging.getLogger().setLevel(logging.DEBUG) else: logging.getLogger().setLevel(logging.INFO) # CDN CDN(app) # moment Moment(app) # encrypted session app.session_interface = EncryptedSessionInterface() # 导入并注册 blueprints from everyclass.server.calendar.views import calendar_bp from everyclass.server.calendar.views_api import calendar_api_bp from everyclass.server.entity.views import entity_bp from everyclass.server.entity.views_api import entity_api_bp from everyclass.server.user.views import user_bp from everyclass.server.user.views_api import user_api_bp from everyclass.server.course.views import course_bp from everyclass.server.course.views_api import course_api_bp from everyclass.server.views_main import main_blueprint app.register_blueprint(calendar_bp) app.register_blueprint(entity_bp) app.register_blueprint(user_bp, url_prefix='/user') app.register_blueprint(entity_api_bp, url_prefix='/mobile/entity') app.register_blueprint(user_api_bp, url_prefix='/mobile/user') app.register_blueprint(calendar_api_bp, url_prefix='/mobile/calendar') if 'course' in _config.FEATURE_GATING and _config.FEATURE_GATING['course']: app.register_blueprint(course_api_bp, url_prefix='/mobile/course') app.register_blueprint(course_bp, url_prefix='/course') app.register_blueprint(main_blueprint) # 初始化 RPC 模块 from everyclass.rpc.auth import Auth if 'AUTH_BASE_URL' in app.config: Auth.set_base_url(app.config['AUTH_BASE_URL']) @app.before_request def set_user_id(): """在请求之前设置 session uid,方便 APM 标识用户""" from everyclass.server.utils.web_consts import SESSION_CURRENT_USER, SESSION_USER_SEQ from everyclass.server.user import service as user_service if not session.get('user_id', None) and request.endpoint not in ( "main.health_check", "static"): logger.info( f"Give a new user ID for new user. endpoint: {request.endpoint}" ) session['user_id'] = user_service.get_user_id() if session.get('user_id', None): tracer.current_root_span().set_tag( "user_id", session[SESSION_USER_SEQ]) # 唯一用户 ID if session.get(SESSION_CURRENT_USER, None): tracer.current_root_span().set_tag( "username", session[SESSION_CURRENT_USER].identifier) # 学号或教工号 @app.before_request def log_request(): """日志中记录请求""" logger.info(f'Request received: {request.method} {request.path}') @app.after_request def response_minify(response): """用 htmlmin 压缩 HTML,减轻带宽压力""" if app.config[ 'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8': response.set_data(minify(response.get_data(as_text=True))) return response from everyclass.server.utils.db.postgres import db_session @app.teardown_appcontext def shutdown_db_session(exception=None): db_session.remove() @app.template_filter('versioned') def version_filter(filename): """ 模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css' :param filename: 文件名 :return: 新的文件名 """ if app.config['STATIC_VERSIONED']: if filename[:4] == 'css/': new_filename = app.config['STATIC_MANIFEST'][filename[4:]] return 'css/' + new_filename elif filename[:3] == 'js/': new_filename = app.config['STATIC_MANIFEST'][filename[3:]] return new_filename else: return app.config['STATIC_MANIFEST'][filename] return filename @app.context_processor def inject_consts(): """允许在模板中使用常量模块,以便使用session key等常量而不用在模板中硬编码""" return dict(consts=web_consts, api_base_url=app.config['MOBILE_API_BASE_URL']) @app.errorhandler(404) def page_not_found(error): if request.path.startswith('/mobile'): return generate_error_response( None, api_helpers.STATUS_CODE_INVALID_REQUEST, "no such API") return render_template('common/error.html', message=MSG_404) @app.errorhandler(base_exceptions.BizException) def handle_biz_exception(error: base_exceptions.BizException): if request.path.startswith("/mobile"): if isinstance(error, base_exceptions.InternalError): logger.error(repr(error)) # 业务错误的status_message可以对外展示 actual_error = {'status_message_overwrite': error.status_message} return generate_error_response(None, error.status_code, **actual_error) @app.errorhandler(500) def internal_server_error(error): if request.path.startswith("/mobile"): # 对于非业务错误,生产环境中不进行返回,其他环境中可返回 actual_error = { 'status_message_overwrite': f"server internal error: {repr(error.original_exception)}" } if not is_production() else {} return generate_error_response( None, api_helpers.STATUS_CODE_INTERNAL_ERROR, **actual_error) if plugin_available("sentry"): return render_template( 'common/error.html', message=MSG_INTERNAL_ERROR, event_id=g.sentry_event_id, public_dsn=sentry.client.get_public_dsn('https')) return f"<h4>500 Error: {repr(error.original_exception)}</h4><br>You are seeing this page because Sentry is not available." global __app __app = app return app
from flask import Flask, flash, render_template, request from flask_cdn import CDN from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import SubmitField from werkzeug.utils import secure_filename import os app = Flask(__name__) app.config.from_object('config') cdn = CDN(app) class UploadForm(FlaskForm): image = FileField('image') submit = SubmitField('Create') def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] @app.route('/', methods=['GET', 'POST']) def file_upload(): form = UploadForm() if form.validate_on_submit(): file_upload = form.image.data file_name = secure_filename(file_upload.filename) if allowed_file(file_name): s3 = app.config['S3']
Sentry(flapp, dsn=sentry_dsn) except Exception as e: logger.info("no sentry logging") logger.error(e) logging.basicConfig(level=logging.DEBUG) mc_logger = logging.getLogger('mediacloud') requests_logger = logging.getLogger('requests') logger.info( "---------------------------------------------------------------------------------------" ) # Flask app config if config.get('custom', 'use_cdn') == 'true': flapp.config['CDN_DOMAIN'] = 'd31f66kh11e0nw.cloudfront.net' CDN(flapp) flapp.config['FLASK_ASSETS_USE_CDN'] = True flapp.secret_key = 'put secret key here' flapp.config['SEND_FILE_MAX_AGE_DEFAULT'] = 7 * 24 * 60 * 60 assets = Environment(flapp) # Create media cloud api app_mc_key = config.get('mediacloud', 'key') mc = mcapi.AdminMediaCloud(app_mc_key) logger.info("Connected to MediaCloud with default key %s" % (app_mc_key)) logging.getLogger('MediaCloud').setLevel(logging.DEBUG) # Create user login manager login_manager = flask_login.LoginManager() login_manager.init_app(flapp)
naming_convention = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", "ck": "ck_%(table_name)s_%(column_0_name)s", "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "pk": "pk_%(table_name)s" } db = SQLAlchemy(metadata=MetaData(naming_convention=naming_convention)) cache = Cache() csrf = CsrfProtect() migrate = Migrate() manager = VersioningManager(options={'strategy': 'subquery'}) make_versioned(manager=manager, plugins=[FlaskPlugin()]) mail = Mail() cdn = CDN() login_manager = LoginManager() assets = Environment() toolbar = DebugToolbarExtension() gocardless_client = None volunteer_admin = None pyscss = get_filter('pyscss', style='compressed') assets.register( 'css_main', Bundle('css/main.scss', output='gen/main-packed.css', depends='css/*.scss', filters=pyscss)) assets.register( 'css_admin',
def create_app(offline=False) -> Flask: """创建 flask app @param offline: 如果设置为 `True`,则为离线模式。此模式下不会连接到 Sentry 和 ElasticAPM """ app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='', template_folder="../../frontend/templates") # load app config from everyclass.server.config import get_config _config = get_config() app.config.from_object(_config) # CDN CDN(app) # logbook handlers # 规则如下: # - 全部输出到 stdout(本地开发调试、服务器端文件日志) # - Elastic APM 或者 LogStash(日志中心) # - WARNING 以上级别的输出到 Sentry # # 日志等级: # critical – for errors that lead to termination # error – for errors that occur, but are handled # warning – for exceptional circumstances that might not be errors # notice – for non-error messages you usually want to see # info – for messages you usually don’t want to see # debug – for debug messages # # Elastic APM: # log.info("Nothing to see here", stack=False) # stack 默认是 True,设置为 False 将不会向 Elastic APM 发送 stack trace # https://discuss.elastic.co/t/how-to-use-logbook-handler/146209/6 # # Sentry: # https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage # - stack 默认是 False,和 Elastic APM 的不一致,所以还是每次手动指定吧... # - 默认事件类型是 `raven.events.Message`,设置 `exc_info` 为 `True` 将把事件类型升级为`raven.events.Exception` stderr_handler = logbook.StderrHandler(bubble=True) logger.handlers.append(stderr_handler) if not offline: # Sentry sentry.init_app(app=app) sentry_handler = SentryHandler( sentry.client, level='WARNING') # Sentry 只处理 WARNING 以上的 logger.handlers.append(sentry_handler) # Elastic APM apm = ElasticAPM(app) elastic_handler = ElasticHandler(client=apm.client, bubble=True) logger.handlers.append(elastic_handler) # 初始化数据库 if os.getenv('MODE', None) != "CI": init_pool(app) # 导入并注册 blueprints from everyclass.server.calendar.views import cal_blueprint from everyclass.server.query import query_blueprint from everyclass.server.views import main_blueprint as main_blueprint from everyclass.server.api import api_v1 as api_blueprint app.register_blueprint(cal_blueprint) app.register_blueprint(query_blueprint) app.register_blueprint(main_blueprint) app.register_blueprint(api_blueprint, url_prefix='/api/v1') @app.before_request def set_user_id(): """在请求之前设置 session uid,方便 Elastic APM 记录用户请求""" if not session.get('user_id', None): # 数据库中生成唯一 ID,参考 https://blog.csdn.net/longjef/article/details/53117354 conn = get_local_conn() cursor = conn.cursor() cursor.execute( "REPLACE INTO user_id_sequence (stub) VALUES ('a');") session['user_id'] = cursor.lastrowid cursor.close() @app.teardown_request def close_db(error): """结束时关闭数据库连接""" if hasattr(g, 'mysql_db') and g.mysql_db: g.mysql_db.close() @app.after_request def response_minify(response): """用 htmlmin 压缩 HTML,减轻带宽压力""" if app.config[ 'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8': response.set_data(minify(response.get_data(as_text=True))) return response @app.template_filter('versioned') def version_filter(filename): """ 模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css' :param filename: 文件名 :return: 新的文件名 """ if app.config['STATIC_VERSIONED']: if filename[:4] == 'css/': new_filename = app.config['STATIC_MANIFEST'][filename[4:]] return 'css/' + new_filename elif filename[:3] == 'js/': new_filename = app.config['STATIC_MANIFEST'][filename[3:]] return new_filename else: return app.config['STATIC_MANIFEST'][filename] return filename @app.errorhandler(500) def internal_server_error(error): return render_template( '500.html', event_id=g.sentry_event_id, public_dsn=sentry.client.get_public_dsn('https')) logger.info('App created with `{}` config'.format( app.config['CONFIG_NAME']), stack=False) return app