This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 982c00c Add a simple namespaced key to value store to the database
982c00c is described below
commit 982c00cff988118bfdd3bf2bdc2efe417af39ca2
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Apr 7 19:42:02 2025 +0100
Add a simple namespaced key to value store to the database
---
atr/blueprints/admin/admin.py | 60 +++++++++++++++++++++++++++++++++++++++++++
atr/db/__init__.py | 17 ++++++++++++
atr/db/models.py | 7 +++++
3 files changed, 84 insertions(+)
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index 430399e..7545dfd 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -16,9 +16,11 @@
# under the License.
import collections
+import datetime
import logging
import pathlib
import statistics
+import uuid
from collections.abc import Callable, Mapping
from typing import Any
@@ -202,6 +204,7 @@ async def admin_data(model: str = "Committee") -> str:
"Release": data.release,
"SSHKey": data.ssh_key,
"Task": data.task,
+ "TextValue": data.text_value,
"VotePolicy": data.vote_policy,
}
@@ -389,6 +392,63 @@ async def _update_committees() -> tuple[int, int]: #
noqa: C901
return added_count, updated_count
[email protected]("/test-kv")
+async def admin_test_kv() -> str:
+ """Test route for writing and reading from the TextValue KV store."""
+ test_ns = "kv_test"
+ test_key = str(uuid.uuid4())
+ test_value = f"Test value set at {datetime.datetime.now(datetime.UTC)}"
+ message: str
+
+ try:
+ async with db.session() as data:
+ existing = await data.text_value(ns=test_ns, key=test_key).get()
+ if existing:
+ existing.value = test_value
+ data.add(existing)
+ else:
+ new_entry = models.TextValue(ns=test_ns, key=test_key,
value=test_value)
+ data.add(new_entry)
+ await data.commit()
+ _LOGGER.info(f"Text value test: Wrote {test_ns}/{test_key} =
{test_value}")
+
+ async with db.session() as data:
+ read_back = await data.text_value(ns=test_ns, key=test_key).get()
+ if read_back and (read_back.value == test_value):
+ message = f"<p class='success'>Test SUCCESS: Wrote/read ok
(ns='{test_ns}', key='{test_key}')</p>"
+ _LOGGER.info("Text value test SUCCESS")
+ elif read_back:
+ message = (
+ f"<p class='error'>Test FAILED: Read back wrong value!</p>"
+ f"<p>Expected: '{test_value}'</p>"
+ f"<p>Got: '{read_back.value}'</p>"
+ )
+ _LOGGER.error(
+ f"Text value test FAILED: Read back wrong value!
Expected='{test_value}', got='{read_back.value}'"
+ )
+ else:
+ message = f"<p class='error'>Test FAILED: Failed read
(ns='{test_ns}', key='{test_key}')</p>"
+ _LOGGER.error(f"Text value test FAILED: Failed to read back
key='{test_key}' in ns='{test_ns}'")
+
+ except Exception as e:
+ message = f"<p class='error'>Test FAILED: Exception occurred -
{e!s}</p>"
+ _LOGGER.exception("Text value test exception")
+
+ return f"""<!DOCTYPE html>
+<html>
+<head><title>Text value test result</title></head>
+<style>
+.error {{ color: red; }}
+.success {{ color: green; }}
+</style>
+<body>
+<h1>Text value test result</h1>
+{message}
+</body>
+</html>
+"""
+
+
@admin.BLUEPRINT.route("/releases")
async def admin_releases() -> str:
"""Display a list of all releases across all stages and phases."""
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index 4a02c77..fb968af 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -464,6 +464,23 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
return Query(self, query)
+ def text_value(
+ self,
+ ns: Opt[str] = NotSet,
+ key: Opt[str] = NotSet,
+ value: Opt[str] = NotSet,
+ ) -> Query[models.TextValue]:
+ query = sqlmodel.select(models.TextValue)
+
+ if is_defined(ns):
+ query = query.where(models.TextValue.ns == ns)
+ if is_defined(key):
+ query = query.where(models.TextValue.key == key)
+ if is_defined(value):
+ query = query.where(models.TextValue.value == value)
+
+ return Query(self, query)
+
def init_database(app: base.QuartApp) -> None:
"""
diff --git a/atr/db/models.py b/atr/db/models.py
index 0dc3da3..110369e 100644
--- a/atr/db/models.py
+++ b/atr/db/models.py
@@ -439,3 +439,10 @@ class CheckResult(sqlmodel.SQLModel, table=True):
status: CheckResultStatus
message: str
data: Any = sqlmodel.Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+
+
+class TextValue(sqlmodel.SQLModel, table=True):
+ # Composite primary key, automatically handled by SQLModel
+ ns: str = sqlmodel.Field(primary_key=True, index=True)
+ key: str = sqlmodel.Field(primary_key=True, index=True)
+ value: str = sqlmodel.Field()
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]