def create_app(config: dict = None): app = Flask(__name__, static_url_path='', instance_relative_config=True) if config: app.config.update(config) else: app.config.from_envvar("CMSERVICE_CONFIG") mako = MakoTemplates() mako.init_app(app) app._mako_lookup = TemplateLookup( directories=[ pkg_resources.resource_filename('cmservice.service', 'templates') ], input_encoding='utf-8', output_encoding='utf-8', imports=['from flask_babel import gettext as _']) app.cm = init_consent_manager(app) babel = Babel(app) babel.localeselector(get_locale) app.config[ 'BABEL_TRANSLATION_DIRECTORIES'] = pkg_resources.resource_filename( 'cmservice.service', 'data/i18n/locales') from .views import consent_views app.register_blueprint(consent_views) setup_logging(app.config.get('LOGGING_LEVEL', 'INFO')) logger = logging.getLogger(__name__) logger.info("Running CMservice version %s", pkg_resources.get_distribution("CMservice").version) return app
def setup_app(config, app): global LOCALES global babel translation_dirs = getattr(config, 'TRANSLATION_DIRS', None) if translation_dirs is None: translation_dirs = \ path.join(path.dirname(path.realpath(__file__)), 'translations') # `babel.translation_directories` is a nightmare # We need to set this manually via an absolute path app.config['BABEL_TRANSLATION_DIRECTORIES'] = translation_dirs babel = Babel(app) if len(list(babel.translation_directories)) != 1: raise AssertionError( 'Expected exactly one translation directory but got {}.' .format(babel.translation_directories)) translation_directories = next(babel.translation_directories) for dirname in os.listdir(translation_directories): if dirname != 'messages.pot': LOCALES.append(dirname) LOCALES = _get_supported_locales( LOCALES, getattr(config, 'SUPPORTED_LOCALES', None), getattr(config, 'DEFAULT_LOCALE', None), translation_directories) babel.localeselector(lambda: get_locale(config))
def setup_app(config, app): global LOCALES global babel translation_dirs = getattr(config, 'TRANSLATION_DIRS', None) if translation_dirs is None: translation_dirs = path.join(path.dirname(path.realpath(__file__)), 'translations') # `babel.translation_directories` is a nightmare # We need to set this manually via an absolute path app.config['BABEL_TRANSLATION_DIRECTORIES'] = translation_dirs babel = Babel(app) if len(list(babel.translation_directories)) != 1: raise AssertionError( 'Expected exactly one translation directory but got {}.'.format( babel.translation_directories)) translation_directories = next(babel.translation_directories) for dirname in os.listdir(translation_directories): if dirname != 'messages.pot': LOCALES.append(dirname) LOCALES = _get_supported_locales( LOCALES, getattr(config, 'SUPPORTED_LOCALES', None), getattr(config, 'DEFAULT_LOCALE', None), translation_directories) babel.localeselector(lambda: get_locale(config))
def app(): app = Flask(__name__) babel = Babel(app, default_locale="fr", default_timezone=USER_TZ) babel.localeselector(en_locale) babel.timezoneselector(user_tz) with app.app_context(): yield app
def create_app(config=None): app = Flask(__name__.split('.')[0]) app_config = { # Defaults 'GALLERY_ROOT': os.path.abspath(os.path.join( os.path.dirname(__file__), '..', 'gallery' )), 'REDIS_URL': 'redis://localhost:6379/0', } if config is not None: app_config.update({key.upper(): value for key, value in config.items()}) if 'DEBUG' in app_config: if app_config['DEBUG'] in ('0', 'n', 'no', 'False', 'false'): app_config.update({'DEBUG': False}) else: app_config.update({'DEBUG': True}) app.config.update(app_config) app.add_url_rule('/galleries/<path:path>', view_func=send_pic, methods=['GET']) babel = Babel(app) babel.localeselector(get_locale) app.jinja_env.filters['date'] = format_date app.jinja_env.filters['datetime'] = format_datetime GalleryView.register(app) return app
def create_app(config: dict = {}, mail_client=None): app = Flask(__name__, static_folder='static') if config: app.config.update(config) else: app.config.from_envvar('ALSERVICE_CONFIG') MakoTemplates(app) app._mako_lookup = TemplateLookup(directories=[pkg_resources.resource_filename('alservice.service', 'templates')], input_encoding='utf-8', output_encoding='utf-8', imports=['from flask_babel import gettext as _']) app.al = init_account_linking(app, mail_client) babel = Babel(app) babel.localeselector(get_locale) app.config['BABEL_TRANSLATION_DIRECTORIES'] = pkg_resources.resource_filename('alservice.service', 'data/i18n/locales') from .views import account_linking_views app.register_blueprint(account_linking_views) setup_logging(app.config.get('LOGGING_LEVEL', 'INFO')) logger = logging.getLogger(__name__) logger.info('Running ALservice version %s', pkg_resources.get_distribution('ALservice').version) return app
def create_app(config: dict = None): app = Flask(__name__, static_url_path='', instance_relative_config=True) if config: app.config.update(config) else: app.config.from_envvar("CMSERVICE_CONFIG") mako = MakoTemplates() mako.init_app(app) app._mako_lookup = TemplateLookup(directories=[pkg_resources.resource_filename('cmservice.service', 'templates')], input_encoding='utf-8', output_encoding='utf-8', imports=['from flask_babel import gettext as _']) app.cm = init_consent_manager(app) babel = Babel(app) babel.localeselector(get_locale) app.config['BABEL_TRANSLATION_DIRECTORIES'] = pkg_resources.resource_filename('cmservice.service', 'data/i18n/locales') from .views import consent_views app.register_blueprint(consent_views) setup_logging(app.config.get('LOGGING_LEVEL', 'INFO')) logger = logging.getLogger(__name__) logger.info("Running CMservice version %s", pkg_resources.get_distribution("CMservice").version) return app
def setup_app(config: SDConfig, app: Flask) -> None: global LOCALES global babel # `babel.translation_directories` is a nightmare # We need to set this manually via an absolute path app.config['BABEL_TRANSLATION_DIRECTORIES'] = str( config.TRANSLATION_DIRS.absolute()) babel = Babel(app) if len(list(babel.translation_directories)) != 1: raise AssertionError( 'Expected exactly one translation directory but got {}.'.format( babel.translation_directories)) translation_directories = next(babel.translation_directories) for dirname in os.listdir(translation_directories): if dirname != 'messages.pot': LOCALES.append(dirname) LOCALES = _get_supported_locales(LOCALES, config.SUPPORTED_LOCALES, config.DEFAULT_LOCALE, translation_directories) babel.localeselector(lambda: get_locale(config))
def init_app(app, **kwargs): """Initialize the Flask app located in the module sipa. This initializes the Flask app by: * calling the internal init_app() procedures of each module * registering the Blueprints * registering the Jinja global variables :return: None """ load_config_file(app, config=kwargs.pop('config', None)) app.wsgi_app = ProxyFix(app.wsgi_app, app.config['NUM_PROXIES']) init_logging(app) init_env_and_config(app) logger.debug('Initializing app') login_manager.init_app(app) babel = Babel() babel.init_app(app) babel.localeselector(select_locale) app.before_request(save_user_locale_setting) app.session_interface = SeparateLocaleCookieSessionInterface() cf_pages = CategorizedFlatPages() cf_pages.init_app(app) backends = build_backends_ext() backends.init_app(app) QRcode(app) app.url_map.converters['int'] = IntegerConverter from sipa.blueprints import bp_features, bp_usersuite, \ bp_pages, bp_documents, bp_news, bp_generic, bp_hooks logger.debug('Registering blueprints') app.register_blueprint(bp_generic) app.register_blueprint(bp_features) app.register_blueprint(bp_usersuite) app.register_blueprint(bp_pages) app.register_blueprint(bp_documents) app.register_blueprint(bp_news) app.register_blueprint(bp_hooks) logger.debug('Registering Jinja globals') form_label_width = 3 form_input_width = 7 app.jinja_env.globals.update( cf_pages=cf_pages, get_locale=get_locale, get_weekday=get_weekday, possible_locales=possible_locales, get_attribute_endpoint=get_attribute_endpoint, should_display_traffic_data=should_display_traffic_data, traffic_chart=provide_render_function(generate_traffic_chart), current_datasource=backends.current_datasource, form_label_width_class="col-sm-{}".format(form_label_width), form_input_width_class="col-sm-{}".format(form_input_width), form_input_offset_class="col-sm-offset-{}".format(form_label_width), url_self=url_self, ) logger.debug("Jinja globals have been set", extra={'data': {'jinja_globals': app.jinja_env.globals}}) backends.init_backends()
def init_locale(server): babel = Babel(default_locale='fi') babel.init_app(server) server.add_url_rule('/language/<lang>', 'change_language', view_func=change_language) # Monkeypatch Plotly to accept lazystrings plotly_utils.PlotlyJSONEncoder = JSONEncoder babel.localeselector(get_active_locale)
def decorate_helper(app): # Add custom template filters for key, func in filter_list_mapping.items(): app.template_filter(key)(func) babel = Babel(app) def get_locale(): translations = [ str(translation) for translation in babel.list_translations() ] return request.accept_languages.best_match(translations) babel.localeselector(get_locale) return app
def create_app(): app = Flask(__name__) app.config["BABEL_TRANSLATION_DIRECTORIES"] = "../translations" app.add_url_rule("/", browse.__name__, browse, methods=("GET", )) app.add_url_rule("/", add_entry.__name__, add_entry, methods=("POST", )) app.add_url_rule("/entry/<int:idx>", delete_entry.__name__, delete_entry, methods=("DELETE", )) babel = Babel(app) babel.localeselector(lambda: request.accept_languages.best_match( ("en", "pl"))) return app
def configure_babel(config: SDConfig, app: Flask) -> None: """ Set up Flask-Babel according to the SecureDrop configuration. """ # Tell Babel where to find our translations. translations_directory = str(config.TRANSLATION_DIRS.absolute()) app.config["BABEL_TRANSLATION_DIRECTORIES"] = translations_directory # Create the app's Babel instance. Passing the app to the # constructor causes the instance to attach itself to the app. babel = Babel(app) # verify that Babel is only using the translations we told it about if list(babel.translation_directories) != [translations_directory]: raise ValueError( "Babel translation directories ({}) do not match SecureDrop configuration ({})" .format(babel.translation_directories, [translations_directory])) # register the function used to determine the locale of a request babel.localeselector(lambda: get_locale(config))
def init_app(): app = Flask(__name__) app.config.from_object('aslo.settings') from .i18n import set_lang_redirect, get_app_locale set_lang_redirect(app) # init Babel babel = Babel(app) babel.localeselector(get_app_locale) # init celery from .celery_app import init_celery init_celery(app) # blueprints from .web import web app.register_blueprint(web) from .api import api app.register_blueprint(api, url_prefix='/api') # logging logger.setLevel(logging.INFO) fmt = logging.Formatter('[%(asctime)s] %(levelname).3s %(message)s') stream_handler = logging.StreamHandler() stream_handler.setFormatter(fmt) logger.addHandler(stream_handler) if app.config['DEBUG']: logger.setLevel(logging.DEBUG) # setup db from .service import setup_db setup_db(app) # custom jinja filters from .filters import init_filters init_filters(app) return app
def setup_app(app, translation_dirs=None): global babel if translation_dirs is None: translation_dirs = \ path.join(path.dirname(path.realpath(__file__)), 'translations') # `babel.translation_directories` is a nightmare # We need to set this manually via an absolute path app.config['BABEL_TRANSLATION_DIRECTORIES'] = translation_dirs babel = Babel(app) if len(list(babel.translation_directories)) != 1: raise AssertionError( 'Expected exactly one translation directory but got {}.'.format( babel.translation_directories)) for dirname in os.listdir(next(babel.translation_directories)): if dirname != 'messages.pot': LOCALES.add(dirname) babel.localeselector(get_locale)
class TCC3Babel(object): DEFAULT_LOCALE = 'zh_Hans_CN' def __init__(self, app=None): self._locale = TCC3Babel.DEFAULT_LOCALE self.babel = Babel() if app is not None: self.init_app(app) def init_app(self, app): """:type app : Flask""" self.babel.init_app(app) self.babel.localeselector(TCC3Babel.get_locale) def set_locale(self, locale=DEFAULT_LOCALE): self._locale = locale # TODO @classmethod def get_locale(cls): locale = request.accept_languages.best_match(LANGUAGES.keys()) print('locale: ', locale) return locale
def create_app(self): app = Flask(__name__) babel = Babel(app, default_locale='fr', default_timezone=USER_TZ) babel.localeselector(en_locale) babel.timezoneselector(user_tz) return app
class AppKernelEngine(object): def __init__(self, app_id: str, app: Flask = None, root_url: str = '/', log_level=logging.DEBUG, cfg_dir: str = None, development: bool = False, enable_defaults: bool = True): """ Initialiser of Flask Engine. :param app: the Flask App :type app: Flask :param root_url: the url where the service are exposed to. :type root_url: str :param log_level: the level of log :param cfg_dir: the directory containing the cfg.yml file. If not provided it will be taken from the command line or from current working dir; :param development: the system will be initialised in development mode if True. If None, it will try to read the value as command line parameter or default to false; :type log_level: logging """ assert app_id is not None, 'The app_id must be provided' assert re.match( '[A-Za-z0-9-_]', app_id ), 'The app_id must be a single word, no space or special characters except - or _ .' self.app: Flask = app or Flask(app_id) assert self.app is not None, 'The Flask App must be provided as init parameter.' try: config.service_registry = {} self.before_request_functions = [] self.after_request_functions = [] self.app_id = app_id self.root_url = root_url self.__configure_flask_app() self.__init_web_layer() self.cmd_line_options = get_cmdline_options() self.cfg_dir = cfg_dir or self.cmd_line_options.get('cfg_dir') self.cfg_engine = CfgEngine(self.cfg_dir, optional=enable_defaults) config.cfg_engine = self.cfg_engine self.__init_babel() self.__init_cross_cutting_concerns() self.__init_event_loop() self.development = development or self.cmd_line_options.get( 'development') cwd = self.cmd_line_options.get('cwd') self.init_logger(log_folder=cwd, level=log_level) # -- initialisation # this can raise false positives if a bit of code running # longer than 1 seconds. # the timeout can be increased by adding the parameter: # resolution=3, where the value 3 represents 3 seconds. eventlet.debug.hub_blocking_detection(True, resolution=3) atexit.register(self.shutdown_hook) if hasattr(app, 'teardown_appcontext'): app.teardown_appcontext(self.teardown) elif hasattr(app, 'teardown_request'): app.teardown_request(self.teardown) # -- database host db_host = self.cmd_line_options.get('db') or self.cfg_engine.get( 'appkernel.mongo.host', 'localhost') db_name = self.cfg_engine.get('appkernel.mongo.db', 'app') self.mongo_client = MongoClient(host=db_host) config.mongo_database = self.mongo_client[db_name] config.flask_app: Flask = self.app config.app_engine = self except (AppInitialisationError, AssertionError) as init_err: # print >> sys.stderr, self.app.logger.error(init_err.message) sys.exit(-1) def enable_security(self, authorisation_method=None): self.enable_pki() if not authorisation_method: authorisation_method = authorize_request self.add_before_request_function(authorisation_method) config.security_enabled = True return self def enable_pki(self): if not hasattr(self.app, 'public_key'): self.__init_crypto() def add_before_request_function(self, func): self.before_request_functions.append(func) def add_after_request_function(self, func): self.after_request_functions.append(func) @staticmethod def __init_event_loop(): # todo: implement event loop # config.event_loop = asyncio.get_event_loop() pass def shutdown_eventloop(): if 'event_loop' in config.__dict__ and config.event_loop and config.event_loop.is_running( ): logging.info('shutting down the event loop.') config.event_loop.shutdown_asyncgens() config.event_loop.stop() atexit.register(shutdown_eventloop) def __init_cross_cutting_concerns(self): def create_function_chain_executor(chain): def function_chain_executor(): for func in chain: return func() return function_chain_executor # todo: journaling request responses # todo: rate limiting self.app.before_request( create_function_chain_executor(self.before_request_functions)) # todo: add after request processor # self.app.after_request(create_function_chain_executor(self.after_request_functions)) def __init_crypto(self): # https://stackoverflow.com/questions/29650495/how-to-verify-a-jwt-using-python-pyjwt-with-public-key with self.app.app_context(): with open('{}/keys/appkernel.pem'.format(self.cfg_dir), "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) config.private_key = private_key with open('{}/keys/appkernel.pub'.format(self.cfg_dir), 'rb') as key_file: public_key = serialization.load_pem_public_key( key_file.read(), backend=default_backend()) config.public_key = public_key def __init_babel(self): self.babel = Babel(self.app) # translations = Translations.load('translations') # translations.merge(Translations.load()) # todo: support for multiple plugins supported_languages = [] for supported_lang in self.cfg_engine.get('appkernel.i18n.languages', ['en-US']): supported_languages.append(supported_lang) if '-' in supported_lang: supported_languages.append(supported_lang.split('-')[0]) def get_current_locale(): with self.app.app_context(): best_match = request.accept_languages.best_match( supported_languages, default='en') return best_match.replace('-', '_') self.babel.localeselector(get_current_locale) # catalogs = gettext.find('locale', 'locale', all=True) # self.logger.info('Using message catalogs: {}'.format(catalogs)) @property def logger(self): return self.app.logger def run(self): self.app.logger.info('===== Starting {} ====='.format(self.app_id)) self.app.logger.debug(f'--> registered routes:\n {self.app.url_map}') if self.development: self.app.logger.info(f'--> initialising in development mode...') self.app.run(debug=self.development, threaded=True) # todo: make the threading and deployment configurable # self.app.run(debug=self.development, processes=8) else: try: from gevent.pywsgi import WSGIServer, LoggingLogAdapter port = self.cfg_engine.get('appkernel.server.port', 5000) binding_address = self.cfg_engine.get( 'appkernel.server.address', '') backlog = self.cfg_engine.get('appkernel.server.backlog', 256) logging_adapter = LoggingLogAdapter(self.app.logger) self.http_server = WSGIServer((binding_address, port), application=self.app, backlog=backlog, log=logging_adapter, error_log=logging_adapter) shutdown_timeout = self.cfg_engine.get( 'appkernel.server.shutdown_timeout', 10) self.app.logger.info( f'--> starting production mode |host: {binding_address}|port: {port}|backlog: {backlog}' ) print(f'=== starting server ===') self.http_server.serve_forever(stop_timeout=shutdown_timeout) except ImportError: self.app.logger.warning( '--> falling back to the builtin development server (since gevent is missing / issue: pip install gevent' ) self.app.run(debug=self.development, threaded=True) def shutdown_hook(self): if config and hasattr(config, 'mongo_database') and config.mongo_database: self.mongo_client.close() if hasattr(self, 'app') and self.app and hasattr( self.app, 'logger') and self.app.logger: self.app.logger.info('======= Shutting Down {} ======='.format( self.app_id)) # no need for the following code snippet while the http_server.serve_forever() is used # if hasattr(self, 'http_server'): # self.http_server.stop(10) @staticmethod def get_cmdline_options(): # working dir is also available on: self.app.root_path argv = sys.argv[1:] opts, args = getopt.getopt( argv, 'c:dw:', ['config-dir=', 'development', 'working-dir=']) # -- config directory config_dir_provided, config_dir_param = AppKernelEngine.is_option_provided( ('-c', '--config-dir'), opts, args) cwd = os.path.dirname(os.path.realpath(sys.argv[0])) if config_dir_provided: cfg_dir = '{}/'.format(str(config_dir_param).rstrip('/')) cfg_dir = os.path.expanduser(cfg_dir) if not os.path.isdir(cfg_dir) or not os.access(cfg_dir, os.W_OK): raise AppInitialisationError( 'The config directory [{}] is not found/not writable.'. format(cfg_dir)) else: cfg_dir = None # -- working directory working_dir_provided, working_dir_param = AppKernelEngine.is_option_provided( ('-w', '--working-dir'), opts, args) if working_dir_provided: cwd = os.path.expanduser('{}/'.format( str(config_dir_param).rstrip('/'))) if not os.path.isdir(cwd) or not os.access(cwd, os.W_OK): raise AppInitialisationError( 'The working directory[{}] is not found/not writable.'. format(cwd)) else: cwd = '{}/../'.format(cwd.rstrip('/')) development, param = AppKernelEngine.is_option_provided( ('-d', '--development'), opts, args) return {'cfg_dir': cfg_dir, 'development': development, 'cwd': cwd} def __configure_flask_app(self): if hasattr(self.app, 'teardown_appcontext'): self.app.teardown_appcontext(self.teardown) else: self.app.teardown_request(self.teardown) if not hasattr(self.app, 'extensions'): self.app.extensions = {} self.app.extensions['appkernel'] = self def __init_web_layer(self): self.app.json_encoder = AppKernelJSONEncoder self.app.register_error_handler(Exception, self.generic_error_handler) for code in default_exceptions.keys(): # add a default error handler for everything is unhandled self.app.register_error_handler(code, self.generic_error_handler) def set_locale_on_request(): g.locale = str(get_locale()) self.app.before_request(set_locale_on_request) def init_logger(self, log_folder, level=logging.DEBUG): assert log_folder is not None, 'The log folder must be provided.' if self.development: formatter = logging.Formatter("%(levelname)s - %(message)s") handler = logging.StreamHandler() handler.setLevel(level) self._enable_werkzeug_logger(handler) else: # self.cfg_engine.get_value_for_section() # log_format = ' in %(module)s [%(pathname)s:%(lineno)d]:\n%(message)s' formatter = logging.Formatter( "%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s" ) max_bytes = self.cfg_engine.get('appkernel.logging.max_size', 10485760) backup_count = self.cfg_engine.get( 'appkernel.logging.backup_count', 3) file_name = self.cfg_engine.get( 'appkernel.logging.file_name' ) or f"{self.app_id.replace(' ', '_').lower()}.log" handler = RotatingFileHandler('{}/{}'.format( log_folder, file_name), maxBytes=max_bytes, backupCount=backup_count) # handler = TimedRotatingFileHandler('logs/foo.log', when='midnight', interval=1) handler.setLevel(level) handler.setFormatter(formatter) self.app.logger.setLevel(level) # self.app.logger.addHandler(handler) self.app.logger.handlers = [handler] self.app.logger.info('Logger initialised') @staticmethod def _enable_werkzeug_logger(handler): logger = logging.getLogger('werkzeug') logger.setLevel(logging.DEBUG) logger.addHandler(handler) def generic_error_handler(self, ex: Exception = None, upstream_service: str = None): """ Takes a generic exception and returns a json error message which will be returned to the client :param ex: the exception which is reported by this method :param upstream_service: the servicr name which generated this error :return: """ code = (ex.code if isinstance(ex, HTTPException) else 500) if ex and code != 404: msg = '{}/{}'.format( ex.__class__.__name__, ex.description if isinstance(ex, HTTPException) else str(ex)) self.logger.exception('generic error handler: {}/{}'.format( ex.__class__.__name__, str(ex))) elif ex and code == 404: msg = '{} ({} {}): {}'.format( ex.__class__.__name__, request.method, request.url, ex.description if isinstance(ex, HTTPException) else str(ex)) self.logger.exception('generic error handler: {}/{}'.format( ex.__class__.__name__, str(ex))) else: msg = 'Generic server error.' self.logger.warning('generic error handler: {}/{}'.format( ex.__class__.__name__, str(ex))) return create_custom_error(code, msg, upstream_service=upstream_service) def teardown(self, exception): """ context teardown based deallocation :param exception: :type exception: Exception :return: """ if exception is not None: self.app.logger.warning(exception.message if hasattr( exception, 'message') else str(exception)) def register(self, service_class_or_instance, url_base=None, methods=['GET'], enable_hateoas=True) -> ResourceController: """ :param service_class_or_instance: :param url_base: :param methods: :param enable_hateoas: :return: :rtype: Service """ if inspect.isclass(service_class_or_instance): assert issubclass( service_class_or_instance, (Model) ), 'Only subclasses of Model can be registered as class. If you want to register a controller, please use its instance.' from appkernel.service import expose_service expose_service(service_class_or_instance, self, url_base or self.root_url, methods=methods, enable_hateoas=enable_hateoas) return ResourceController(service_class_or_instance)
def init_app(app, **kwargs): """Initialize the Flask app located in the module sipa. This initializes the Flask app by: * calling the internal init_app() procedures of each module * registering the Blueprints * registering the Jinja global variables :return: None """ load_config_file(app, config=kwargs.pop('config', None)) app.wsgi_app = ProxyFix(app.wsgi_app, app.config['NUM_PROXIES']) init_logging(app) init_env_and_config(app) logger.debug('Initializing app') login_manager.init_app(app) babel = Babel() babel.init_app(app) babel.localeselector(select_locale) app.before_request(save_user_locale_setting) app.session_interface = SeparateLocaleCookieSessionInterface() cf_pages = CategorizedFlatPages() cf_pages.init_app(app) backends = Backends() backends.init_app(app) app.url_map.converters['int'] = IntegerConverter from sipa.blueprints import bp_features, bp_usersuite, \ bp_pages, bp_documents, bp_news, bp_generic, bp_hooks logger.debug('Registering blueprints') app.register_blueprint(bp_generic) app.register_blueprint(bp_features) app.register_blueprint(bp_usersuite) app.register_blueprint(bp_pages) app.register_blueprint(bp_documents) app.register_blueprint(bp_news) app.register_blueprint(bp_hooks) from sipa.model import query_gauge_data logger.debug('Registering Jinja globals') form_label_width = 3 form_input_width = 7 app.jinja_env.globals.update( cf_pages=cf_pages, gauge_data=query_gauge_data, get_locale=get_locale, get_weekday=get_weekday, possible_locales=possible_locales, get_attribute_endpoint=get_attribute_endpoint, traffic_chart=provide_render_function(generate_traffic_chart), credit_chart=provide_render_function(generate_credit_chart), current_datasource=backends.current_datasource, form_label_width_class="col-sm-{}".format(form_label_width), form_input_width_class="col-sm-{}".format(form_input_width), form_input_offset_class="col-sm-offset-{}".format(form_label_width), url_self=url_self, ) logger.debug("Jinja globals have been set", extra={'data': {'jinja_globals': app.jinja_env.globals}}) backends.init_backends()
def set_locale(): """ Set the locale in the session to one of the available languages. If a language has been given via the URL, it is set if it is a valid language. If no language has been given via the URL and no language is present in the session, the default language will be determined according to what the user's browser prefers. """ if "lang" in request.args and request.args["lang"] in language_list: session["lang"] = request.args["lang"] if "lang" not in session: session["lang"] = request.accept_languages.best_match(language_list) g.languages, g.locales = language_list, locales babel.localeselector(get_locale) c3bottles.before_request(set_locale) # Trim and strip blocks in jinja2 so no unnecessary # newlines and tabs appear in the output: c3bottles.jinja_env.trim_blocks = True c3bottles.jinja_env.lstrip_blocks = True from view.api import api from view.main import index, faq, dp_list, dp_map, dp_view from view.create import create_dp from view.edit import edit_dp from view.report import report from view.visit import visit from view.user import login, logout from view.statistics import stats
def get_locale() -> str | None: """Get locale. Returns: The locale that should be used for Babel. If not given as an option to Fava, guess from browser. """ lang = g.ledger.fava_options.language if lang is not None: return lang return request.accept_languages.best_match(["en"] + LANGUAGES) BABEL = Babel(app) BABEL.localeselector(get_locale) for function in template_filters.FILTERS: app.add_template_filter(function) # type: ignore app.add_template_filter(serialise) @app.url_defaults def _inject_filters(endpoint: str, values: dict[str, str | None]) -> None: if "bfile" not in values and app.url_map.is_endpoint_expecting( endpoint, "bfile" ): values["bfile"] = g.beancount_file_slug if endpoint in ["static", "index"]: return
class AppKernelEngine(object): def __init__(self, app_id, app=None, root_url='/', log_level=logging.DEBUG, cfg_dir=None, development=None, enable_defaults=False): """ Initialiser of Flask Engine. :param app: the Flask App :type app: Flask :param root_url: the url where the service are exposed to. :type root_url: str :param log_level: the level of log :param cfg_dir: the directory containing the cfg.yml file. If not provided it will be taken from the command line or from current working dir; :param development: the system will be initialised in development mode if True. If None, it will try to read the value as command line parameter or default to false; :type log_level: logging """ assert app_id is not None, 'The app_id must be provided' assert re.match('[A-Za-z0-9-_]', app_id), 'The app_id must be a single word, no space or special characters except - or _ .' self.app = app or current_app assert self.app is not None, 'The Flask App must be provided as init parameter.' try: config.service_registry = {} self.before_request_functions = [] self.after_request_functions = [] self.app_id = app_id self.root_url = root_url self.__configure_flask_app() self.__init_web_layer() self.cmd_line_options = get_cmdline_options() self.cfg_dir = cfg_dir or self.cmd_line_options.get('cfg_dir') self.cfg_engine = CfgEngine(self.cfg_dir, optional=enable_defaults) config.cfg_engine = self.cfg_engine self.__init_babel() self.__init_cross_cutting_concerns() self.development = development or self.cmd_line_options.get('development') cwd = self.cmd_line_options.get('cwd') self.init_logger(log_folder=cwd, level=log_level) # -- initialisation # this can raise false positives if a bit of code running # longer than 1 seconds. # the timeout can be increased by adding the parameter: # resolution=3, where the value 3 represents 3 seconds. eventlet.debug.hub_blocking_detection(True, resolution=3) atexit.register(self.shutdown_hook) if hasattr(app, 'teardown_appcontext'): app.teardown_appcontext(self.teardown) else: app.teardown_request(self.teardown) # -- database host db_host = self.cfg_engine.get('appkernel.mongo.host') or 'localhost' db_name = self.cfg_engine.get('appkernel.mongo.db') or 'app' self.mongo_client = MongoClient(host=db_host) config.mongo_database = self.mongo_client[db_name] except (AppInitialisationError, AssertionError) as init_err: # print >> sys.stderr, self.app.logger.error(init_err.message) sys.exit(-1) def enable_security(self, authorisation_method=None): self.enable_pki() if not authorisation_method: authorisation_method = authorize_request self.add_before_request_function(authorisation_method) config.security_enabled = True return self def enable_pki(self): if not hasattr(self.app, 'public_key'): self.__init_crypto() def add_before_request_function(self, func): self.before_request_functions.append(func) def add_after_request_function(self, func): self.after_request_functions.append(func) def __init_cross_cutting_concerns(self): def create_function_chain_executor(chain): def function_chain_executor(): for func in chain: return func() return function_chain_executor # todo: journaling request responses # todo: rate limiting self.app.before_request(create_function_chain_executor(self.before_request_functions)) # todo: add after request processor # self.app.after_request(create_function_chain_executor(self.after_request_functions)) def __init_crypto(self): # https://stackoverflow.com/questions/29650495/how-to-verify-a-jwt-using-python-pyjwt-with-public-key with self.app.app_context(): with open('{}/keys/appkernel.pem'.format(self.cfg_dir), "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend() ) config.private_key = private_key with open('{}/keys/appkernel.pub'.format(self.cfg_dir), 'rb') as key_file: public_key = serialization.load_pem_public_key( key_file.read(), backend=default_backend() ) config.public_key = public_key def __init_babel(self): self.babel = Babel(self.app) # translations = Translations.load('translations') # translations.merge(Translations.load()) # todo: support for multiple plugins supported_languages = [] for supported_lang in self.cfg_engine.get('appkernel.i18n.languages') or ['en-US']: supported_languages.append(supported_lang) if '-' in supported_lang: supported_languages.append(supported_lang.split('-')[0]) def get_current_locale(): with self.app.app_context(): best_match = request.accept_languages.best_match(supported_languages, default='en') return best_match.replace('-', '_') self.babel.localeselector(get_current_locale) # catalogs = gettext.find('locale', 'locale', all=True) # self.logger.info('Using message catalogs: {}'.format(catalogs)) @property def logger(self): return self.app.logger def run(self): self.app.logger.info('===== Starting {} ====='.format(self.app_id)) self.app.run(debug=self.development) def shutdown_hook(self): if config.mongo_database: self.mongo_client.close() if self.app and self.app.logger: self.app.logger.info('======= Shutting Down {} ======='.format(self.app_id)) def get_cmdline_options(self): # working dir is also available on: self.app.root_path argv = sys.argv[1:] opts, args = getopt.getopt(argv, 'c:dw:', ['config-dir=', 'development', 'working-dir=']) # -- config directory config_dir_provided, config_dir_param = AppKernelEngine.is_option_provided(('-c', '--config-dir'), opts, args) cwd = os.path.dirname(os.path.realpath(sys.argv[0])) if config_dir_provided: cfg_dir = '{}/'.format(str(config_dir_param).rstrip('/')) cfg_dir = os.path.expanduser(cfg_dir) if not os.path.isdir(cfg_dir) or not os.access(cfg_dir, os.W_OK): raise AppInitialisationError('The config directory [{}] is not found/not writable.'.format(cfg_dir)) else: cfg_dir = '{}/../'.format(cwd.rstrip('/')) # -- working directory working_dir_provided, working_dir_param = AppKernelEngine.is_option_provided(('-w', '--working-dir'), opts, args) if working_dir_provided: cwd = os.path.expanduser('{}/'.format(str(config_dir_param).rstrip('/'))) if not os.path.isdir(cwd) or not os.access(cwd, os.W_OK): raise AppInitialisationError('The working directory[{}] is not found/not writable.'.format(cwd)) else: cwd = '{}/../'.format(cwd.rstrip('/')) development, param = AppKernelEngine.is_option_provided(('-d', '--development'), opts, args) return { 'cfg_dir': cfg_dir, 'development': development, 'cwd': cwd } @staticmethod def is_option_provided(option_dict, opts, args): for opt, arg in opts: if opt in option_dict: return True, arg return False, '' def __configure_flask_app(self): if hasattr(self.app, 'teardown_appcontext'): self.app.teardown_appcontext(self.teardown) else: self.app.teardown_request(self.teardown) if not hasattr(self.app, 'extensions'): self.app.extensions = {} self.app.extensions['appkernel'] = self def __init_web_layer(self): self.app.json_encoder = AppKernelJSONEncoder self.app.register_error_handler(Exception, self.generic_error_handler) for code in default_exceptions.keys(): # add a default error handler for everything is unhandled self.app.register_error_handler(code, self.generic_error_handler) def set_locale_on_request(): g.locale = str(get_locale()) self.app.before_request(set_locale_on_request) def init_logger(self, log_folder, level=logging.DEBUG): assert log_folder is not None, 'The log folder must be provided.' if self.development: formatter = logging.Formatter("%(levelname)s - %(message)s") handler = logging.StreamHandler() handler.setLevel(level) self._enable_werkzeug_logger(handler) else: # self.cfg_engine.get_value_for_section() # log_format = ' in %(module)s [%(pathname)s:%(lineno)d]:\n%(message)s' formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s") max_bytes = self.cfg_engine.get('appkernel.logging.max_size') or 10485760 backup_count = self.cfg_engine.get('appkernel.logging.backup_count') or 3 file_name = self.cfg_engine.get('appkernel.logging.file_name') or '{}.log'.format(self.app_id) handler = RotatingFileHandler('{}/{}.log'.format(log_folder, file_name), maxBytes=max_bytes, backupCount=backup_count) # handler = TimedRotatingFileHandler('logs/foo.log', when='midnight', interval=1) handler.setLevel(level) handler.setFormatter(formatter) self.app.logger.setLevel(level) self.app.logger.addHandler(handler) self.app.logger.info('Logger initialised') def _enable_werkzeug_logger(self, handler): logger = logging.getLogger('werkzeug') logger.setLevel(logging.DEBUG) logger.addHandler(handler) def create_custom_error(self, code, message): return make_response(jsonify({'_type': MessageType.ErrorMessage.name, 'code': code, 'message': message}), code) def generic_error_handler(self, ex=None): """ Takes a generic exception and returns a json error message which will be returned to the client :param ex: :return: """ code = (ex.code if isinstance(ex, HTTPException) else 500) if ex: msg = '{}/{}'.format(ex.__class__.__name__, ex.description if isinstance(ex, HTTPException) else str(ex)) self.logger.exception('generic error handler: {}/{}'.format(ex.__class__.__name__, str(ex))) else: msg = 'Generic server error.' self.logger.warn('generic error handler: {}/{}'.format(ex.__class__.__name__, str(ex))) return self.create_custom_error(code, msg) def teardown(self, exception): """ context teardown based deallocation :param exception: :type exception: Exception :return: """ if exception is not None: self.app.logger.warn(exception.message) def register(self, service_class, url_base=None, methods=['GET'], enable_hateoas=True): """ :param service_class: :param url_base: :param methods: :param enable_hateoas: :return: :rtype: Service """ assert issubclass(service_class, appkernel.Service), 'Only subclasses of Service can be registered.' service_class.set_app_engine(self, url_base or self.root_url, methods=methods, enable_hateoas=enable_hateoas) return service_class
def i18n_init(app): """ initialize Flask-Babel """ babel = Babel(app) babel.localeselector(get_locale) babel.timezoneselector(get_timezone)
def make_flask_stack(conf, **app_conf): """ This has to pass the flask app through all the same middleware that Pylons used """ root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) debug = asbool(app_conf.get('debug', app_conf.get('DEBUG', False))) testing = asbool(app_conf.get('testing', app_conf.get('TESTING', False))) app = flask_app = CKANFlask(__name__) app.debug = debug app.testing = testing app.template_folder = os.path.join(root, 'templates') app.app_ctx_globals_class = CKAN_AppCtxGlobals app.url_rule_class = CKAN_Rule app.jinja_loader = ChoiceLoader([ app.jinja_loader, CkanextTemplateLoader() ]) # Update Flask config with the CKAN values. We use the common config # object as values might have been modified on `load_environment` if config: app.config.update(config) else: app.config.update(conf) app.config.update(app_conf) # Do all the Flask-specific stuff before adding other middlewares # Secret key needed for flask-debug-toolbar and sessions if not app.config.get('SECRET_KEY'): app.config['SECRET_KEY'] = config.get('beaker.session.secret') if not app.config.get('SECRET_KEY'): raise RuntimeError(u'You must provide a value for the secret key' ' with the SECRET_KEY config option') if debug: from flask_debugtoolbar import DebugToolbarExtension app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False DebugToolbarExtension(app) # Use Beaker as the Flask session interface class BeakerSessionInterface(SessionInterface): def open_session(self, app, request): if 'beaker.session' in request.environ: return request.environ['beaker.session'] def save_session(self, app, session, response): session.save() namespace = 'beaker.session.' session_opts = dict([(k.replace('beaker.', ''), v) for k, v in config.iteritems() if k.startswith(namespace)]) if (not session_opts.get('session.data_dir') and session_opts.get('session.type', 'file') == 'file'): cache_dir = app_conf.get('cache_dir') or app_conf.get('cache.dir') session_opts['session.data_dir'] = '{data_dir}/sessions'.format( data_dir=cache_dir) app.wsgi_app = SessionMiddleware(app.wsgi_app, session_opts) app.session_interface = BeakerSessionInterface() # Add Jinja2 extensions and filters extensions = [ 'jinja2.ext.do', 'jinja2.ext.with_', jinja_extensions.SnippetExtension, jinja_extensions.CkanExtend, jinja_extensions.CkanInternationalizationExtension, jinja_extensions.LinkForExtension, jinja_extensions.ResourceExtension, jinja_extensions.UrlForStaticExtension, jinja_extensions.UrlForExtension ] for extension in extensions: app.jinja_env.add_extension(extension) app.jinja_env.filters['empty_and_escape'] = \ jinja_extensions.empty_and_escape # Common handlers for all requests app.before_request(ckan_before_request) app.after_request(ckan_after_request) # Template context processors app.context_processor(helper_functions) app.context_processor(c_object) @app.context_processor def ungettext_alias(): u''' Provide `ungettext` as an alias of `ngettext` for backwards compatibility ''' return dict(ungettext=ungettext) # Babel app.config[u'BABEL_TRANSLATION_DIRECTORIES'] = os.path.join(root, u'i18n') app.config[u'BABEL_DOMAIN'] = 'ckan' babel = Babel(app) babel.localeselector(get_locale) @app.route('/hello', methods=['GET']) def hello_world(): return 'Hello World, this is served by Flask' @app.route('/hello', methods=['POST']) def hello_world_post(): return 'Hello World, this was posted to Flask' # Auto-register all blueprints defined in the `views` folder _register_core_blueprints(app) # Set up each IBlueprint extension as a Flask Blueprint for plugin in PluginImplementations(IBlueprint): if hasattr(plugin, 'get_blueprint'): app.register_extension_blueprint(plugin.get_blueprint()) # Start other middleware for plugin in PluginImplementations(IMiddleware): app = plugin.make_middleware(app, config) # Fanstatic if debug: fanstatic_config = { 'versioning': True, 'recompute_hashes': True, 'minified': False, 'bottom': True, 'bundle': False, } else: fanstatic_config = { 'versioning': True, 'recompute_hashes': False, 'minified': True, 'bottom': True, 'bundle': True, } root_path = config.get('ckan.root_path', None) if root_path: root_path = re.sub('/{{LANG}}', '', root_path) fanstatic_config['base_url'] = root_path app = Fanstatic(app, **fanstatic_config) for plugin in PluginImplementations(IMiddleware): try: app = plugin.make_error_log_middleware(app, config) except AttributeError: log.critical('Middleware class {0} is missing the method' 'make_error_log_middleware.' .format(plugin.__class__.__name__)) # Initialize repoze.who who_parser = WhoConfig(conf['here']) who_parser.parse(open(app_conf['who.config_file'])) app = PluggableAuthenticationMiddleware( app, who_parser.identifiers, who_parser.authenticators, who_parser.challengers, who_parser.mdproviders, who_parser.request_classifier, who_parser.challenge_decider, logging.getLogger('repoze.who'), logging.WARN, # ignored who_parser.remote_user_key ) # Update the main CKAN config object with the Flask specific keys # that were set here or autogenerated flask_config_keys = set(flask_app.config.keys()) - set(config.keys()) for key in flask_config_keys: config[key] = flask_app.config[key] # Add a reference to the actual Flask app so it's easier to access app._wsgi_app = flask_app return app
def make_flask_stack(conf, **app_conf): """ This has to pass the flask app through all the same middleware that Pylons used """ root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) debug = asbool(app_conf.get('debug', app_conf.get('DEBUG', False))) testing = asbool(app_conf.get('testing', app_conf.get('TESTING', False))) app = flask_app = CKANFlask(__name__) app.debug = debug app.testing = testing app.template_folder = os.path.join(root, 'templates') app.app_ctx_globals_class = CKAN_AppCtxGlobals app.url_rule_class = CKAN_Rule # Update Flask config with the CKAN values. We use the common config # object as values might have been modified on `load_environment` if config: app.config.update(config) else: app.config.update(conf) app.config.update(app_conf) # Do all the Flask-specific stuff before adding other middlewares # Secret key needed for flask-debug-toolbar and sessions if not app.config.get('SECRET_KEY'): app.config['SECRET_KEY'] = config.get('beaker.session.secret') if not app.config.get('SECRET_KEY'): raise RuntimeError(u'You must provide a value for the secret key' ' with the SECRET_KEY config option') if debug: from flask_debugtoolbar import DebugToolbarExtension app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False DebugToolbarExtension(app) # Use Beaker as the Flask session interface class BeakerSessionInterface(SessionInterface): def open_session(self, app, request): if 'beaker.session' in request.environ: return request.environ['beaker.session'] def save_session(self, app, session, response): session.save() namespace = 'beaker.session.' session_opts = dict([(k.replace('beaker.', ''), v) for k, v in config.iteritems() if k.startswith(namespace)]) if (not session_opts.get('session.data_dir') and session_opts.get('session.type', 'file') == 'file'): cache_dir = app_conf.get('cache_dir') or app_conf.get('cache.dir') session_opts['session.data_dir'] = '{data_dir}/sessions'.format( data_dir=cache_dir) app.wsgi_app = SessionMiddleware(app.wsgi_app, session_opts) app.session_interface = BeakerSessionInterface() # Add Jinja2 extensions and filters extensions = [ 'jinja2.ext.do', 'jinja2.ext.with_', jinja_extensions.SnippetExtension, jinja_extensions.CkanExtend, jinja_extensions.CkanInternationalizationExtension, jinja_extensions.LinkForExtension, jinja_extensions.ResourceExtension, jinja_extensions.UrlForStaticExtension, jinja_extensions.UrlForExtension ] for extension in extensions: app.jinja_env.add_extension(extension) app.jinja_env.filters['empty_and_escape'] = \ jinja_extensions.empty_and_escape # Common handlers for all requests app.before_request(ckan_before_request) app.after_request(ckan_after_request) # Template context processors app.context_processor(helper_functions) app.context_processor(c_object) @app.context_processor def ungettext_alias(): u''' Provide `ungettext` as an alias of `ngettext` for backwards compatibility ''' return dict(ungettext=ungettext) # Babel app.config[u'BABEL_TRANSLATION_DIRECTORIES'] = os.path.join(root, u'i18n') app.config[u'BABEL_DOMAIN'] = 'ckan' babel = Babel(app) babel.localeselector(get_locale) @app.route('/hello', methods=['GET']) def hello_world(): return 'Hello World, this is served by Flask' @app.route('/hello', methods=['POST']) def hello_world_post(): return 'Hello World, this was posted to Flask' # Auto-register all blueprints defined in the `views` folder _register_core_blueprints(app) # Set up each IBlueprint extension as a Flask Blueprint for plugin in PluginImplementations(IBlueprint): if hasattr(plugin, 'get_blueprint'): app.register_extension_blueprint(plugin.get_blueprint()) # Start other middleware for plugin in PluginImplementations(IMiddleware): app = plugin.make_middleware(app, config) # Fanstatic if debug: fanstatic_config = { 'versioning': True, 'recompute_hashes': True, 'minified': False, 'bottom': True, 'bundle': False, } else: fanstatic_config = { 'versioning': True, 'recompute_hashes': False, 'minified': True, 'bottom': True, 'bundle': True, } root_path = config.get('ckan.root_path', None) if root_path: root_path = re.sub('/{{LANG}}', '', root_path) fanstatic_config['base_url'] = root_path app = Fanstatic(app, **fanstatic_config) for plugin in PluginImplementations(IMiddleware): try: app = plugin.make_error_log_middleware(app, config) except AttributeError: log.critical('Middleware class {0} is missing the method' 'make_error_log_middleware.' .format(plugin.__class__.__name__)) # Initialize repoze.who who_parser = WhoConfig(conf['here']) who_parser.parse(open(app_conf['who.config_file'])) app = PluggableAuthenticationMiddleware( app, who_parser.identifiers, who_parser.authenticators, who_parser.challengers, who_parser.mdproviders, who_parser.request_classifier, who_parser.challenge_decider, logging.getLogger('repoze.who'), logging.WARN, # ignored who_parser.remote_user_key ) # Update the main CKAN config object with the Flask specific keys # that were set here or autogenerated flask_config_keys = set(flask_app.config.keys()) - set(config.keys()) for key in flask_config_keys: config[key] = flask_app.config[key] # Add a reference to the actual Flask app so it's easier to access app._wsgi_app = flask_app return app
def init(babel: Babel) -> None: LANGUAGES.extend([str(locale) for locale in babel.list_translations()]) babel.localeselector(get_locale)