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 c1ea4d1  Move the code to upload files into the release writer
c1ea4d1 is described below

commit c1ea4d145b9cff894f0a6bdbfb3234763b0f0f08
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Sep 11 20:37:50 2025 +0100

    Move the code to upload files into the release writer
---
 atr/models/policy.py           |  2 +-
 atr/routes/upload.py           | 56 +++---------------------------------------
 atr/storage/writers/keys.py    |  2 +-
 atr/storage/writers/release.py | 41 ++++++++++++++++++++++++++++++-
 atr/tarzip.py                  |  4 +--
 5 files changed, 48 insertions(+), 57 deletions(-)

diff --git a/atr/models/policy.py b/atr/models/policy.py
index 8abb337..ecc88ac 100644
--- a/atr/models/policy.py
+++ b/atr/models/policy.py
@@ -19,7 +19,7 @@ from typing import Any
 
 import pydantic
 
-from atr.models import schema
+from . import schema
 
 
 # TODO: Maybe it's easier to use quart_schema for all our forms
diff --git a/atr/routes/upload.py b/atr/routes/upload.py
index e326fd9..21ad2d3 100644
--- a/atr/routes/upload.py
+++ b/atr/routes/upload.py
@@ -15,22 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import asyncio
 import pathlib
-from collections.abc import Sequence
 
-import aiofiles
 import quart
-import werkzeug.datastructures as datastructures
 import werkzeug.wrappers.response as response
 import wtforms
 
 import atr.db as db
 import atr.forms as forms
 import atr.log as log
-import atr.revision as revision
 import atr.routes as routes
 import atr.routes.compose as compose
+import atr.storage as storage
 import atr.template as template
 
 
@@ -78,7 +74,9 @@ async def selected(session: routes.CommitterSession, 
project_name: str, version_
                 file_name = pathlib.Path(form.file_name.data)
             file_data = form.file_data.data
 
-            number_of_files = await _upload_files(project_name, version_name, 
session.uid, file_name, file_data)
+            async with storage.write(session.uid) as write:
+                wacp = await 
write.as_project_committee_participant(project_name)
+                number_of_files = await 
wacp.release.upload_files(project_name, version_name, file_name, file_data)
             return await session.redirect(
                 compose.selected,
                 success=f"{number_of_files} file{'' if number_of_files == 1 
else 's'} added successfully",
@@ -107,49 +105,3 @@ async def selected(session: routes.CommitterSession, 
project_name: str, version_
         svn_form=svn_form,
         user_ssh_keys=user_ssh_keys,
     )
-
-
-async def _save_file(file: datastructures.FileStorage, target_path: 
pathlib.Path) -> None:
-    # TODO: Move to the storage interface
-    async with aiofiles.open(target_path, "wb") as f:
-        while chunk := await asyncio.to_thread(file.stream.read, 8192):
-            await f.write(chunk)
-
-
-async def _upload_files(
-    project_name: str,
-    version_name: str,
-    asf_uid: str,
-    file_name: pathlib.Path | None,
-    files: Sequence[datastructures.FileStorage],
-) -> int:
-    """Process and save the uploaded files into a new draft revision."""
-    number_of_files = len(files)
-    description = f"Upload of {number_of_files} file{'' if number_of_files == 
1 else 's'} through web interface"
-    async with revision.create_and_manage(project_name, version_name, asf_uid, 
description=description) as creating:
-
-        def get_target_path(file: datastructures.FileStorage) -> pathlib.Path:
-            # Determine the target path within the new revision directory
-            relative_file_path: pathlib.Path
-            if not file_name:
-                if not file.filename:
-                    raise routes.FlashError("No filename provided")
-                # Use the original name
-                relative_file_path = pathlib.Path(file.filename)
-            else:
-                # Use the provided name, relative to its anchor
-                # In other words, ignore the leading "/"
-                relative_file_path = file_name.relative_to(file_name.anchor)
-
-            # Construct path inside the new revision directory
-            target_path = creating.interim_path / relative_file_path
-            return target_path
-
-        # Save each uploaded file to the new revision directory
-        for file in files:
-            target_path = get_target_path(file)
-            # Ensure parent directories exist within the new revision
-            target_path.parent.mkdir(parents=True, exist_ok=True)
-            await _save_file(file, target_path)
-
-    return len(files)
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index a4c4497..a21033d 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -428,7 +428,7 @@ class CommitteeParticipant(FoundationCommitter):
             return outcome.Error(e)
 
         try:
-            await asyncio.to_thread(committee_keys_dir.mkdir, parents=True, 
exist_ok=True)
+            await aiofiles.os.makedirs(committee_keys_dir, exist_ok=True)
             await asyncio.to_thread(util.chmod_directories, 
committee_keys_dir, permissions=0o755)
             await asyncio.to_thread(committee_keys_path.write_text, 
full_keys_file_content, encoding="utf-8")
         except OSError as e:
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index e508dfe..238981f 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -18,6 +18,7 @@
 # Removing this will cause circular imports
 from __future__ import annotations
 
+import asyncio
 import base64
 import contextlib
 import datetime
@@ -38,8 +39,9 @@ import atr.storage as storage
 import atr.util as util
 
 if TYPE_CHECKING:
-    from collections.abc import AsyncGenerator
+    from collections.abc import AsyncGenerator, Sequence
 
+    import werkzeug.datastructures as datastructures
 
 SPECIAL_SUFFIXES: Final[frozenset[str]] = frozenset({".asc", ".sha256", 
".sha512"})
 
@@ -298,6 +300,38 @@ class CommitteeParticipant(FoundationCommitter):
                 number=creating.new.number,
             ).demand(storage.AccessError("Revision not found"))
 
+    async def upload_files(
+        self,
+        project_name: str,
+        version_name: str,
+        file_name: pathlib.Path | None,
+        files: Sequence[datastructures.FileStorage],
+    ) -> int:
+        """Process and save the uploaded files into a new draft revision."""
+        number_of_files = len(files)
+        description = f"Upload of {number_of_files} file{'' if number_of_files 
== 1 else 's'} through web interface"
+        async with self.create_and_manage_revision(project_name, version_name, 
description) as creating:
+            # Save each uploaded file to the new revision directory
+            for file in files:
+                # Determine the target path within the new revision directory
+                relative_file_path: pathlib.Path
+                if not file_name:
+                    if not file.filename:
+                        raise storage.AccessError("No filename provided")
+                    # Use the original name
+                    relative_file_path = pathlib.Path(file.filename)
+                else:
+                    # Use the provided name, relative to its anchor
+                    # In other words, ignore the leading "/"
+                    relative_file_path = 
file_name.relative_to(file_name.anchor)
+
+                # Construct path inside the new revision directory
+                target_path = creating.interim_path / relative_file_path
+                # Ensure parent directories exist within the new revision
+                await aiofiles.os.makedirs(target_path.parent, exist_ok=True)
+                await self.__save_file(file, target_path)
+        return len(files)
+
     async def __current_paths(self, creating: revision.Creating) -> 
list[pathlib.Path]:
         all_current_paths_interim: list[pathlib.Path] = []
         async for p_rel_interim in 
util.paths_recursive_all(creating.interim_path):
@@ -389,6 +423,11 @@ class CommitteeParticipant(FoundationCommitter):
                 return True, renamed_count_local
         return False, renamed_count_local
 
+    async def __save_file(self, file: datastructures.FileStorage, target_path: 
pathlib.Path) -> None:
+        async with aiofiles.open(target_path, "wb") as f:
+            while chunk := await asyncio.to_thread(file.stream.read, 8192):
+                await f.write(chunk)
+
     async def __setup_revision(
         self,
         source_files_rel: list[pathlib.Path],
diff --git a/atr/tarzip.py b/atr/tarzip.py
index 414f37a..13f6cd8 100644
--- a/atr/tarzip.py
+++ b/atr/tarzip.py
@@ -15,10 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import contextlib
 import tarfile
 import zipfile
 from collections.abc import Generator, Iterator
-from contextlib import contextmanager
 from typing import IO, TypeVar
 from typing import Protocol as TypingProtocol
 
@@ -134,7 +134,7 @@ ZipArchive = ArchiveContext[zipfile.ZipFile]
 Archive = TarArchive | ZipArchive
 
 
-@contextmanager
[email protected]
 def open_archive(archive_path: str) -> Generator[Archive]:
     archive_file: tarfile.TarFile | zipfile.ZipFile | None = None
     try:


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

Reply via email to