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]

Reply via email to