From 7fa29be50c4b31444ab4354e704c55f21832a5f5 Mon Sep 17 00:00:00 2001 From: Lazlo Westerhof Date: Fri, 25 Oct 2024 15:23:00 +0200 Subject: [PATCH] PoC with response caching --- app.py | 8 ++++++++ cache_config.py | 31 +++++++++++++++++++++++++++++++ general/general.py | 3 +++ group_manager/group_manager.py | 3 +++ requirements.txt | 1 + setup.cfg | 2 +- stats/stats.py | 3 +++ user/user.py | 11 ++++++++--- 8 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 cache_config.py diff --git a/app.py b/app.py index a2154adc..73de7ac2 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,7 @@ from admin.admin import admin_bp, set_theme_loader from api import api_bp +from cache_config import cache from datarequest.datarequest import datarequest_bp from deposit.deposit import deposit_bp from fileviewer.fileviewer import fileviewer_bp @@ -128,6 +129,9 @@ def load_admin_setting() -> Dict[str, Any]: # Start Flask-Session Session(app) +# Initialize the cache. +cache.init_app(app) + # Start monitoring thread for extracting tech support information # Monitor signal file can be set to empty to completely disable monitor thread monitor_enabled: bool = app.config.get("MONITOR_SIGNAL_FILE", "/var/www/yoda/show-tech.sig") != "" @@ -192,6 +196,10 @@ def static_loader() -> Optional[Response]: return None +def authenticated() -> bool: + return g.get('user') is not None and g.get('irods') is not None + + @app.before_request def protect_pages() -> Optional[Response]: """Restricted pages access protection.""" diff --git a/cache_config.py b/cache_config.py new file mode 100644 index 00000000..73a99601 --- /dev/null +++ b/cache_config.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +__copyright__ = 'Copyright (c) 2024, Utrecht University' +__license__ = 'GPLv3, see LICENSE' + +from flask import g, request, session +from flask_caching import Cache + +config = { + 'CACHE_TYPE': 'FileSystemCache', + 'CACHE_DIR': '/tmp', + 'CACHE_DEFAULT_TIMEOUT': 300 +} + + +def authenticated() -> bool: + return g.get('user') is not None and g.get('irods') is not None + + +def make_key(key=None) -> str: + if key is None: + key = f"{request.endpoint}_{request.method}" + + if authenticated(): + return f"{session.sid}-{key}" + else: + return f"unauthenticated-{key}" + + +# Create a Cache instance. +cache = Cache(config=config) diff --git a/general/general.py b/general/general.py index 20cad295..0a96b94c 100644 --- a/general/general.py +++ b/general/general.py @@ -6,6 +6,8 @@ from flask import Blueprint, redirect, render_template, Response, url_for from flask_wtf.csrf import CSRFError +from cache_config import cache, make_key + general_bp = Blueprint('general_bp', __name__, template_folder='templates/general', static_folder='static/general', @@ -13,6 +15,7 @@ @general_bp.route('/') +@cache.cached(make_cache_key=make_key) def index() -> Response: return render_template('index.html') diff --git a/group_manager/group_manager.py b/group_manager/group_manager.py index ec5e7132..43a95bba 100644 --- a/group_manager/group_manager.py +++ b/group_manager/group_manager.py @@ -6,6 +6,7 @@ from flask import Blueprint, make_response, render_template, request, Response import api +from cache_config import cache, make_key group_manager_bp = Blueprint('group_manager_bp', __name__, template_folder='templates', @@ -14,6 +15,7 @@ @group_manager_bp.route('/') +@cache.cached(make_cache_key=make_key) def index() -> Response: response = api.call('group_data', data={}) group_hierarchy = response['data']['group_hierarchy'] @@ -67,6 +69,7 @@ def get_subcategories() -> Response: @group_manager_bp.route('/get_schemas', methods=['POST']) +@cache.cached(make_cache_key=make_key) def get_schemas() -> Response: response = api.call('schema_get_schemas', data={}) diff --git a/requirements.txt b/requirements.txt index c0217f8a..0f3ea29b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ python-irodsclient==2.2.0 Flask==3.0.2 flask-session==0.6.0 Flask-WTF==1.2.1 +Flask-Caching==2.3.0 mod-wsgi==5.0.0 pyjwt[crypto]==2.8.0 requests==2.32.0 diff --git a/setup.cfg b/setup.cfg index 8f4785f7..39d7c090 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ import-order-style=smarkets strictness=short docstring_style=sphinx max-line-length=127 -application-import-names=admin,api,connman,errors,fileviewer,general,group_manager,monitor,research,search,open_search,stats,user,vault,deposit,intake,datarequest,util +application-import-names=admin,api,cache_config,connman,errors,fileviewer,general,group_manager,monitor,research,search,open_search,stats,user,vault,deposit,intake,datarequest,util exclude=venv [mypy] diff --git a/stats/stats.py b/stats/stats.py index 8a486df9..3cc1de5e 100644 --- a/stats/stats.py +++ b/stats/stats.py @@ -6,6 +6,7 @@ from flask import Blueprint, make_response, render_template, Response import api +from cache_config import cache, make_key stats_bp = Blueprint('stats_bp', __name__, template_folder='templates', @@ -14,6 +15,7 @@ @stats_bp.route('/') +@cache.cached(make_cache_key=make_key) def index() -> Response: # resource_tiers_response = api.call('resource_resource_and_tier_data', data={}) category_response = api.call('resource_category_stats', data={}) @@ -24,6 +26,7 @@ def index() -> Response: @stats_bp.route('/export') +@cache.cached(make_cache_key=make_key) def export() -> Response: response = api.call('resource_monthly_category_stats', data={}) diff --git a/user/user.py b/user/user.py index 5036a66a..7b2513c6 100644 --- a/user/user.py +++ b/user/user.py @@ -33,6 +33,7 @@ import api import connman +from cache_config import cache, make_key from util import is_email_in_domains, is_relative_url, log_error # Blueprint creation @@ -481,9 +482,13 @@ def prepare_user() -> None: try: endpoints = ["static", "call", "upload_get", "upload_post"] if request.endpoint is not None and not request.endpoint.endswith(tuple(endpoints)): - # Check for notifications. - response = api.call('notifications_load', data={}) - g.notifications = len(response['data']) + # Check for notifications (cached for 60 seconds). + response = cache.get(make_key('notifications_load')) + if response is None: + response = api.call('notifications_load', data={}) + cache.set(make_key('notifications_load'), response, timeout=60) + else: + g.notifications = len(response['data']) # Load saved settings. if session.get('settings', None) is None: