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]