This is an automated email from the ASF dual-hosted git repository.

arm pushed a commit to branch safe_committee_type
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit d5f002546822b372e6debde62b47cb847df42448
Author: Alastair McFarlane <[email protected]>
AuthorDate: Thu Mar 12 11:16:53 2026 +0000

    #840: safe committee name type - without validation
---
 atr/api/__init__.py            |  6 +++---
 atr/blueprints/common.py       |  1 +
 atr/get/committees.py          |  8 ++++----
 atr/get/keys.py                |  3 ++-
 atr/get/projects.py            | 17 ++++++++---------
 atr/models/safe.py             |  5 +++++
 atr/post/projects.py           |  5 ++---
 atr/storage/writers/project.py |  9 ++++-----
 atr/web.py                     |  4 ++--
 9 files changed, 31 insertions(+), 27 deletions(-)

diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index cbc0065e..820f9b60 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -180,7 +180,7 @@ async def checks_ongoing(
 @quart_schema.validate_response(models.api.CommitteeGetResults, 200)
 async def committee_get(
     _committee_get: Literal["committee/get"],
-    name: unsafe.UnsafeStr,
+    name: safe.CommitteeKey,
 ) -> DictResponse:
     """
     URL: GET /committee/get/<name>
@@ -206,7 +206,7 @@ async def committee_get(
 @quart_schema.validate_response(models.api.CommitteeKeysResults, 200)
 async def committee_keys(
     _committee_keys: Literal["committee/keys"],
-    name: unsafe.UnsafeStr,
+    name: safe.CommitteeKey,
 ) -> DictResponse:
     """
     URL: GET /committee/keys/<name>
@@ -232,7 +232,7 @@ async def committee_keys(
 @quart_schema.validate_response(models.api.CommitteeProjectsResults, 200)
 async def committee_projects(
     _committee_projects: Literal["committee/projects"],
-    name: unsafe.UnsafeStr,
+    name: safe.CommitteeKey,
 ) -> DictResponse:
     """
     URL: GET /committee/projects/<name>
diff --git a/atr/blueprints/common.py b/atr/blueprints/common.py
index 0fd2073b..1382e90c 100644
--- a/atr/blueprints/common.py
+++ b/atr/blueprints/common.py
@@ -40,6 +40,7 @@ QUART_CONVERTERS: dict[Any, str] = {
 
 VALIDATED_TYPES: set[Any] = {
     safe.Alphanumeric,
+    safe.CommitteeKey,
     safe.ProjectName,
     safe.RevisionNumber,
     safe.VersionName,
diff --git a/atr/get/committees.py b/atr/get/committees.py
index 1ab70af9..03089197 100644
--- a/atr/get/committees.py
+++ b/atr/get/committees.py
@@ -23,8 +23,8 @@ import asfquart.base as base
 import atr.blueprints.get as get
 import atr.db as db
 import atr.form as form
+import atr.models.safe as safe
 import atr.models.sql as sql
-import atr.models.unsafe as unsafe
 import atr.post as post
 import atr.shared as shared
 import atr.template as template
@@ -49,7 +49,7 @@ async def directory(_session: web.Public, _committees: 
Literal["committees"]) ->
 
 
 @get.typed
-async def view(_session: web.Public, _committees: Literal["committees"], name: 
unsafe.UnsafeStr) -> str:
+async def view(_session: web.Public, _committees: Literal["committees"], name: 
safe.CommitteeKey) -> str:
     """
     URL: /committees/<name>
     """
@@ -75,8 +75,8 @@ async def view(_session: web.Public, _committees: 
Literal["committees"], name: u
             model_cls=shared.keys.UpdateCommitteeKeysForm,
             action=util.as_url(post.keys.keys),
             submit_label="Regenerate KEYS file",
-            defaults={"committee_name": str(name)},
+            defaults={"committee_name": committee.name},
             empty=True,
         ),
-        is_standing=util.committee_is_standing(str(name)),
+        is_standing=util.committee_is_standing(committee.name),
     )
diff --git a/atr/get/keys.py b/atr/get/keys.py
index d0fba17f..dab0082e 100644
--- a/atr/get/keys.py
+++ b/atr/get/keys.py
@@ -26,6 +26,7 @@ import atr.blueprints.get as get
 import atr.db as db
 import atr.form as form
 import atr.htm as htm
+import atr.models.safe as safe
 import atr.models.sql as sql
 import atr.models.unsafe as unsafe
 import atr.post as post
@@ -183,7 +184,7 @@ async def details(session: web.Committer, _keys_details: 
Literal["keys/details"]
 
 @get.typed
 async def export(
-    _session: web.Committer, _keys_export: Literal["keys/export"], 
committee_name: unsafe.UnsafeStr
+    _session: web.Committer, _keys_export: Literal["keys/export"], 
committee_name: safe.CommitteeKey
 ) -> web.TextResponse:
     """
     URL: /keys/export/<committee_name>
diff --git a/atr/get/projects.py b/atr/get/projects.py
index d57622cf..20cb2a77 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -35,7 +35,6 @@ import atr.get.start as start
 import atr.htm as htm
 import atr.models.safe as safe
 import atr.models.sql as sql
-import atr.models.unsafe as unsafe
 import atr.post as post
 import atr.registry as registry
 import atr.shared as shared
@@ -47,12 +46,12 @@ import atr.web as web
 
 @get.typed
 async def add_project(
-    session: web.Committer, _project_add: Literal["project/add"], 
committee_name: unsafe.UnsafeStr
+    session: web.Committer, _project_add: Literal["project/add"], 
committee_name: safe.CommitteeKey
 ) -> web.WerkzeugResponse | str:
     """
     URL: /project/add/<committee_name>
     """
-    await session.check_access_committee(str(committee_name))
+    await session.check_access_committee(committee_name)
 
     async with db.session() as data:
         committee = await data.committee(name=str(committee_name)).demand(
@@ -60,20 +59,20 @@ async def add_project(
         )
 
     page = htm.Block()
-    page.p[htm.a(".atr-back-link", href=util.as_url(committees.view, 
name=str(committee_name)))["← Back to committee"]]
+    page.p[htm.a(".atr-back-link", href=util.as_url(committees.view, 
name=committee.name))["← Back to committee"]]
     page.h1["Add project"]
     page.p[f"Add a new project to the {committee.display_name} committee."]
 
-    committee_display_name = committee.full_name or str(committee_name).title()
+    committee_display_name = committee.full_name or committee.name.title()
 
     form.render_block(
         page,
         model_cls=shared.projects.AddProjectForm,
-        action=util.as_url(post.projects.add_project, 
committee_name=str(committee_name)),
+        action=util.as_url(post.projects.add_project, 
committee_name=committee.name),
         submit_label="Add project",
-        cancel_url=util.as_url(committees.view, name=str(committee_name)),
+        cancel_url=util.as_url(committees.view, name=committee.name),
         defaults={
-            "committee_name": str(committee_name),
+            "committee_name": committee.name,
         },
     )
 
@@ -81,7 +80,7 @@ async def add_project(
     page.append(
         htpy.div(
             "#projects-add-config.d-none",
-            data_committee_name=str(committee_name),
+            data_committee_name=committee.name,
             data_committee_display_name=committee_display_name,
         )
     )
diff --git a/atr/models/safe.py b/atr/models/safe.py
index 799ac5dc..e72cfb21 100644
--- a/atr/models/safe.py
+++ b/atr/models/safe.py
@@ -90,6 +90,11 @@ class Alphanumeric(SafeType):
         return _ALPHANUM
 
 
+class CommitteeKey(Alphanumeric):
+    def _additional_validations(self, value: str):
+        pass
+
+
 class Numeric(SafeType):
     @classmethod
     def _valid_chars(cls) -> frozenset[str]:
diff --git a/atr/post/projects.py b/atr/post/projects.py
index 6763bf48..9896834a 100644
--- a/atr/post/projects.py
+++ b/atr/post/projects.py
@@ -37,7 +37,7 @@ import atr.web as web
 async def add_project(
     session: web.Committer,
     _project_add: Literal["project/add"],
-    committee_name: unsafe.UnsafeStr,
+    committee_name: safe.CommitteeKey,
     project_form: shared.projects.AddProjectForm,
 ) -> web.WerkzeugResponse:
     """
@@ -46,11 +46,10 @@ async def add_project(
     display_name = project_form.display_name
     label = project_form.label
 
-    # TODO: Is this right? Name is unvalidated
     async with storage.write(session) as write:
         wacm = await 
write.as_project_committee_member(safe.ProjectName(str(committee_name)))
         try:
-            await wacm.project.create(str(committee_name), display_name, label)
+            await wacm.project.create(committee_name, display_name, label)
         except storage.AccessError as e:
             return await session.redirect(
                 get.projects.add_project, committee_name=str(committee_name), 
error=f"Error adding project: {e}"
diff --git a/atr/storage/writers/project.py b/atr/storage/writers/project.py
index 0580a2bc..031be6d7 100644
--- a/atr/storage/writers/project.py
+++ b/atr/storage/writers/project.py
@@ -132,15 +132,14 @@ class CommitteeMember(CommitteeParticipant):
             return True
         return False
 
-    async def create(self, committee_name: str, display_name: str, label: str) 
-> None:
+    async def create(self, committee_name: safe.CommitteeKey, display_name: 
str, label: str) -> None:
         super_project = None
-        # Wrap the name in safe.ProjectName so we can create it in the database
         # TODO: Do we need to do any additional validation on the string value?
         # Get the base project to derive from
         # We're allowing derivation from a retired project here
         # TODO: Should we disallow this instead?
         committee_projects = await self.__data.project(
-            committee_name=committee_name, _committee=True, 
_release_policy=True
+            committee_name=str(committee_name), _committee=True, 
_release_policy=True
         ).all()
         for committee_project in committee_projects:
             if label.startswith(str(committee_project.name) + "-"):
@@ -160,7 +159,7 @@ class CommitteeMember(CommitteeParticipant):
             description=super_project.description if super_project else None,
             category=super_project.category if super_project else None,
             programming_languages=super_project.programming_languages if 
super_project else None,
-            committee_name=committee_name,
+            committee_name=str(committee_name),
             created=datetime.datetime.now(datetime.UTC),
             created_by=self.__asf_uid,
         )
@@ -172,7 +171,7 @@ class CommitteeMember(CommitteeParticipant):
         await self.__data.commit()
         self.__write_as.append_to_audit_log(
             asf_uid=self.__asf_uid,
-            committee_name=committee_name,
+            committee_name=str(committee_name),
             project_name=label,
         )
 
diff --git a/atr/web.py b/atr/web.py
index e8cbd8e0..2d79589d 100644
--- a/atr/web.py
+++ b/atr/web.py
@@ -99,8 +99,8 @@ class Committer:
                 return
             raise base.ASFQuartException("You do not have access to this 
project", errorcode=403)
 
-    async def check_access_committee(self, committee_name: str) -> None:
-        if committee_name not in self.committees:
+    async def check_access_committee(self, committee_name: safe.CommitteeKey) 
-> None:
+        if str(committee_name) not in self.committees:
             if self.is_admin:
                 # Admins can view all committees
                 # But we must warn them when the committee is not one of their 
own


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

Reply via email to