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 e0bb1d2  add methods to retrieve committee data from whimsy
e0bb1d2 is described below

commit e0bb1d2104a9f556735a758273b6ec66f051e973
Author: Thomas Neidhart <t...@apache.org>
AuthorDate: Thu Feb 20 16:38:36 2025 +0100

    add methods to retrieve committee data from whimsy
---
 atr/apache.py                   | 80 ++++++++++++++++++++++++++++++++++++----
 atr/blueprints/secret/secret.py |  6 +--
 atr/util.py                     |  4 +-
 tests/test_apache.py            | 82 +++++++++++++++++++++++++++++++++++++++--
 4 files changed, 156 insertions(+), 16 deletions(-)

diff --git a/atr/apache.py b/atr/apache.py
index aad9e7c..96f1c72 100644
--- a/atr/apache.py
+++ b/atr/apache.py
@@ -17,24 +17,30 @@
 
 from __future__ import annotations
 
+from datetime import datetime
 from typing import Annotated
 
 import httpx
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 
 from atr.util import DictToList
 
-_WHIMSY_COMMITTEE_URL = "https://whimsy.apache.org/public/committee-info.json";
+_WHIMSY_COMMITTEE_INFO_URL = 
"https://whimsy.apache.org/public/committee-info.json";
+_WHIMSY_COMMITTEE_RETIRED_URL = 
"https://whimsy.apache.org/public/committee-retired.json";
 _WHIMSY_PROJECTS_URL = 
"https://whimsy.apache.org/public/public_ldap_projects.json";
 
 
-class ApacheProjects(BaseModel):
-    lastTimestamp: str
+class LDAPProjects(BaseModel):
+    last_timestamp: str = Field(alias="lastTimestamp")
     project_count: int
-    projects: Annotated[list[ApacheProject], DictToList(key="name")]
+    projects: Annotated[list[Project], DictToList(key="name")]
 
+    @property
+    def last_time(self) -> datetime:
+        return datetime.strptime(self.last_timestamp, "%Y%m%d%H%M%S%z")
 
-class ApacheProject(BaseModel):
+
+class Project(BaseModel):
     name: str
     createTimestamp: str
     modifyTimestamp: str
@@ -46,10 +52,68 @@ class ApacheProject(BaseModel):
     podling: str | None = None
 
 
-async def get_apache_project_data() -> ApacheProjects:
+class CommitteeInfo(BaseModel):
+    last_updated: str
+    committee_count: int
+    pmc_count: int
+    committees: Annotated[list[Committee], DictToList(key="name")]
+
+
+class CommitteeRetired(BaseModel):
+    last_updated: str
+    retired_count: int
+    retired: Annotated[list[RetiredCommittee], DictToList(key="name")]
+
+
+class Committee(BaseModel):
+    name: str
+    display_name: str
+    site: str
+    description: str
+    mail_list: str
+    established: str
+    report: list[str]
+    # chair: Annotated[list[User], DictToList(key="id")]
+    roster_count: int
+    roster: Annotated[list[User], DictToList(key="id")]
+    pmc: bool
+
+
+class User(BaseModel):
+    id: str
+    name: str
+    date: str
+
+
+class RetiredCommittee(BaseModel):
+    name: str
+    display_name: str
+    retired: str
+    description: str
+
+
+async def get_ldap_projects_data() -> LDAPProjects:
     async with httpx.AsyncClient() as client:
         response = await client.get(_WHIMSY_PROJECTS_URL)
         response.raise_for_status()
         data = response.json()
 
-    return ApacheProjects.model_validate(data)
+    return LDAPProjects.model_validate(data)
+
+
+async def get_committee_info_data() -> CommitteeInfo:
+    async with httpx.AsyncClient() as client:
+        response = await client.get(_WHIMSY_COMMITTEE_INFO_URL)
+        response.raise_for_status()
+        data = response.json()
+
+    return CommitteeInfo.model_validate(data)
+
+
+async def get_committee_retired_data() -> CommitteeRetired:
+    async with httpx.AsyncClient() as client:
+        response = await client.get(_WHIMSY_COMMITTEE_RETIRED_URL)
+        response.raise_for_status()
+        data = response.json()
+
+    return CommitteeRetired.model_validate(data)
diff --git a/atr/blueprints/secret/secret.py b/atr/blueprints/secret/secret.py
index ae7b791..1d8a2e7 100644
--- a/atr/blueprints/secret/secret.py
+++ b/atr/blueprints/secret/secret.py
@@ -26,7 +26,7 @@ from werkzeug.wrappers.response import Response
 
 from asfquart.base import ASFQuartException
 from asfquart.session import read as session_read
-from atr.apache import ApacheProjects, get_apache_project_data
+from atr.apache import LDAPProjects, get_ldap_projects_data
 from atr.db import get_session
 from atr.db.models import (
     PMC,
@@ -185,11 +185,11 @@ async def secret_pmcs_update() -> str | Response:
     return await render_template("secret/update-pmcs.html")
 
 
-async def secret_pmcs_update_data() -> tuple[ApacheProjects, dict, dict]:
+async def secret_pmcs_update_data() -> tuple[LDAPProjects, dict, dict]:
     """Fetch and update PMCs and podlings from remote data."""
     # Fetch committee-info.json from whimsy.apache.org
     try:
-        apache_projects = await get_apache_project_data()
+        apache_projects = await get_ldap_projects_data()
     except (httpx.RequestError, json.JSONDecodeError) as e:
         raise FlashError(f"Failed to fetch committee data: {e!s}")
 
diff --git a/atr/util.py b/atr/util.py
index 1e44012..9346c62 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -71,8 +71,8 @@ def _get_dict_to_list_inner_type_adapter(source_type: Any, 
key: str) -> TypeAdap
     assert (other_fields := {k: v for k, v in fields.items() if k != key})  # 
noqa: RUF018
 
     model_name = f"{cls.__name__}Inner"
-    inner_model = create_model(model_name, **{k: (Any, v) for k, v in 
other_fields.items()})  # type: ignore
-    return TypeAdapter(dict[Annotated[Any, key_field], inner_model])  # type: 
ignore
+    inner_model = create_model(model_name, **{k: (v.annotation, v) for k, v in 
other_fields.items()})  # type: ignore
+    return TypeAdapter(dict[Annotated[str, key_field], inner_model])  # type: 
ignore
 
 
 def _get_dict_to_list_validator(inner_adapter: TypeAdapter[dict[Any, Any]], 
key: str) -> Any:
diff --git a/tests/test_apache.py b/tests/test_apache.py
index 57bbbcc..8741d5a 100644
--- a/tests/test_apache.py
+++ b/tests/test_apache.py
@@ -17,10 +17,10 @@
 
 import json
 
-from atr.apache import ApacheProjects
+from atr.apache import CommitteeInfo, CommitteeRetired, LDAPProjects
 
 
-def test_model():
+def test_ldap_projects_model():
     json_data = """
 {
   "lastTimestamp": "20250219115218Z",
@@ -44,8 +44,84 @@ def test_model():
     }
   }
 }"""
-    projects = ApacheProjects.model_validate(json.loads(json_data))
+    projects = LDAPProjects.model_validate(json.loads(json_data))
 
     assert projects is not None
     assert projects.project_count == 1
     assert projects.projects[0].name == "tooling"
+
+
+def test_committee_info_model():
+    json_data = """
+{
+  "last_updated": "2025-02-19 21:57:21 UTC",
+  "committee_count": 1,
+  "pmc_count": 1,
+  "committees": {
+    "tooling": {
+      "display_name": "Tooling",
+      "site": "http://tooling.apache.org/";,
+      "description": "tools, tools, tools",
+      "mail_list": "tooling",
+      "established": "01/2025",
+      "report": [
+        "January",
+        "April",
+        "July",
+        "October"
+      ],
+      "chair": {
+        "wave": {
+          "name": "Dave Fisher"
+        }
+      },
+      "roster_count": 3,
+      "roster": {
+        "wave": {
+          "name": "Dave Fisher",
+          "date": "2025-01-01"
+        },
+        "sbp": {
+          "name": "Sean B. Palmer",
+          "date": "2025-02-01"
+        },
+        "tn": {
+          "name": "Thomas Neidhart",
+          "date": "2025-03-01"
+        }
+      },
+      "pmc": true
+    }
+  }
+}"""
+    committees = CommitteeInfo.model_validate(json.loads(json_data))
+
+    assert committees is not None
+    assert committees.pmc_count == 1
+
+    tooling = committees.committees[0]
+    assert tooling.name == "tooling"
+    assert len(tooling.roster) == 3
+    assert "tn" in map(lambda x: x.id, tooling.roster)
+
+
+def test_committee_retired_model():
+    json_data = """
+{
+  "last_updated": "2025-02-19 21:57:21 UTC",
+  "retired_count": 1,
+  "retired": {
+    "abdera": {
+      "display_name": "Abdera",
+      "description": "blablabla",
+      "retired": "2017-03"
+    }
+  }
+}"""
+    retired_committees = CommitteeRetired.model_validate(json.loads(json_data))
+
+    assert retired_committees is not None
+    assert retired_committees.retired_count == 1
+
+    pmc = retired_committees.retired[0]
+    assert pmc.name == "abdera"


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tooling.apache.org
For additional commands, e-mail: dev-h...@tooling.apache.org

Reply via email to