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 41525a3  Get JSON from distribution platforms, and improve HTML 
generation
41525a3 is described below

commit 41525a3ed930d30298141fbc237ea3047d32dba0
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Aug 6 17:30:59 2025 +0100

    Get JSON from distribution platforms, and improve HTML generation
---
 atr/htm.py               | 100 ++++++++++++++++++++++++++++++++++++++++++++++
 atr/routes/distribute.py | 102 ++++++++++++++++++++++++++++-------------------
 atr/worker.py            |   1 -
 3 files changed, 160 insertions(+), 43 deletions(-)

diff --git a/atr/htm.py b/atr/htm.py
new file mode 100644
index 0000000..13df5f3
--- /dev/null
+++ b/atr/htm.py
@@ -0,0 +1,100 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+import htpy
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+
+
+class BlockElementGetable:
+    def __init__(self, block: Block, element: htpy.Element):
+        self.block = block
+        self.element = element
+
+    def __getitem__(self, *items: htpy.Element | str) -> htpy.Element:
+        element = self.element[*items]
+        for i in range(len(self.block.elements) - 1, -1, -1):
+            if self.block.elements[i] is self.element:
+                self.block.elements[i] = element
+                return element
+        self.block.append(element)
+        return element
+
+
+class BlockElementCallable:
+    def __init__(self, block: Block, constructor: Callable[..., htpy.Element]):
+        self.block = block
+        self.constructor = constructor
+
+    def __call__(self, *args, **kwargs) -> BlockElementGetable:
+        element = self.constructor(*args, **kwargs)
+        self.block.append(element)
+        return BlockElementGetable(self.block, element)
+
+    def __getitem__(self, *items: Any) -> htpy.Element:
+        element = self.constructor()[*items]
+        self.block.append(element)
+        return element
+
+
+class Block:
+    def __init__(self, element: htpy.Element | None = None, *elements: 
htpy.Element):
+        self.element = element
+        self.elements = list(elements)
+
+    def __str__(self) -> str:
+        return f"{self.element}{self.elements}"
+
+    def __repr__(self) -> str:
+        return f"{self.element!r}[*{self.elements!r}]"
+
+    def append(self, element: htpy.Element) -> None:
+        self.elements.append(element)
+
+    def collect(self) -> htpy.Element:
+        if self.element is None:
+            return htpy.div[*self.elements]
+        return self.element[*self.elements]
+
+    @property
+    def h1(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.h1)
+
+    @property
+    def h2(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.h2)
+
+    @property
+    def h3(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.h3)
+
+    @property
+    def p(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.p)
+
+    @property
+    def pre(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.pre)
+
+    @property
+    def table(self) -> BlockElementCallable:
+        return BlockElementCallable(self, htpy.table)
diff --git a/atr/routes/distribute.py b/atr/routes/distribute.py
index 6455a03..7c056d7 100644
--- a/atr/routes/distribute.py
+++ b/atr/routes/distribute.py
@@ -21,11 +21,15 @@ import dataclasses
 import enum
 import json
 
+import aiohttp
 import htpy
+import pydantic
 import quart
 
 import atr.db as db
 import atr.forms as forms
+import atr.htm as htm
+import atr.models.schema as schema
 import atr.models.sql as sql
 import atr.routes as routes
 import atr.template as template
@@ -143,60 +147,74 @@ async def _distribute_page(*, project: str, version: str, 
form: DistributeForm)
         # if release.project.status != sql.ProjectStatus.ACTIVE:
         #     raise RuntimeError(f"Project {project} is not active")
     form_content = forms.render_columns(form, action=quart.request.path, 
descriptions=True)
-    introduction = [
-        htpy.p[
-            "Record a manual distribution during the ",
-            htpy.span(".atr-phase-three.atr-phase-label")["FINISH"],
-            " phase using the form below.",
-        ],
-        htpy.p["Please note that this form is a work in progress and not fully 
functional."],
+    block = htm.Block()
+    block.p[
+        "Record a manual distribution during the ",
+        htpy.span(".atr-phase-three.atr-phase-label")["FINISH"],
+        " phase using the form below.",
     ]
-    content = _page("Record a manual distribution", *introduction, 
form_content)
+    block.p["Please note that this form is a work in progress and not fully 
functional."]
+    content = _page("Record a manual distribution", *block.elements, 
form_content)
     return await template.blank("Distribute", content=content)
 
 
+# Lax to ignore csrf_token and submit
+class Data(schema.Lax):
+    platform: Platform
+    owner_namespace: str | None = None
+    package: str
+    version: str
+
+    @pydantic.field_validator("owner_namespace", mode="before")
+    @classmethod
+    def empty_to_none(cls, v):
+        return None if v is None or (isinstance(v, str) and v.strip() == "") 
else v
+
+
 async def _distribute_post_validated(form: DistributeForm) -> str:
-    data = {
-        "platform": form.platform.data,
-        "owner_namespace": form.owner_namespace.data,
-        "package": form.package.data,
-        "version": form.version.data,
-    }
-    table = _distribute_post_table(data)
-    pre_json_results = htpy.pre[
-        json.dumps({k: str(v.name if isinstance(v, enum.Enum) else v) for k, v 
in data.items()}, indent=2)
-    ]
-    content = _page(
-        "Submitted values",
-        htpy.div[
-            table,
-            htpy.h2["As JSON"],
-            pre_json_results,
-        ],
-        htpy.pre[
-            form.platform.data.value.template_url.format(
-                owner_namespace=data["owner_namespace"],
-                package=data["package"],
-                version=data["version"],
-            ),
-        ],
+    block = htm.Block()
+
+    # Submitted values
+    block.h2["Submitted values"]
+    data = Data.model_validate(form.data)
+    _distribute_post_table(block, data)
+
+    # As JSON
+    block.h2["As JSON"]
+    block.pre[data.model_dump_json(indent=2)]
+
+    # API URL
+    block.h2["API URL"]
+    api_url = form.platform.data.value.template_url.format(
+        owner_namespace=data.owner_namespace,
+        package=data.package,
+        version=data.version,
     )
-    return await template.blank("Distribution Submitted", content=content)
+    block.pre[api_url]
+
+    # API response
+    block.h2["API response"]
+    async with aiohttp.ClientSession() as session:
+        async with session.get(api_url) as response:
+            response.raise_for_status()
+            json_results = await response.json()
+    block.pre[json.dumps(json_results, indent=2)]
+
+    content = _page("Distribution submitted", block.collect())
+    return await template.blank("Distribution submitted", content=content)
 
 
-def _distribute_post_table(data: dict[str, str | Platform]) -> htpy.Element:
-    def row(label: str, value: str | Platform) -> htpy.Element:
-        if isinstance(value, Platform):
-            return htpy.tr[htpy.th[label], htpy.td[value.name]]
+def _distribute_post_table(block: htm.Block, data: Data) -> None:
+    def row(label: str, value: str) -> htpy.Element:
         return htpy.tr[htpy.th[label], htpy.td[value]]
 
     tbody = htpy.tbody[
-        row("Platform", data["platform"]),
-        row("Owner or Namespace", data["owner_namespace"] or "(blank)"),
-        row("Package", data["package"]),
-        row("Version", data["version"]),
+        row("Platform", data.platform.name),
+        row("Owner or Namespace", data.owner_namespace or "(blank)"),
+        row("Package", data.package),
+        row("Version", data.version),
     ]
-    return htpy.table(".table.table-striped.table-bordered")[tbody]
+    block.table(".table.table-striped.table-bordered")[tbody]
 
 
 def _page(title_str: str, *content: htpy.Element) -> htpy.Element:
diff --git a/atr/worker.py b/atr/worker.py
index d6f9c7e..f8ee447 100644
--- a/atr/worker.py
+++ b/atr/worker.py
@@ -58,7 +58,6 @@ def main() -> None:
         os.chdir(conf.STATE_DIR)
 
     _setup_logging()
-
     log.info(f"Starting worker process with pid {os.getpid()}")
 
     tasks: list[asyncio.Task] = []


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

Reply via email to