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 86c0fbc Extract the package upload date from distribution platform
JSON
86c0fbc is described below
commit 86c0fbc6dc7c259751a2313baa25316ae1baabc8
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Aug 7 14:40:38 2025 +0100
Extract the package upload date from distribution platform JSON
---
atr/htm.py | 8 +++
atr/routes/distribute.py | 138 ++++++++++++++++++++++++++++++++++++-----------
2 files changed, 115 insertions(+), 31 deletions(-)
diff --git a/atr/htm.py b/atr/htm.py
index c33f41a..6fece29 100644
--- a/atr/htm.py
+++ b/atr/htm.py
@@ -83,6 +83,10 @@ class Block:
def code(self) -> BlockElementCallable:
return BlockElementCallable(self, htpy.code)
+ @property
+ def details(self) -> BlockElementCallable:
+ return BlockElementCallable(self, htpy.details)
+
@property
def div(self) -> BlockElementCallable:
return BlockElementCallable(self, htpy.div)
@@ -107,6 +111,10 @@ class Block:
def pre(self) -> BlockElementCallable:
return BlockElementCallable(self, htpy.pre)
+ @property
+ def summary(self) -> BlockElementCallable:
+ return BlockElementCallable(self, htpy.summary)
+
@property
def table(self) -> BlockElementCallable:
return BlockElementCallable(self, htpy.table)
diff --git a/atr/routes/distribute.py b/atr/routes/distribute.py
index 78d081e..c11344e 100644
--- a/atr/routes/distribute.py
+++ b/atr/routes/distribute.py
@@ -18,6 +18,7 @@
from __future__ import annotations
import dataclasses
+import datetime
import enum
import json
@@ -37,6 +38,42 @@ import atr.storage.outcome as outcome
import atr.template as template
+class ArtifactHubAvailableVersion(schema.Lax):
+ ts: int
+
+
+class ArtifactHubResponse(schema.Lax):
+ available_versions: list[ArtifactHubAvailableVersion] =
pydantic.Field(default_factory=list)
+
+
+class DockerResponse(schema.Lax):
+ tag_last_pushed: str | None = None
+
+
+class GitHubResponse(schema.Lax):
+ published_at: str | None = None
+
+
+class MavenDoc(schema.Lax):
+ timestamp: int | None = None
+
+
+class MavenResponse(schema.Lax):
+ response: dict[str, list[MavenDoc]] = pydantic.Field(default_factory=dict)
+
+
+class NpmResponse(schema.Lax):
+ pass
+
+
+class PyPIUrl(schema.Lax):
+ upload_time_iso_8601: str | None = None
+
+
+class PyPIResponse(schema.Lax):
+ urls: list[PyPIUrl] = pydantic.Field(default_factory=list)
+
+
@dataclasses.dataclass(frozen=True)
class PlatformValue:
name: str
@@ -46,39 +83,39 @@ class PlatformValue:
class Platform(enum.Enum):
- MAVEN = PlatformValue(
- name="Maven Central",
-
template_url="https://search.maven.org/solrsearch/select?q=g:{owner_namespace}+AND+a:{package}+AND+v:{version}&core=gav&rows=20&wt=json",
- requires_owner_namespace=True,
- )
- PYPI = PlatformValue(
- name="PyPI",
- template_url="https://pypi.org/pypi/{package}/{version}/json",
- )
- NPM_SCOPED = PlatformValue(
- name="npm (scoped)",
-
template_url="https://registry.npmjs.org/@{owner_namespace}/{package}/{version}",
+ ARTIFACTHUB = PlatformValue(
+ name="ArtifactHub (Helm)",
+
template_url="https://artifacthub.io/api/v1/packages/helm/{owner_namespace}/{package}/{version}",
requires_owner_namespace=True,
)
- NPM = PlatformValue(
- name="npm",
- template_url="https://registry.npmjs.org/{package}/{version}",
- )
DOCKER = PlatformValue(
name="Docker",
template_url="https://hub.docker.com/v2/namespaces/{owner_namespace}/repositories/{package}/tags/{version}",
default_owner_namespace="library",
)
- ARTIFACTHUB = PlatformValue(
- name="ArtifactHub (Helm)",
-
template_url="https://artifacthub.io/api/v1/packages/helm/{owner_namespace}/{package}/{version}",
- requires_owner_namespace=True,
- )
GITHUB = PlatformValue(
name="GitHub",
template_url="https://api.github.com/repos/{owner_namespace}/{package}/releases/tags/v{version}",
requires_owner_namespace=True,
)
+ MAVEN = PlatformValue(
+ name="Maven Central",
+
template_url="https://search.maven.org/solrsearch/select?q=g:{owner_namespace}+AND+a:{package}+AND+v:{version}&core=gav&rows=20&wt=json",
+ requires_owner_namespace=True,
+ )
+ NPM = PlatformValue(
+ name="npm",
+ template_url="https://registry.npmjs.org/{package}/{version}",
+ )
+ NPM_SCOPED = PlatformValue(
+ name="npm (scoped)",
+
template_url="https://registry.npmjs.org/@{owner_namespace}/{package}/{version}",
+ requires_owner_namespace=True,
+ )
+ PYPI = PlatformValue(
+ name="PyPI",
+ template_url="https://pypi.org/pypi/{package}/{version}/json",
+ )
class DistributeForm(forms.Typed):
@@ -214,7 +251,7 @@ async def _distribute_post_validated(form: DistributeForm,
project: str, version
# Distribution submitted
block.h1["Distribution submitted"]
match api_oc:
- case outcome.Result():
+ case outcome.Result(result):
block.p["The distribution was submitted successfully."]
case outcome.Error(error):
div = htm.Block(htpy.div(".alert.alert-danger"))
@@ -230,6 +267,15 @@ async def _distribute_post_validated(form: DistributeForm,
project: str, version
form=form,
extra_content=div.collect(),
)
+ # We leak result, usefully, from this scope
+
+ ### Upload date
+ block.h2["Upload date"]
+ upload_date = _platform_upload_date(form.platform.data, result)
+ if upload_date is not None:
+ block.pre[str(upload_date)]
+ else:
+ block.p["No upload date found."]
if dd.details:
## Details
@@ -241,19 +287,18 @@ async def _distribute_post_validated(form:
DistributeForm, project: str, version
### As JSON
block.h3["As JSON"]
- block.pre[dd.model_dump_json(indent=2)]
+ block.pre(".mb-3")[dd.model_dump_json(indent=2)]
### API URL
block.h3["API URL"]
- block.pre[api_url]
+ block.pre(".mb-3")[api_url]
- ## API response
- block.h2["API response"]
- match api_oc:
- case outcome.Result(result):
- block.pre[json.dumps(result, indent=2)]
- # case outcome.Error(exception):
- # block.pre[f"Error: {exception}"]
+ ### API response
+ block.h3["API response"]
+ block.details[
+ htpy.summary["Show full API response"],
+ htpy.pre(".atr-pre-wrap.mb-3")[json.dumps(result, indent=2)],
+ ]
return await template.blank("Distribution submitted",
content=block.collect())
@@ -269,3 +314,34 @@ def _distribute_post_table(block: htm.Block, dd:
DistributeData) -> None:
row("Version", dd.version),
]
block.table(".table.table-striped.table-bordered")[tbody]
+
+
+def _platform_upload_date(platform: Platform, data: basic.JSON) ->
datetime.datetime | None: # noqa: C901
+ match platform:
+ case Platform.ARTIFACTHUB:
+ if not (versions :=
ArtifactHubResponse.model_validate(data).available_versions):
+ return None
+ return datetime.datetime.fromtimestamp(versions[0].ts,
tz=datetime.UTC)
+ case Platform.DOCKER:
+ if not (pushed_at :=
DockerResponse.model_validate(data).tag_last_pushed):
+ return None
+ return datetime.datetime.fromisoformat(pushed_at.rstrip("Z"))
+ case Platform.GITHUB:
+ if not (published_at :=
GitHubResponse.model_validate(data).published_at):
+ return None
+ return datetime.datetime.fromisoformat(published_at.rstrip("Z"))
+ case Platform.MAVEN:
+ if not (docs :=
MavenResponse.model_validate(data).response.get("docs")):
+ return None
+ if not (timestamp := docs[0].timestamp):
+ return None
+ return datetime.datetime.fromtimestamp(timestamp / 1000,
tz=datetime.UTC)
+ case Platform.NPM | Platform.NPM_SCOPED:
+ return None
+ case Platform.PYPI:
+ if not (urls := PyPIResponse.model_validate(data).urls):
+ return None
+ if not (upload_time := urls[0].upload_time_iso_8601):
+ return None
+ return datetime.datetime.fromisoformat(upload_time.rstrip("Z"))
+ raise NotImplementedError(f"Platform {platform.name} is not yet supported")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]