This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 7f5b0c63 Remove the deprecated context manager to create a new revision
7f5b0c63 is described below
commit 7f5b0c632c69349edf9a55e1d016bbb70f50864f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Feb 18 17:08:36 2026 +0000
Remove the deprecated context manager to create a new revision
---
atr/storage/types.py | 8 --
atr/storage/writers/release.py | 12 +--
atr/storage/writers/revision.py | 167 +---------------------------------------
3 files changed, 2 insertions(+), 185 deletions(-)
diff --git a/atr/storage/types.py b/atr/storage/types.py
index 3ed5d423..eb7209a5 100644
--- a/atr/storage/types.py
+++ b/atr/storage/types.py
@@ -99,11 +99,3 @@ class PublicKeyError(Exception):
class FailedError(Exception):
pass
-
-
[email protected]
-class Creating:
- old: sql.Revision | None
- interim_path: pathlib.Path
- new: sql.Revision | None
- failed: FailedError | None = None
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 31a78695..7bce2997 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -20,7 +20,6 @@ from __future__ import annotations
import asyncio
import base64
-import contextlib
import datetime
import hashlib
from typing import TYPE_CHECKING, Final
@@ -44,7 +43,7 @@ import atr.util as util
if TYPE_CHECKING:
import pathlib
- from collections.abc import AsyncGenerator, Sequence
+ from collections.abc import Sequence
import werkzeug.datastructures as datastructures
@@ -94,15 +93,6 @@ class CommitteeParticipant(FoundationCommitter):
self.__asf_uid = asf_uid
self.__committee_name = committee_name
- @contextlib.asynccontextmanager
- async def create_and_manage_revision(
- self, project_name: str, version: str, description: str
- ) -> AsyncGenerator[types.Creating]:
- async with self.__write_as.revision.create_and_manage(
- project_name, version, self.__asf_uid, description=description
- ) as _creating:
- yield _creating
-
async def delete(
self,
project_name: str,
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index 8009853a..4e7f6713 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -41,7 +41,7 @@ import atr.tasks as tasks
import atr.util as util
if TYPE_CHECKING:
- from collections.abc import AsyncGenerator, Awaitable, Callable
+ from collections.abc import Awaitable, Callable
class SafeSession:
@@ -105,171 +105,6 @@ class CommitteeParticipant(FoundationCommitter):
self.__asf_uid = asf_uid
self.__committee_name = committee_name
- @contextlib.asynccontextmanager
- async def create_and_manage( # noqa: C901
- self,
- project_name: str,
- version_name: str,
- asf_uid: str,
- description: str | None = None,
- use_check_cache: bool = True,
- ) -> AsyncGenerator[types.Creating]:
- """Manage the creation and symlinking of a mutable release revision."""
- # Get the release
- release_name = sql.release_name(project_name, version_name)
- async with db.session() as data:
- release = await data.release(name=release_name,
_release_policy=True, _project_release_policy=True).demand(
- RuntimeError("Release does not exist for new revision
creation")
- )
- old_revision = await interaction.latest_revision(release)
-
- # Create a temporary directory
- # We ensure, below, that it's removed on any exception
- # Use the tmp subdirectory of state, to ensure that it is on the same
filesystem
- prefix_token = secrets.token_hex(16)
- temp_dir: str = await asyncio.to_thread(tempfile.mkdtemp,
prefix=prefix_token + "-", dir=util.get_tmp_dir())
- temp_dir_path = pathlib.Path(temp_dir)
- creating = types.Creating(old=old_revision,
interim_path=temp_dir_path, new=None, failed=None)
- try:
- # The directory was created by mkdtemp, but it's empty
- if old_revision is not None:
- # If this is not the first revision, hard link the previous
revision
- old_release_dir = util.release_directory(release)
- await util.create_hard_link_clone(old_release_dir,
temp_dir_path, do_not_create_dest_dir=True)
- # The directory is either empty or its files are hard linked to
the previous revision
- yield creating
- except types.FailedError as e:
- await aioshutil.rmtree(temp_dir)
- creating.failed = e
- return
- except Exception:
- await aioshutil.rmtree(temp_dir)
- raise
-
- validation_errors = await
asyncio.to_thread(detection.validate_directory, temp_dir_path)
- if validation_errors:
- await aioshutil.rmtree(temp_dir)
- creating.failed = types.FailedError("File validation failed:\n" +
"\n".join(validation_errors))
- return
-
- # Ensure that the permissions of every directory are 755
- try:
- await asyncio.to_thread(util.chmod_directories, temp_dir_path)
- except Exception:
- await aioshutil.rmtree(temp_dir)
- raise
-
- # Make files read only to prevent them from being modified through
hard links
- try:
- await asyncio.to_thread(util.chmod_files, temp_dir_path, 0o444)
- except Exception:
- await aioshutil.rmtree(temp_dir)
- raise
-
- try:
- path_to_hash, path_to_size = await
attestable.paths_to_hashes_and_sizes(temp_dir_path)
- parent_revision_number = old_revision.number if old_revision else
None
- previous_attestable = None
- if parent_revision_number is not None:
- previous_attestable = await attestable.load(project_name,
version_name, parent_revision_number)
- base_inodes: dict[str, int] = {}
- base_hashes: dict[str, str] = {}
- if old_revision is not None:
- base_dir = util.release_directory(release)
- base_inodes = await asyncio.to_thread(util.paths_to_inodes,
base_dir)
- base_hashes = dict(previous_attestable.paths) if
(previous_attestable is not None) else {}
- n_inodes = await asyncio.to_thread(util.paths_to_inodes,
temp_dir_path)
- except Exception:
- await aioshutil.rmtree(temp_dir)
- raise
-
- async with SafeSession(temp_dir) as data:
- try:
- # This is the only place where models.Revision is constructed
- # That makes models.populate_revision_sequence_and_name safe
against races
- # Because that event is called when data.add is called below
- # And we have a write lock at that point through the use of
data.begin_immediate
- new_revision = sql.Revision(
- release_name=release_name,
- release=release,
- asfuid=asf_uid,
- created=datetime.datetime.now(datetime.UTC),
- phase=release.phase,
- description=description,
- use_check_cache=use_check_cache,
- )
-
- # Acquire the write lock and add the row
- # We need this write lock for moving the directory below
atomically
- # But it also helps to make
models.populate_revision_sequence_and_name safe against races
- await data.begin_immediate()
- data.add(new_revision)
-
- # Flush but do not commit the new revision row to get its name
and number
- # The row will still be invisible to other sessions after
flushing
- await data.flush()
- # Give the caller details about the new revision
- creating.new = new_revision
-
- # Merge with the prior revision if there was an intervening
change
- prior_name = new_revision.parent_name
- if (old_revision is not None) and (prior_name is not None) and
(prior_name != old_revision.name):
- prior_number = prior_name.split()[-1]
- prior_dir = util.release_directory_base(release) /
prior_number
- await merge.merge(
- base_inodes,
- base_hashes,
- prior_dir,
- project_name,
- version_name,
- prior_number,
- temp_dir_path,
- n_inodes,
- path_to_hash,
- path_to_size,
- )
- previous_attestable = await attestable.load(project_name,
version_name, prior_number)
-
- # Rename the directory to the new revision number
- await data.refresh(release)
- new_revision_dir = util.release_directory(release)
-
- # Ensure that the parent directory exists
- await aiofiles.os.makedirs(new_revision_dir.parent,
exist_ok=True)
-
- # Rename the temporary interim directory to the new revision
number
- await aiofiles.os.rename(temp_dir, new_revision_dir)
- except Exception:
- await aioshutil.rmtree(temp_dir)
- raise
-
- policy = release.release_policy or release.project.release_policy
-
- await attestable.write_files_data(
- project_name,
- version_name,
- new_revision.number,
- policy.model_dump() if policy else None,
- asf_uid,
- previous_attestable,
- path_to_hash,
- path_to_size,
- )
-
- # Commit to end the transaction started by data.begin_immediate
- # We must commit the revision before starting the checks
- # This also releases the write lock
- await data.commit()
-
- async with data.begin():
- # Run checks if in DRAFT phase
- # We could also run this outside the data Session
- # But then it would create its own new Session
- # It does, however, need a transaction to be created using
data.begin()
- if release.phase == sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT:
- # Must use caller_data here because we acquired the write
lock
- await tasks.draft_checks(asf_uid, project_name,
version_name, new_revision.number, caller_data=data)
-
async def create_revision( # noqa: C901
self,
project_name: str,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]