This is an automated email from the ASF dual-hosted git repository.

tn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-atr-experiments.git


The following commit(s) were added to refs/heads/main by this push:
     new f1c4fd3  support lazy loading of model properties within jinja 
templates
f1c4fd3 is described below

commit f1c4fd36f66defc1f868d13ec66274744ed2b03e
Author: Thomas Neidhart <[email protected]>
AuthorDate: Mon Mar 10 23:03:55 2025 +0100

    support lazy loading of model properties within jinja templates
---
 atr/blueprints/admin/admin.py |  5 ++---
 atr/db/models.py              | 19 +++++++++++++++----
 atr/db/service.py             | 25 +++++++++++--------------
 atr/routes/project.py         | 15 +++++++++------
 4 files changed, 37 insertions(+), 27 deletions(-)

diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index f7c0d92..6398f79 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -46,7 +46,7 @@ from atr.db.models import (
     Task,
     VotePolicy,
 )
-from atr.db.service import get_pmcs
+from atr.db.service import get_pmc_by_name, get_pmcs
 
 from . import blueprint
 
@@ -219,8 +219,7 @@ async def _update_pmcs() -> int:
                     continue
 
                 # Get or create PMC
-                statement = select(PMC).where(PMC.project_name == name)
-                pmc = (await 
db_session.execute(statement)).scalar_one_or_none()
+                pmc = await get_pmc_by_name(name, db_session)
                 if not pmc:
                     pmc = PMC(project_name=name)
                     db_session.add(pmc)
diff --git a/atr/db/models.py b/atr/db/models.py
index 6c9999e..0a4290a 100644
--- a/atr/db/models.py
+++ b/atr/db/models.py
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-"""models.py"""
+"""The data models to be persisted in the database."""
 
 import datetime
 from enum import Enum
@@ -23,9 +23,16 @@ from typing import Any, Optional
 
 from pydantic import BaseModel
 from sqlalchemy import JSON, CheckConstraint, Column, Index
+from sqlalchemy.ext.asyncio import AsyncAttrs
 from sqlmodel import Field, Relationship, SQLModel
 
 
+class ATRSQLModel(AsyncAttrs, SQLModel):
+    """The base model to use for ATR entities which allows to access related 
properties in an async manner."""
+
+    pass
+
+
 class UserRole(str, Enum):
     PMC_MEMBER = "pmc_member"
     RELEASE_MANAGER = "release_manager"
@@ -58,7 +65,7 @@ class PublicSigningKey(SQLModel, table=True):
     # The ASCII armored key
     ascii_armored_key: str
     # The PMCs that use this key
-    pmcs: list["PMC"] = Relationship(back_populates="public_signing_keys", 
link_model=PMCKeyLink)
+    pmcs: list["PMC"] = Relationship(back_populates="_public_signing_keys", 
link_model=PMCKeyLink)
 
 
 class VotePolicy(SQLModel, table=True):
@@ -77,7 +84,7 @@ class VotePolicy(SQLModel, table=True):
     releases: list["Release"] = Relationship(back_populates="vote_policy")
 
 
-class PMC(SQLModel, table=True):
+class PMC(ATRSQLModel, table=True):
     id: int | None = Field(default=None, primary_key=True)
     project_name: str = Field(unique=True)
     # True if this is an incubator podling with a PPMC, otherwise False
@@ -91,7 +98,11 @@ class PMC(SQLModel, table=True):
     release_managers: list[str] = Field(default_factory=list, 
sa_column=Column(JSON))
 
     # Many-to-many: A PMC can have multiple signing keys, and a signing key 
can belong to multiple PMCs
-    public_signing_keys: list[PublicSigningKey] = 
Relationship(back_populates="pmcs", link_model=PMCKeyLink)
+    _public_signing_keys: list[PublicSigningKey] = 
Relationship(back_populates="pmcs", link_model=PMCKeyLink)
+
+    @property
+    async def public_signing_keys(self) -> list[PublicSigningKey]:
+        return await self.awaitable_attrs._public_signing_keys  # type: ignore
 
     # Many-to-one: A PMC can have one vote policy, a vote policy can be used 
by multiple entities
     vote_policy_id: int | None = Field(default=None, 
foreign_key="votepolicy.id")
diff --git a/atr/db/service.py b/atr/db/service.py
index e1c4511..16a5116 100644
--- a/atr/db/service.py
+++ b/atr/db/service.py
@@ -16,33 +16,31 @@
 # under the License.
 
 from collections.abc import Sequence
+from contextlib import nullcontext
 from typing import cast
 
 from sqlalchemy import func
+from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.orm import selectinload
 from sqlalchemy.orm.attributes import InstrumentedAttribute
 from sqlmodel import select
 
-from atr.db.models import PMC, ProductLine, PublicSigningKey, Release, Task
+from atr.db.models import PMC, ProductLine, Release, Task
 
 from . import create_async_db_session
 
 
-async def get_pmc_by_name(project_name: str, include_keys: bool = False) -> 
PMC | None:
-    async with create_async_db_session() as db_session:
+async def get_pmc_by_name(project_name: str, session: AsyncSession | None = 
None) -> PMC | None:
+    """Returns a PMC object by name."""
+    async with create_async_db_session() if session is None else 
nullcontext(session) as db_session:
         statement = select(PMC).where(PMC.project_name == project_name)
-
-        if include_keys:
-            statement = statement.options(
-                selectinload(cast(InstrumentedAttribute[PublicSigningKey], 
PMC.public_signing_keys))
-            )
-
         pmc = (await db_session.execute(statement)).scalar_one_or_none()
         return pmc
 
 
-async def get_pmcs() -> Sequence[PMC]:
-    async with create_async_db_session() as db_session:
+async def get_pmcs(session: AsyncSession | None = None) -> Sequence[PMC]:
+    """Returns a list of PMC objects."""
+    async with create_async_db_session() if session is None else 
nullcontext(session) as db_session:
         # Get all PMCs and their latest releases
         statement = select(PMC).order_by(PMC.project_name)
         pmcs = (await db_session.execute(statement)).scalars().all()
@@ -79,10 +77,9 @@ def get_release_by_key_sync(storage_key: str) -> Release | 
None:
         return result.scalar_one_or_none()
 
 
-async def get_tasks(limit: int, offset: int) -> tuple[Sequence[Task], int]:
+async def get_tasks(limit: int, offset: int, session: AsyncSession | None = 
None) -> tuple[Sequence[Task], int]:
     """Returns a list of Tasks based on limit and offset values together with 
the total count."""
-
-    async with create_async_db_session() as db_session:
+    async with create_async_db_session() if session is None else 
nullcontext(session) as db_session:
         statement = 
select(Task).limit(limit).offset(offset).order_by(Task.id.desc())  # type: 
ignore
         tasks = (await db_session.execute(statement)).scalars().all()
         count = (await 
db_session.execute(select(func.count(Task.id)))).scalar_one()  # type: ignore
diff --git a/atr/routes/project.py b/atr/routes/project.py
index 27f51a1..cbd1991 100644
--- a/atr/routes/project.py
+++ b/atr/routes/project.py
@@ -21,6 +21,7 @@ from http.client import HTTPException
 
 from quart import render_template
 
+from atr.db import create_async_db_session
 from atr.db.service import get_pmc_by_name, get_pmcs
 from atr.routes import algorithms, app_route
 
@@ -28,14 +29,16 @@ from atr.routes import algorithms, app_route
 @app_route("/projects")
 async def root_project_directory() -> str:
     """Main project directory page."""
-    projects = await get_pmcs()
-    return await render_template("project-directory.html", projects=projects)
+    async with create_async_db_session() as session:
+        projects = await get_pmcs(session)
+        return await render_template("project-directory.html", 
projects=projects)
 
 
 @app_route("/projects/<project_name>")
 async def root_project_view(project_name: str) -> str:
-    project = await get_pmc_by_name(project_name, include_keys=True)
-    if not project:
-        raise HTTPException(404)
+    async with create_async_db_session() as session:
+        project = await get_pmc_by_name(project_name, session=session)
+        if not project:
+            raise HTTPException(404)
 
-    return await render_template("project-view.html", project=project, 
algorithms=algorithms)
+        return await render_template("project-view.html", project=project, 
algorithms=algorithms)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to