This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch sbp in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit e6463a0bac805d3a8fc28c5335268a5232275786 Author: Sean B. Palmer <[email protected]> AuthorDate: Fri Mar 6 15:01:19 2026 +0000 Render templates synchronously --- atr/get/checks.py | 2 +- atr/get/download.py | 5 ++++- atr/server.py | 18 +++++++++++++----- atr/template.py | 22 +++++++++++++++++++--- atr/templates/download-all.html | 1 - atr/templates/includes/topnav.html | 14 ++++++-------- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/atr/get/checks.py b/atr/get/checks.py index df41705c..e942d0e7 100644 --- a/atr/get/checks.py +++ b/atr/get/checks.py @@ -196,7 +196,7 @@ async def selected_revision( ) ) - files_table_html = await quart.render_template( + files_table_html = await template.render( "check-selected-path-table.html", paths=all_paths, info=info, diff --git a/atr/get/download.py b/atr/get/download.py index 830f9633..d0d9fb15 100644 --- a/atr/get/download.py +++ b/atr/get/download.py @@ -62,6 +62,7 @@ async def all_selected( user_ssh_keys = await data.ssh_key(asf_uid=session.uid).all() back_url = mapping.release_as_url(release) + file_count, total_bytes, formatted_size = await util.get_release_stats(release) return await template.render( "download-all.html", @@ -73,7 +74,9 @@ async def all_selected( server_host=session.app_host, user_ssh_keys=user_ssh_keys, back_url=back_url, - get_release_stats=util.get_release_stats, + file_count=file_count, + total_bytes=total_bytes, + formatted_size=formatted_size, ) diff --git a/atr/server.py b/atr/server.py index 594c80f6..51318cb7 100644 --- a/atr/server.py +++ b/atr/server.py @@ -142,6 +142,7 @@ def _app_create_base(app_config: type[config.AppConfig]) -> base.QuartApp: if asfquart.construct is ...: raise ValueError("asfquart.construct is not set") app = asfquart.construct(__name__, token_file="secrets/generated/apptoken.txt") + app.jinja_environment = template.SyncEnvironment # ASFQuart sets secret_key from apptoken.txt, or generates a new one # We must preserve this because from_object will overwrite it # Our AppConfig.SECRET_KEY is None since we no longer support that setting @@ -221,7 +222,7 @@ def _app_setup_api_docs(app: base.QuartApp) -> None: @app.route("/api/docs") @quart_schema.hide async def swagger_ui() -> str: - return await quart.render_template_string( + return await template.render_string( _SWAGGER_UI_TEMPLATE, title="ATR API", swagger_js_url=app.config["QUART_SCHEMA_SWAGGER_JS_URL"], @@ -242,11 +243,19 @@ def _app_setup_context(app: base.QuartApp) -> None: import atr.metadata as metadata import atr.post as post + current_user = await asfquart.session.read() + topnav_unfinished_releases: list[tuple[str, str, list[sql.Release]]] = [] + topnav_user_projects: list[tuple[str, str]] = [] + current_uid = current_user.uid if current_user else None + if isinstance(current_uid, str): + topnav_unfinished_releases = await interaction.unfinished_releases(current_uid) + topnav_user_projects = await interaction.user_projects(current_uid) + return { "admin": admin, "as_url": util.as_url, "commit": metadata.commit, - "current_user": await asfquart.session.read(), + "current_user": current_user, "get": get, "is_admin_fn": user.is_admin, "is_viewing_as_admin_fn": util.is_user_viewing_as_admin, @@ -254,9 +263,8 @@ def _app_setup_context(app: base.QuartApp) -> None: "is_test_mode": config.get().ALLOW_TESTS, "post": post, "static_url": util.static_url, - "unfinished_releases_fn": interaction.unfinished_releases, - # "user_committees_fn": interaction.user_committees, - "user_projects_fn": interaction.user_projects, + "topnav_unfinished_releases": topnav_unfinished_releases, + "topnav_user_projects": topnav_user_projects, "release_as_url": mapping.release_as_url, "version": metadata.version, } diff --git a/atr/template.py b/atr/template.py index d71d5707..5fcf88a6 100644 --- a/atr/template.py +++ b/atr/template.py @@ -22,11 +22,20 @@ import jinja2 import quart import quart.app as app import quart.signals as signals +import quart.templating as templating import atr.htm as htm import atr.util as util -render_async = quart.render_template + +class SyncEnvironment(templating.Environment): + """Quart Jinja environment with async template execution disabled.""" + + def __init__(self, app_instance: app.Quart, **options: Any) -> None: + if "loader" not in options: + options["loader"] = app_instance.create_global_jinja_loader() + options["enable_async"] = False + jinja2.Environment.__init__(self, **options) async def blank( @@ -47,6 +56,13 @@ async def blank( ) +async def render_string_sync(source: str, **context_vars: Any) -> str: + app_instance = quart.current_app + await app_instance.update_template_context(context_vars) + template = app_instance.jinja_env.from_string(source) + return await _render_in_thread(template, context_vars, app_instance) + + async def render_sync( template_name_or_list: str | jinja2.Template | list[str | jinja2.Template], **context_vars: Any, @@ -58,8 +74,6 @@ async def render_sync( async def _render_in_thread(template: jinja2.Template, context: dict, app: app.Quart) -> str: - if template.environment.is_async is False: - raise RuntimeError("Template environment is not async") await signals.before_render_template.send_async( app, _sync_wrapper=app.ensure_async, # pyright: ignore[reportArgumentType] @@ -76,4 +90,6 @@ async def _render_in_thread(template: jinja2.Template, context: dict, app: app.Q return rendered_template +render_async = render_sync render = render_sync +render_string = render_string_sync diff --git a/atr/templates/download-all.html b/atr/templates/download-all.html index 5a5e4b22..e8802806 100644 --- a/atr/templates/download-all.html +++ b/atr/templates/download-all.html @@ -42,7 +42,6 @@ <p class="border rounded p-3 mb-3"> <i class="bi bi-info-circle me-1"></i> - {% set file_count, total_bytes, formatted_size = get_release_stats(release) %} This release consists of {% if file_count == 1 %} <code>{{ file_count }}</code> file diff --git a/atr/templates/includes/topnav.html b/atr/templates/includes/topnav.html index 002b591e..4acd2191 100644 --- a/atr/templates/includes/topnav.html +++ b/atr/templates/includes/topnav.html @@ -33,9 +33,8 @@ <a class="dropdown-item" href="{{ as_url(get.root.index) }}"><i class="bi bi-play-circle"></i> Candidates</a> </li> - {% set unfinished_releases = unfinished_releases_fn(current_user.uid) %} - {% if unfinished_releases %} - {% for project_short_display_name, project_name, releases in unfinished_releases %} + {% if topnav_unfinished_releases %} + {% for project_short_display_name, project_name, releases in topnav_unfinished_releases %} <li> <hr class="dropdown-divider" /> </li> @@ -51,8 +50,7 @@ {% endfor %} {% endfor %} {% endif %} - {% set user_projects = user_projects_fn(current_user.uid) %} - {% if user_projects %} + {% if topnav_user_projects %} {% set max_projects = 8 %} <li> <hr class="dropdown-divider" /> @@ -60,16 +58,16 @@ <li> <a class="dropdown-item fw-bold" href="{{ as_url(get.root.index) }}">Start a release</a> </li> - {% for project_name, project_full_name in user_projects[:max_projects] %} + {% for project_name, project_full_name in topnav_user_projects[:max_projects] %} <li> <a class="dropdown-item" href="{{ as_url(get.start.selected, project_name=project_name) }}"><i class="bi bi-plus-circle"></i> {{ project_full_name.removeprefix("Apache ").removesuffix(" (Incubating)") }}</a> </li> {% endfor %} - {% if user_projects|length > max_projects %} + {% if topnav_user_projects|length > max_projects %} <li> - <a class="dropdown-item" href="{{ as_url(get.root.index) }}"><i class="bi bi-three-dots"></i> {{ user_projects|length - max_projects }} more</a> + <a class="dropdown-item" href="{{ as_url(get.root.index) }}"><i class="bi bi-three-dots"></i> {{ topnav_user_projects|length - max_projects }} more</a> </li> {% endif %} {% endif %} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
