You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/06/08 20:01:09 UTC
(tooling-trusted-releases) branch tertia updated: Avoid shadowing a standard library module, and add a lint
This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch tertia
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/tertia by this push:
new b2f31089 Avoid shadowing a standard library module, and add a lint
b2f31089 is described below
commit b2f310896f7494cf15778f0b1977021851fef381
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Mon Jun 8 21:00:43 2026 +0100
Avoid shadowing a standard library module, and add a lint
---
atr/api/__init__.py | 16 +++---
atr/get/checks.py | 8 +--
atr/get/tokens.py | 4 +-
atr/post/draft.py | 6 +--
atr/post/keys.py | 12 ++---
atr/render.py | 10 ++--
atr/shared/keys.py | 4 +-
atr/ssh.py | 10 ++--
atr/storage/{types.py => datatypes.py} | 0
atr/storage/readers/checks.py | 6 +--
atr/storage/readers/releases.py | 24 ++++-----
atr/storage/readers/tokens.py | 10 ++--
atr/storage/writers/keys.py | 72 +++++++++++++--------------
atr/storage/writers/release.py | 58 ++++++++++-----------
atr/storage/writers/revision.py | 14 +++---
atr/storage/writers/tokens.py | 14 +++---
atr/util.py | 4 +-
pyproject.toml | 4 ++
scripts/keys_import.py | 6 +--
tests/unit/test_checker_stats.py | 8 +--
tests/unit/test_checks_table.py | 8 +--
tests/unit/test_concern_acknowledgements.py | 10 ++--
tests/unit/test_create_revision.py | 14 +++---
tests/unit/test_create_revision_quarantine.py | 4 +-
tests/unit/test_release_writer_hashgen.py | 10 ++--
tests/unit/test_source_artifact_check.py | 22 ++++----
tests/unit/test_svn_publish_writer.py | 4 +-
tests/unit/test_tokens_safe.py | 12 ++---
28 files changed, 190 insertions(+), 184 deletions(-)
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index 48414a72..4561099d 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -47,8 +47,8 @@ import atr.paths as paths
import atr.principal as principal
import atr.shared.catalog as catalog
import atr.storage as storage
+import atr.storage.datatypes as datatypes
import atr.storage.outcome as outcome
-import atr.storage.types as types
import atr.tabulate as tabulate
import atr.user as user
import atr.util as util
@@ -688,15 +688,15 @@ async def key_add(
async with storage.write(asf_uid) as write:
wafc = write.as_foundation_committer()
- ocr: outcome.Outcome[types.Key] = await wafc.keys.ensure_stored_one(data.key)
+ ocr: outcome.Outcome[datatypes.Key] = await wafc.keys.ensure_stored_one(data.key)
try:
key = ocr.result_or_raise()
- except types.UnknownApacheUidError as e:
+ except datatypes.UnknownApacheUidError as e:
raise exceptions.BadRequest(str(e)) from e
for selected_committee_key in selected_committee_keys:
wacm = write.as_committee_member(selected_committee_key)
- oc: outcome.Outcome[types.LinkedCommittee] = await wacm.keys.associate_fingerprint(
+ oc: outcome.Outcome[datatypes.LinkedCommittee] = await wacm.keys.associate_fingerprint(
key.key_model.fingerprint
)
oc.result_or_raise()
@@ -797,11 +797,11 @@ async def keys_upload(
selected_committee_key = data.committee
async with storage.write(asf_uid) as write:
wacm = write.as_committee_member(selected_committee_key)
- outcomes: outcome.List[types.Key] = await wacm.keys.ensure_associated(filetext)
+ outcomes: outcome.List[datatypes.Key] = await wacm.keys.ensure_associated(filetext)
# TODO: It would be nice to serialise the actual outcomes
# Or, perhaps better yet, to have a standard datatype mapping
- # This would be specified in models.api, then imported into storage.types
+ # This would be specified in models.api, then imported into storage.datatypes
# Or perhaps it should go in models.storage or models.outcomes
api_outcomes = []
for oc in outcomes.outcomes():
@@ -815,7 +815,7 @@ async def keys_upload(
case outcome.Error(error):
# TODO: This branch means we must improve the return type
match error:
- case types.PublicKeyError() as pke:
+ case datatypes.PublicKeyError() as pke:
api_outcome = models.api.KeysUploadException(
status="error",
key=pke.key.key_model,
@@ -1422,7 +1422,7 @@ async def release_upload(
async with storage.write(asf_uid) as write:
wacp = await write.as_project_committee_participant(data.project)
result = await wacp.release.upload_file(data)
- except types.PhaseMismatchError as e:
+ except datatypes.PhaseMismatchError as e:
raise exceptions.Conflict(str(e))
if isinstance(result, sql.Quarantined):
return {
diff --git a/atr/get/checks.py b/atr/get/checks.py
index efd6f1a7..47a551a4 100644
--- a/atr/get/checks.py
+++ b/atr/get/checks.py
@@ -40,7 +40,7 @@ import atr.post as post
import atr.render as render
import atr.shared.draft as draft
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.template as template
import atr.util as util
import atr.web as web
@@ -202,7 +202,7 @@ async def selected_revision(
def _classification_badge_cell(
- info: types.PathInfo | None, path: safe.RelPath, severity: sql.CheckResultStatus | None
+ info: datatypes.PathInfo | None, path: safe.RelPath, severity: sql.CheckResultStatus | None
) -> htm.Element:
file_type = info.file_types.get(path) if (info is not None) else None
match file_type:
@@ -313,7 +313,7 @@ def _render_checks_table(
release: sql.Release,
paths: list[safe.RelPath],
per_file_stats: dict[safe.RelPath, FileStats],
- info: types.PathInfo | None,
+ info: datatypes.PathInfo | None,
) -> None:
if not paths:
page.div(".alert.alert-info")["This release candidate does not have any files."]
@@ -346,7 +346,7 @@ def _render_file_row(
release: sql.Release,
path: safe.RelPath,
stats: FileStats,
- info: types.PathInfo | None,
+ info: datatypes.PathInfo | None,
) -> None:
path_str = str(path)
num_style = "font-size: 1.1rem;"
diff --git a/atr/get/tokens.py b/atr/get/tokens.py
index 441d6a84..8b01fd30 100644
--- a/atr/get/tokens.py
+++ b/atr/get/tokens.py
@@ -23,7 +23,7 @@ import atr.htm as htm
import atr.post as post
import atr.shared as shared
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.template as template
import atr.util as util
import atr.web as web
@@ -103,7 +103,7 @@ async def tokens(_session: web.Committer, _tokens: Literal["tokens"]) -> str:
)
-async def _build_tokens_table(page: htm.Block, tokens_list: list[types.PersonalAccessTokenSafe]) -> None:
+async def _build_tokens_table(page: htm.Block, tokens_list: list[datatypes.PersonalAccessTokenSafe]) -> None:
if not tokens_list:
page.p["No tokens found."]
return
diff --git a/atr/post/draft.py b/atr/post/draft.py
index b90fb45b..57607d17 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -32,7 +32,7 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.shared as shared
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.util as util
import atr.web as web
@@ -64,7 +64,7 @@ async def cache_reset(
description=description,
reset_to_global_cache=True,
)
- except types.PhaseMismatchError as e:
+ except datatypes.PhaseMismatchError as e:
return await session.redirect(
get.compose.selected,
project_key=str(project_key),
@@ -227,7 +227,7 @@ async def recheck(
description=description,
set_local_cache=True,
)
- except types.PhaseMismatchError as e:
+ except datatypes.PhaseMismatchError as e:
return await session.redirect(
get.compose.selected,
project_key=str(project_key),
diff --git a/atr/post/keys.py b/atr/post/keys.py
index 58f2b30d..c038450b 100644
--- a/atr/post/keys.py
+++ b/atr/post/keys.py
@@ -39,8 +39,8 @@ import atr.paths as paths
import atr.sessions as sessions
import atr.shared as shared
import atr.storage as storage
+import atr.storage.datatypes as datatypes
import atr.storage.outcome as outcome
-import atr.storage.types as types
import atr.util as util
import atr.web as web
@@ -70,18 +70,18 @@ async def add(
async with storage.write() as write:
wafc = write.as_foundation_committer()
- ocr: outcome.Outcome[types.Key] = await wafc.keys.ensure_stored_one(key_text)
+ ocr: outcome.Outcome[datatypes.Key] = await wafc.keys.ensure_stored_one(key_text)
key = ocr.result_or_raise()
for selected_committee_key in selected_committee_keys:
wacp = write.as_committee_participant(selected_committee_key)
- oc: outcome.Outcome[types.LinkedCommittee] = await wacp.keys.associate_fingerprint(
+ oc: outcome.Outcome[datatypes.LinkedCommittee] = await wacp.keys.associate_fingerprint(
key.key_model.fingerprint
)
oc.result_or_raise()
fingerprint_upper = key.key_model.fingerprint.upper()
- if key.status == types.KeyStatus.PARSED:
+ if key.status == datatypes.KeyStatus.PARSED:
details_url = util.as_url(get.keys.details, fingerprint=key.key_model.fingerprint)
p = htm.p[
f"OpenPGP key {fingerprint_upper} was already in the database. ",
@@ -95,7 +95,7 @@ async def add(
except PrivateKeyUploadError:
await quart.flash(util.PRIVATE_KEY_UPLOAD_WARNING, "error")
- except types.UnknownApacheUidError as e:
+ except datatypes.UnknownApacheUidError as e:
await quart.flash(str(e), "error")
except web.FlashError as e:
log.warning(f"FlashError adding OpenPGP key: {e}")
@@ -152,7 +152,7 @@ async def import_selected_revision(
await session.release(project_key, version_key, with_committee=False, with_project=False)
async with storage.write() as write:
wacm = await write.as_project_committee_member(project_key)
- outcomes: outcome.List[types.Key] = await wacm.keys.import_keys_file(project_key, version_key)
+ outcomes: outcome.List[datatypes.Key] = await wacm.keys.import_keys_file(project_key, version_key)
message = f"Uploaded {util.plural(outcomes.result_count, 'key')}"
if outcomes.error_count > 0:
diff --git a/atr/render.py b/atr/render.py
index b867925e..4759fdb5 100644
--- a/atr/render.py
+++ b/atr/render.py
@@ -26,7 +26,7 @@ import atr.get as get
import atr.htm as htm
import atr.models.safe as safe
import atr.models.sql as sql
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.util as util
type Phase = Literal["COMPOSE", "VOTE", "FINISH"]
@@ -309,7 +309,7 @@ def html_recipients_to_radios(
def render_checks_summary(
- info: types.PathInfo | None, project_key: safe.ProjectKey, version_key: safe.VersionKey
+ info: datatypes.PathInfo | None, project_key: safe.ProjectKey, version_key: safe.VersionKey
) -> htm.Element | None:
if info is None:
return None
@@ -346,7 +346,7 @@ def render_checks_summary(
return card.collect()
-def render_exception_banner(info: types.PathInfo) -> htm.Element | None:
+def render_exception_banner(info: datatypes.PathInfo) -> htm.Element | None:
path_count = sum(len(results) for results in info.exceptions.values())
release_count = len(info.release_level_exceptions)
total = path_count + release_count
@@ -375,7 +375,7 @@ def render_exception_banner(info: types.PathInfo) -> htm.Element | None:
def _render_checker_entry(
- stat: types.CheckerStats | None,
+ stat: datatypes.CheckerStats | None,
release_problems: list[sql.CheckResult],
index: int,
project_key: safe.ProjectKey,
@@ -419,7 +419,7 @@ def _render_release_problem(result: sql.CheckResult) -> list[htm.Element | str]:
def _render_stat_files(
files_div: htm.Block,
- stat: types.CheckerStats,
+ stat: datatypes.CheckerStats,
project_key: safe.ProjectKey,
version_key: safe.VersionKey,
) -> None:
diff --git a/atr/shared/keys.py b/atr/shared/keys.py
index eff61859..f408c12a 100644
--- a/atr/shared/keys.py
+++ b/atr/shared/keys.py
@@ -26,7 +26,7 @@ import atr.form as form
import atr.htm as htm
import atr.shared as shared
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.template as template
import atr.util as util
@@ -293,7 +293,7 @@ def _render_results_table(
fingerprint = key_obj.key_model.fingerprint if key_obj else "UNKNOWN"
email_addr = key_obj.key_model.primary_declared_uid if key_obj else ""
# Check whether the LINKED flag is set
- added_flag = bool(key_obj.status & types.KeyStatus.LINKED) if key_obj else False
+ added_flag = bool(key_obj.status & datatypes.KeyStatus.LINKED) if key_obj else False
error_flag = False
else:
err = outcome.error_or_none()
diff --git a/atr/ssh.py b/atr/ssh.py
index 76cee3be..a11afb9f 100644
--- a/atr/ssh.py
+++ b/atr/ssh.py
@@ -46,7 +46,7 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.paths as paths
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.user as user
import atr.util as util
@@ -459,7 +459,7 @@ async def _step_04_command_validate(
async def _step_05a_command_path_validate_read(path: str) -> tuple[safe.ProjectKey, safe.VersionKey, str | None]:
- """Validate the path argument for rsync read commands, returning safe types."""
+ """Validate the path argument for rsync read commands, returning safe datatypes."""
# READ: rsync --server --sender -vlogDtpre.iLsfxCIvu . /proj/v1/
# Validating path: /proj/v1/
_validate_path_common(path)
@@ -490,7 +490,7 @@ async def _step_05a_command_path_validate_read(path: str) -> tuple[safe.ProjectK
async def _step_05b_command_path_validate_write(path: str) -> tuple[safe.ProjectKey, safe.VersionKey, None]:
- """Validate the path argument for rsync write commands, returning safe types."""
+ """Validate the path argument for rsync write commands, returning safe datatypes."""
# WRITE: rsync --server -vlogDtpre.iLsfxCIvu . /proj/v1/
# Validating path: /proj/v1/
_validate_path_common(path)
@@ -667,7 +667,7 @@ async def _step_07b_process_validated_rsync_write(
f"rsync upload failed with exit status {exit_status} for {for_revision}. "
f"Command: {process.command} (run as {' '.join(rsync_argv)})"
)
- raise types.FailedError(f"rsync upload failed with exit status {exit_status} for {for_revision}")
+ raise datatypes.FailedError(f"rsync upload failed with exit status {exit_status} for {for_revision}")
try:
result = await wacp.revision.create_revision_with_quarantine(
@@ -694,7 +694,7 @@ async def _step_07b_process_validated_rsync_write(
if not process.stderr.is_closing():
process.stderr.write(message.encode())
await process.stderr.drain()
- except types.FailedError:
+ except datatypes.FailedError:
log.info(f"rsync upload unsuccessful for release {release_key}")
# If we got here, there was no exception
diff --git a/atr/storage/types.py b/atr/storage/datatypes.py
similarity index 100%
rename from atr/storage/types.py
rename to atr/storage/datatypes.py
diff --git a/atr/storage/readers/checks.py b/atr/storage/readers/checks.py
index 53096479..0811fc5c 100644
--- a/atr/storage/readers/checks.py
+++ b/atr/storage/readers/checks.py
@@ -25,7 +25,7 @@ import atr.db.interaction as interaction
import atr.models.safe as safe
import atr.models.sql as sql
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.util as util
if TYPE_CHECKING:
@@ -46,7 +46,7 @@ class GeneralPublic:
self.__data = data
self.__asf_uid = read.authorisation.asf_uid
- async def by_release_path(self, release: sql.Release, rel_path: pathlib.Path) -> types.CheckResults:
+ async def by_release_path(self, release: sql.Release, rel_path: pathlib.Path) -> datatypes.CheckResults:
if release.latest_revision_number is None:
raise ValueError("Release has no revision - Invalid state")
@@ -77,7 +77,7 @@ class GeneralPublic:
# Order member results by relative path and then by checker name
for member_rel_path in sorted(member_results_list.keys()):
member_results_list[member_rel_path].sort(key=lambda r: r.checker)
- return types.CheckResults(primary_results_list, member_results_list, ignored_checks)
+ return datatypes.CheckResults(primary_results_list, member_results_list, ignored_checks)
async def ignores(self, project_key: safe.ProjectKey) -> list[sql.CheckResultIgnore]:
results = await self.__data.check_result_ignore(
diff --git a/atr/storage/readers/releases.py b/atr/storage/readers/releases.py
index 16cbc097..30a796fa 100644
--- a/atr/storage/readers/releases.py
+++ b/atr/storage/readers/releases.py
@@ -28,7 +28,7 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.paths as paths
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
@dataclasses.dataclass
@@ -50,8 +50,8 @@ class GeneralPublic:
self.__data = data
self.__asf_uid = read.authorisation.asf_uid
- async def path_info(self, release: sql.Release, all_paths: list[safe.RelPath]) -> types.PathInfo | None:
- info = types.PathInfo()
+ async def path_info(self, release: sql.Release, all_paths: list[safe.RelPath]) -> datatypes.PathInfo | None:
+ info = datatypes.PathInfo()
latest_revision_number = release.latest_revision_number
if latest_revision_number is None:
return None
@@ -95,7 +95,7 @@ class GeneralPublic:
files_for_status = acc.files.setdefault(status, {})
files_for_status[path_str] = files_for_status.get(path_str, 0) + 1
- def __compute_checker_stats(self, info: types.PathInfo, paths: list[safe.RelPath]) -> None:
+ def __compute_checker_stats(self, info: datatypes.PathInfo, paths: list[safe.RelPath]) -> None:
paths_set = set(paths)
checker_data: dict[str, CheckerAccumulator] = {}
@@ -112,7 +112,7 @@ class GeneralPublic:
if non_note_total == 0:
continue
info.checker_stats.append(
- types.CheckerStats(
+ datatypes.CheckerStats(
checker=checker,
counts=acc.counts,
files=acc.files,
@@ -120,14 +120,14 @@ class GeneralPublic:
)
async def __notes_suggestions_concerns(
- self, release: sql.Release, latest_revision_number: safe.RevisionNumber, info: types.PathInfo
+ self, release: sql.Release, latest_revision_number: safe.RevisionNumber, info: datatypes.PathInfo
) -> None:
match_ignore = await self.__read_as.checks.ignores_matcher(release.safe_project_key)
attestable_checks = await interaction.checks_for(
release, revision=latest_revision_number, caller_data=self.__data
)
- cs = types.ChecksSubset(
+ cs = datatypes.ChecksSubset(
checks=attestable_checks,
info=info,
match_ignore=match_ignore,
@@ -140,7 +140,7 @@ class GeneralPublic:
await self.__exceptions(cs)
await self.__blocker(cs)
- async def __blocker(self, cs: types.ChecksSubset) -> None:
+ async def __blocker(self, cs: datatypes.ChecksSubset) -> None:
blocker = [cr for cr in cs.checks if cr.status == sql.CheckResultStatus.BLOCKER]
for result in blocker:
if path := result.safe_primary_rel_path:
@@ -148,7 +148,7 @@ class GeneralPublic:
else:
cs.info.release_level_blockers.append(result)
- async def __concerns(self, cs: types.ChecksSubset) -> None:
+ async def __concerns(self, cs: datatypes.ChecksSubset) -> None:
concerns = [cr for cr in cs.checks if cr.status == sql.CheckResultStatus.CONCERN]
for concern in concerns:
if cs.match_ignore(concern):
@@ -159,7 +159,7 @@ class GeneralPublic:
else:
cs.info.release_level_concerns.append(concern)
- async def __exceptions(self, cs: types.ChecksSubset) -> None:
+ async def __exceptions(self, cs: datatypes.ChecksSubset) -> None:
exceptions = [cr for cr in cs.checks if cr.status == sql.CheckResultStatus.EXCEPTION]
for exception in exceptions:
if cs.match_ignore(exception):
@@ -170,14 +170,14 @@ class GeneralPublic:
else:
cs.info.release_level_exceptions.append(exception)
- async def __notes(self, cs: types.ChecksSubset) -> None:
+ async def __notes(self, cs: datatypes.ChecksSubset) -> None:
notes = [cr for cr in cs.checks if cr.status == sql.CheckResultStatus.NOTE]
for note in notes:
# Notes cannot be ignored
if path := note.safe_primary_rel_path:
cs.info.notes.setdefault(path, []).append(note)
- async def __suggestions(self, cs: types.ChecksSubset) -> None:
+ async def __suggestions(self, cs: datatypes.ChecksSubset) -> None:
suggestions = [cr for cr in cs.checks if cr.status == sql.CheckResultStatus.SUGGESTION]
for suggestion in suggestions:
if cs.match_ignore(suggestion):
diff --git a/atr/storage/readers/tokens.py b/atr/storage/readers/tokens.py
index 0119cb10..087f0cb8 100644
--- a/atr/storage/readers/tokens.py
+++ b/atr/storage/readers/tokens.py
@@ -23,7 +23,7 @@ import sqlmodel
import atr.db as db
import atr.models.sql as sql
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
class GeneralPublic:
@@ -41,7 +41,7 @@ class FoundationCommitter(GeneralPublic):
self.__read_as = read_as
self.__data = data
- async def own_personal_access_tokens(self) -> list[types.PersonalAccessTokenSafe]:
+ async def own_personal_access_tokens(self) -> list[datatypes.PersonalAccessTokenSafe]:
asf_uid = self.__read.authorisation.asf_uid
if asf_uid is None:
raise ValueError("Not authorized")
@@ -49,9 +49,9 @@ class FoundationCommitter(GeneralPublic):
tokens = await (
self.__data.personal_access_token(asfuid=asf_uid).order_by(via(sql.PersonalAccessToken.created)).all()
)
- return [types.PersonalAccessTokenSafe.from_sql(token) for token in tokens]
+ return [datatypes.PersonalAccessTokenSafe.from_sql(token) for token in tokens]
- async def most_recent_jwt_pat(self) -> types.PersonalAccessTokenSafe | None:
+ async def most_recent_jwt_pat(self) -> datatypes.PersonalAccessTokenSafe | None:
# , asf_uid: str | None = None
# if asf_uid is None:
asf_uid = self.__read.authorisation.asf_uid
@@ -69,4 +69,4 @@ class FoundationCommitter(GeneralPublic):
token = await self.__data.query_one_or_none(stmt)
if token is None:
return None
- return types.PersonalAccessTokenSafe.from_sql(token)
+ return datatypes.PersonalAccessTokenSafe.from_sql(token)
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index 890b81ac..36d5bd50 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -42,8 +42,8 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.paths as paths
import atr.storage as storage
+import atr.storage.datatypes as datatypes
import atr.storage.outcome as outcome
-import atr.storage.types as types
import atr.svn as svn
import atr.tasks as tasks
import atr.user as user
@@ -234,7 +234,7 @@ class FoundationCommitter(GeneralPublic):
except Exception as e:
return outcome.Error(e)
- async def ensure_stored_one(self, key_file_text: str) -> outcome.Outcome[types.Key]:
+ async def ensure_stored_one(self, key_file_text: str) -> outcome.Outcome[datatypes.Key]:
return await self.__ensure_one(key_file_text, associate=False)
def public_key_model(
@@ -484,7 +484,7 @@ class FoundationCommitter(GeneralPublic):
return affected
- def __block_model(self, key_block: str, ldap_data: cache.EmailUidLookup) -> types.Key:
+ def __block_model(self, key_block: str, ldap_data: cache.EmailUidLookup) -> datatypes.Key:
# This cache is only held for the session
if key_block in self.__key_block_models_cache:
cached_key_models = self.__key_block_models_cache[key_block]
@@ -497,16 +497,16 @@ class FoundationCommitter(GeneralPublic):
self.__key_block_models_cache[key_block] = [key]
return key
- def __block_model_create(self, key_block: str, ldap_data: cache.EmailUidLookup) -> types.Key:
+ def __block_model_create(self, key_block: str, ldap_data: cache.EmailUidLookup) -> datatypes.Key:
public_key, _ = openpgp.PublicKey.from_armor(key_block)
key_model = self.public_key_model(public_key, ldap_data, original_key_block=key_block)
_validate_key_strength(key_model.algorithm, key_model.length, key_model.created)
- return types.Key(status=types.KeyStatus.PARSED, key_model=key_model)
+ return datatypes.Key(status=datatypes.KeyStatus.PARSED, key_model=key_model)
async def __database_add_model(
self,
- key: types.Key,
- ) -> outcome.Outcome[types.Key]:
+ key: datatypes.Key,
+ ) -> outcome.Outcome[datatypes.Key]:
via = sql.validate_instrumented_attribute
await self.__data.begin_immediate()
@@ -522,7 +522,7 @@ class FoundationCommitter(GeneralPublic):
if key_insert_result.one_or_none() is None:
log.info(f"Key {key.key_model.fingerprint} already exists in database")
- return outcome.Result(types.Key(status=types.KeyStatus.PARSED, key_model=key.key_model))
+ return outcome.Result(datatypes.Key(status=datatypes.KeyStatus.PARSED, key_model=key.key_model))
log.info(f"Inserted key {key.key_model.fingerprint}")
self.__write_as.append_to_audit_log(
@@ -533,9 +533,9 @@ class FoundationCommitter(GeneralPublic):
)
# TODO: PARSED now acts as "ALREADY_ADDED"
- return outcome.Result(types.Key(status=types.KeyStatus.INSERTED, key_model=key.key_model))
+ return outcome.Result(datatypes.Key(status=datatypes.KeyStatus.INSERTED, key_model=key.key_model))
- async def __ensure_one(self, key_file_text: str, associate: bool = True) -> outcome.Outcome[types.Key]:
+ async def __ensure_one(self, key_file_text: str, associate: bool = True) -> outcome.Outcome[datatypes.Key]:
try:
key_blocks = util.parse_key_blocks(key_file_text)
except Exception as e:
@@ -554,7 +554,7 @@ class FoundationCommitter(GeneralPublic):
return outcome.Error(e)
if key.key_model.apache_uid is None:
return outcome.Error(
- types.UnknownApacheUidError(
+ datatypes.UnknownApacheUidError(
"OpenPGP key could not be associated with an ASF UID. Import it through a KEYS file instead."
)
)
@@ -658,7 +658,7 @@ class CommitteeParticipant(FoundationCommitter):
self.__asf_uid = asf_uid
self.__committee_key = committee_key
- async def associate_fingerprint(self, fingerprint: str) -> outcome.Outcome[types.LinkedCommittee]:
+ async def associate_fingerprint(self, fingerprint: str) -> outcome.Outcome[datatypes.LinkedCommittee]:
via = sql.validate_instrumented_attribute
link_values = [{"committee_key": self.__committee_key, "key_fingerprint": fingerprint}]
try:
@@ -689,7 +689,7 @@ class CommitteeParticipant(FoundationCommitter):
except Exception as e:
return outcome.Error(e)
return outcome.Result(
- types.LinkedCommittee(
+ datatypes.LinkedCommittee(
name=self.__committee_key,
autogenerated_keys_file=autogenerated_outcome,
)
@@ -722,21 +722,21 @@ class CommitteeParticipant(FoundationCommitter):
def committee_key(self) -> str:
return self.__committee_key
- async def ensure_associated(self, keys_file_text: str) -> outcome.List[types.Key]:
- outcomes: outcome.List[types.Key] = await self.__ensure(keys_file_text, associate=True)
+ async def ensure_associated(self, keys_file_text: str) -> outcome.List[datatypes.Key]:
+ outcomes: outcome.List[datatypes.Key] = await self.__ensure(keys_file_text, associate=True)
if outcomes.any_result:
await self.autogenerate_keys_file()
return outcomes
- async def ensure_stored(self, keys_file_text: str) -> outcome.List[types.Key]:
- outcomes: outcome.List[types.Key] = await self.__ensure(keys_file_text, associate=False)
+ async def ensure_stored(self, keys_file_text: str) -> outcome.List[datatypes.Key]:
+ outcomes: outcome.List[datatypes.Key] = await self.__ensure(keys_file_text, associate=False)
if outcomes.any_result:
await self.autogenerate_keys_file()
return outcomes
async def import_keys_file(
self, project_key: safe.ProjectKey, version_key: safe.VersionKey
- ) -> outcome.List[types.Key]:
+ ) -> outcome.List[datatypes.Key]:
release = await self.__data.release(
project_key=str(project_key),
version=str(version_key),
@@ -784,7 +784,7 @@ class CommitteeParticipant(FoundationCommitter):
)
return outcomes
- def __block_models(self, key_block: str, ldap_data: cache.EmailUidLookup) -> list[types.Key | Exception]:
+ def __block_models(self, key_block: str, ldap_data: cache.EmailUidLookup) -> list[datatypes.Key | Exception]:
try:
public_key, _ = openpgp.PublicKey.from_armor(key_block)
except Exception as e:
@@ -793,15 +793,15 @@ class CommitteeParticipant(FoundationCommitter):
try:
key_model = self.public_key_model(public_key, ldap_data, original_key_block=key_block)
_validate_key_strength(key_model.algorithm, key_model.length, key_model.created)
- key = types.Key(status=types.KeyStatus.PARSED, key_model=key_model)
+ key = datatypes.Key(status=datatypes.KeyStatus.PARSED, key_model=key_model)
key_list.append(key)
except Exception as e:
key_list.append(e)
return key_list
async def __database_add_models(
- self, outcomes: outcome.List[types.Key], associate: bool = True
- ) -> outcome.List[types.Key]:
+ self, outcomes: outcome.List[datatypes.Key], associate: bool = True
+ ) -> outcome.List[datatypes.Key]:
# Try to upsert all models and link to the committee in one transaction
try:
outcomes = await self.__database_add_models_core(outcomes, associate=associate)
@@ -809,20 +809,20 @@ class CommitteeParticipant(FoundationCommitter):
# This logging is just so that ruff does not erase e
log.info(f"Post-parse error: {e}")
- def raise_post_parse_error(key: types.Key) -> NoReturn:
+ def raise_post_parse_error(key: datatypes.Key) -> NoReturn:
nonlocal e
# We assume here that the transaction was rolled back correctly
- key = types.Key(status=types.KeyStatus.PARSED, key_model=key.key_model)
- raise types.PublicKeyError(key, e)
+ key = datatypes.Key(status=datatypes.KeyStatus.PARSED, key_model=key.key_model)
+ raise datatypes.PublicKeyError(key, e)
outcomes.update_roes(Exception, raise_post_parse_error)
return outcomes
async def __database_add_models_core( # noqa: C901
self,
- outcomes: outcome.List[types.Key],
+ outcomes: outcome.List[datatypes.Key],
associate: bool = True,
- ) -> outcome.List[types.Key]:
+ ) -> outcome.List[datatypes.Key]:
via = sql.validate_instrumented_attribute
key_list = outcomes.results()
@@ -846,9 +846,9 @@ class CommitteeParticipant(FoundationCommitter):
key_inserts = set()
log.info("Inserted 0 keys (no keys to insert)")
- def replace_with_inserted(key: types.Key) -> types.Key:
+ def replace_with_inserted(key: datatypes.Key) -> datatypes.Key:
if key.key_model.fingerprint in key_inserts:
- key.status = types.KeyStatus.INSERTED
+ key.status = datatypes.KeyStatus.INSERTED
return key
outcomes.update_roes(Exception, replace_with_inserted)
@@ -870,15 +870,15 @@ class CommitteeParticipant(FoundationCommitter):
link_inserts = {row.key_fingerprint for row in link_insert_result}
log.info(f"Inserted {util.plural(len(link_inserts), 'key link')}")
- def replace_with_linked(key: types.Key) -> types.Key:
+ def replace_with_linked(key: datatypes.Key) -> datatypes.Key:
# nonlocal link_inserts
match key:
- case types.Key(status=types.KeyStatus.INSERTED):
+ case datatypes.Key(status=datatypes.KeyStatus.INSERTED):
if key.key_model.fingerprint in link_inserts:
- key.status = types.KeyStatus.INSERTED_AND_LINKED
- case types.Key(status=types.KeyStatus.PARSED):
+ key.status = datatypes.KeyStatus.INSERTED_AND_LINKED
+ case datatypes.Key(status=datatypes.KeyStatus.PARSED):
if key.key_model.fingerprint in link_inserts:
- key.status = types.KeyStatus.LINKED
+ key.status = datatypes.KeyStatus.LINKED
return key
outcomes.update_roes(Exception, replace_with_linked)
@@ -902,8 +902,8 @@ class CommitteeParticipant(FoundationCommitter):
self,
keys_file_text: str,
associate: bool = True,
- ) -> outcome.List[types.Key]:
- outcomes = outcome.List[types.Key]()
+ ) -> outcome.List[datatypes.Key]:
+ outcomes = outcome.List[datatypes.Key]()
try:
ldap_data = await cache.email_uid_view_or_live()
key_blocks = util.parse_key_blocks(keys_file_text)
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 978dd9e3..c9203ef2 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -47,7 +47,7 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.paths as paths
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.svn as svn
import atr.tasks.checks as checks
import atr.tasks.checks.signature as signature
@@ -89,7 +89,7 @@ async def _signature_provenance_metadata_for(
signature_rel_path: pathlib.Path,
) -> dict[str, str]:
if parent_revision is None:
- raise types.FailedError("SHA512 generation requires a parent revision with a verified signature.")
+ raise datatypes.FailedError("SHA512 generation requires a parent revision with a verified signature.")
release_key = sql.release_key(str(project_key), str(version_key))
parent_revision_number = parent_revision.safe_number
parent_number = str(parent_revision_number)
@@ -107,7 +107,7 @@ async def _signature_provenance_metadata_for(
"SHA512 generation is waiting for signature verification"
f" release={release_key} revision={parent_number} path={signature_rel_str}"
)
- raise types.FailedError(
+ raise datatypes.FailedError(
"Signature verification has not completed yet for"
f" {signature_rel_path.name}. Please wait for checks to finish and try again."
)
@@ -121,8 +121,10 @@ async def _signature_provenance_metadata_for(
)
if latest.status != sql.CheckResultStatus.NOTE:
if latest_message:
- raise types.FailedError(f"Signature verification for {signature_rel_path.name} failed: {latest_message}")
- raise types.FailedError(
+ raise datatypes.FailedError(
+ f"Signature verification for {signature_rel_path.name} failed: {latest_message}"
+ )
+ raise datatypes.FailedError(
"Signature verification for"
f" {signature_rel_path.name} is {latest.status.value}; cannot generate the SHA512 yet."
)
@@ -361,9 +363,9 @@ class CommitteeParticipant(FoundationCommitter):
resolved = await asyncio.to_thread(path_to_remove.path.resolve)
resolved.relative_to(await asyncio.to_thread(path.path.resolve))
if not await aiofiles.os.path.isdir(path_to_remove):
- raise types.FailedError(f"Path '{dir_to_delete_rel}' is not a directory.")
+ raise datatypes.FailedError(f"Path '{dir_to_delete_rel}' is not a directory.")
if await aiofiles.os.listdir(path_to_remove):
- raise types.FailedError(f"Directory '{dir_to_delete_rel}' is not empty.")
+ raise datatypes.FailedError(f"Directory '{dir_to_delete_rel}' is not empty.")
await aiofiles.os.rmdir(path_to_remove)
try:
@@ -375,7 +377,7 @@ class CommitteeParticipant(FoundationCommitter):
description=description,
modify=modify,
)
- except types.FailedError as e:
+ except datatypes.FailedError as e:
return str(e)
return None
@@ -454,7 +456,7 @@ class CommitteeParticipant(FoundationCommitter):
raise storage.AccessError("SHA512 file already exists", status=409)
if not await aiofiles.os.path.exists(signature_path_in_new_revision):
- raise types.FailedError(
+ raise datatypes.FailedError(
"SHA512 generation requires a detached OpenPGP signature at"
f" {signature_rel_path!s}. Please upload the signature first."
)
@@ -556,7 +558,7 @@ class CommitteeParticipant(FoundationCommitter):
description=description,
modify=modify,
)
- except types.FailedError as e:
+ except datatypes.FailedError as e:
return str(e), moved_files_names, skipped_files_names
return None, moved_files_names, skipped_files_names
@@ -655,18 +657,18 @@ class CommitteeParticipant(FoundationCommitter):
phase=sql.ReleasePhase.RELEASE_PREVIEW,
_project=True,
_committee=True,
- ).demand(types.FailedError("Release preview not found for publish"))
+ ).demand(datatypes.FailedError("Release preview not found for publish"))
storage.ensure_project_active(release.project)
committee = release.project.committee
if committee is None:
- raise types.FailedError("Release has no committee - Invalid state")
+ raise datatypes.FailedError("Release has no committee - Invalid state")
latest_revision = release.safe_latest_revision_number
if latest_revision != task_args.revision_number:
- raise types.FailedError("A newer revision appeared after queuing this publish task")
+ raise datatypes.FailedError("A newer revision appeared after queuing this publish task")
try:
internal_url = util.svn_publish_internal_url(committee, task_args.download_path_suffix)
except ValueError as exc:
- raise types.FailedError(f"SVN publish URL is not acceptable: {exc}") from exc
+ raise datatypes.FailedError(f"SVN publish URL is not acceptable: {exc}") from exc
preview_path = paths.release_directory(release)
log_message = (
f"Publish {task_args.project_key!s}-{task_args.version_key!s}\n\n"
@@ -687,10 +689,10 @@ class CommitteeParticipant(FoundationCommitter):
message = f"{message}: {match.group(1)}"
if svn_publish_url := config.get().SVN_PUBLISH_URL:
message = message.replace(svn_publish_url, "")
- raise types.FailedError(message) from exc
- raise types.FailedError("SVN publish failed; see worker logs for details") from exc
+ raise datatypes.FailedError(message) from exc
+ raise datatypes.FailedError("SVN publish failed; see worker logs for details") from exc
if revision is None:
- raise types.FailedError("SVN publish did not return a Committed revision line")
+ raise datatypes.FailedError("SVN publish did not return a Committed revision line")
return results.SvnPublish(
kind="svn_publish",
svn_revision=revision,
@@ -891,7 +893,7 @@ class CommitteeParticipant(FoundationCommitter):
description=description,
modify=modify,
)
- except types.FailedError as e:
+ except datatypes.FailedError as e:
return str(e), renamed_count, error_messages
return None, renamed_count, error_messages
@@ -1060,7 +1062,7 @@ class CommitteeParticipant(FoundationCommitter):
description=description,
modify=modify,
)
- except types.FailedError as e:
+ except datatypes.FailedError as e:
return str(e), len(files), False
return None, len(files), isinstance(result, sql.Quarantined)
@@ -1182,23 +1184,23 @@ class CommitteeParticipant(FoundationCommitter):
resolved.relative_to(await asyncio.to_thread(interim_path.path.resolve))
except ValueError:
# Path traversal detected
- raise types.FailedError("Paths must be restricted to the release directory")
+ raise datatypes.FailedError("Paths must be restricted to the release directory")
if not await aiofiles.os.path.exists(target_path):
for part in target_path.path.parts:
# TODO: This .prefix check could include some existing directory segment
# TODO: The second check probably isn't needed now since this came from a safe RelPath type.
if util.is_disallowed_dotfile(part):
- raise types.FailedError("This segment is a disallowed dotfile")
+ raise datatypes.FailedError("This segment is a disallowed dotfile")
if ".." in part:
- raise types.FailedError("Segments must not contain '..'")
+ raise datatypes.FailedError("Segments must not contain '..'")
try:
await aiofiles.os.makedirs(target_path)
except OSError:
- raise types.FailedError("Failed to create target directory")
+ raise datatypes.FailedError("Failed to create target directory")
elif not await aiofiles.os.path.isdir(target_path):
- raise types.FailedError("Target path is not a directory")
+ raise datatypes.FailedError("Target path is not a directory")
for source_file_rel in source_files_rel:
await self.__setup_revision_item(
@@ -1226,11 +1228,11 @@ class CommitteeParticipant(FoundationCommitter):
if (target_dir_rel == source_file_rel) or (interim_path / target_dir_path).path.resolve().is_relative_to(
full_source_item_path.path.resolve()
):
- raise types.FailedError("Cannot move a directory into itself or a subdirectory of itself")
+ raise datatypes.FailedError("Cannot move a directory into itself or a subdirectory of itself")
final_target_for_item = target_path / source_path.name
if await aiofiles.os.path.exists(final_target_for_item):
- raise types.FailedError("Target name already exists")
+ raise datatypes.FailedError("Target name already exists")
await aiofiles.os.rename(full_source_item_path, final_target_for_item)
moved_files_names.append(source_path.name)
@@ -1239,11 +1241,11 @@ class CommitteeParticipant(FoundationCommitter):
bundle = [f for f in related_files if await aiofiles.os.path.exists(interim_path / f)]
for f_check in bundle:
if await aiofiles.os.path.isdir(interim_path / f_check):
- raise types.FailedError("A related 'file' is actually a directory")
+ raise datatypes.FailedError("A related 'file' is actually a directory")
collisions = [f.name for f in bundle if await aiofiles.os.path.exists(target_path / f.name)]
if collisions:
- raise types.FailedError("A related file already exists in the target directory")
+ raise datatypes.FailedError("A related file already exists in the target directory")
for f in bundle:
await aiofiles.os.rename(interim_path / f, target_path / f.name)
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index 4f1858c1..b8db47ee 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -43,7 +43,7 @@ import atr.models.safe as safe
import atr.models.sql as sql
import atr.paths as paths
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.tasks as tasks
import atr.util as util
@@ -185,7 +185,7 @@ async def _commit_new_revision(
# Raise an error if the destination directory already exists
# This can happen for example if there was a previous failed cleanup
if await aiofiles.os.path.exists(new_revision_dir):
- raise types.FailedError(f"Revision directory {new_revision_dir} already exists")
+ raise datatypes.FailedError(f"Revision directory {new_revision_dir} already exists")
# Rename the temporary interim directory to the new revision number
await aiofiles.os.rename(temp_dir, new_revision_dir)
@@ -344,7 +344,7 @@ async def _verify_provenance_sources_unchanged(
except FileNotFoundError:
post_merge_inode = None
if (pre_merge_inode is None) or (post_merge_inode != pre_merge_inode):
- raise types.FailedError(
+ raise datatypes.FailedError(
f"A concurrent revision replaced {dependency_rel}; please regenerate {generated_rel}."
)
@@ -442,7 +442,7 @@ class CommitteeParticipant(FoundationCommitter):
)
storage.ensure_project_active(release.project)
if release.phase not in allowed_phases:
- raise types.PhaseMismatchError(
+ raise datatypes.PhaseMismatchError(
f"Cannot create revision: release phase is {release.phase.value}, "
f"allowed: {', '.join(sorted(p.value for p in allowed_phases))}"
)
@@ -475,7 +475,7 @@ class CommitteeParticipant(FoundationCommitter):
# The directory is either empty or its files are hard linked to the previous revision
if modify is not None:
path_provenance = await modify(temp_dir_path, old_revision)
- except types.FailedError:
+ except datatypes.FailedError:
await aioshutil.rmtree(temp_dir)
raise
except Exception:
@@ -485,7 +485,7 @@ class CommitteeParticipant(FoundationCommitter):
validation_errors = await asyncio.to_thread(detection.validate_directory, temp_dir_path.path)
if validation_errors:
await aioshutil.rmtree(temp_dir)
- raise types.FailedError("File validation failed:\n" + "\n".join(validation_errors))
+ raise datatypes.FailedError("File validation failed:\n" + "\n".join(validation_errors))
# Ensure that the permissions of every directory are 755
try:
@@ -547,7 +547,7 @@ class CommitteeParticipant(FoundationCommitter):
if merged_release.phase not in allowed_phases:
await aioshutil.rmtree(temp_dir)
- raise types.PhaseMismatchError(
+ raise datatypes.PhaseMismatchError(
f"Cannot create revision: release phase is {merged_release.phase.value}, "
f"allowed: {', '.join(sorted(p.value for p in allowed_phases))}"
)
diff --git a/atr/storage/writers/tokens.py b/atr/storage/writers/tokens.py
index fa1aae97..78b61e1f 100644
--- a/atr/storage/writers/tokens.py
+++ b/atr/storage/writers/tokens.py
@@ -32,7 +32,7 @@ import atr.log as log
import atr.mail as mail
import atr.models.sql as sql
import atr.storage as storage
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
class GeneralPublic:
@@ -61,7 +61,7 @@ class FoundationCommitter(GeneralPublic):
async def add_token(
self, token_hash: str, created: datetime.datetime, expires: datetime.datetime, label: str | None
- ) -> types.PersonalAccessTokenSafe:
+ ) -> datatypes.PersonalAccessTokenSafe:
if not label:
raise ValueError("Label is required")
pat = sql.PersonalAccessToken(
@@ -83,7 +83,7 @@ class FoundationCommitter(GeneralPublic):
"If you did not create this token, please revoke it immediately.",
)
await self.__write_as.mail.send(message, mail.MailFooterCategory.AUTO)
- return types.PersonalAccessTokenSafe.from_sql(pat)
+ return datatypes.PersonalAccessTokenSafe.from_sql(pat)
# audit_guidance PAT deletion revokes associated JWTs
# audit_guidance JWT verification rechecks PAT existence on every API request
@@ -197,7 +197,7 @@ class FoundationAdmin(FoundationCommitter):
expires: datetime.datetime,
label: str,
allowed_ip: str | None,
- ) -> types.PersonalAccessTokenSafe:
+ ) -> datatypes.PersonalAccessTokenSafe:
if not label:
raise ValueError("Label is required")
# asfuid stays null so a system PAT has no owning user; created_by
@@ -221,15 +221,15 @@ class FoundationAdmin(FoundationCommitter):
allowed_ip=allowed_ip,
)
log.auth_event("system_pat_issuance", self.__asf_uid, pat_hash=token_hash, name=label)
- return types.PersonalAccessTokenSafe.from_sql(pat)
+ return datatypes.PersonalAccessTokenSafe.from_sql(pat)
- async def list_system_tokens(self) -> list[types.PersonalAccessTokenSafe]:
+ async def list_system_tokens(self) -> list[datatypes.PersonalAccessTokenSafe]:
tokens = await (
self.__data.personal_access_token(is_system=True)
.order_by(sql.validate_instrumented_attribute(sql.PersonalAccessToken.created))
.all()
)
- return [types.PersonalAccessTokenSafe.from_sql(token) for token in tokens]
+ return [datatypes.PersonalAccessTokenSafe.from_sql(token) for token in tokens]
async def revoke_all_user_tokens(self, target_asf_uid: str) -> int:
"""Revoke all PATs that a specified user owns or created. Returns count of revoked tokens."""
diff --git a/atr/util.py b/atr/util.py
index a49a9217..0dc5d43c 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -63,7 +63,7 @@ import atr.registry as registry
import atr.user as user
if TYPE_CHECKING:
- import atr.storage.types as types
+ import atr.storage.datatypes as datatypes
ARCHIVE_ROOT_SUFFIXES: Final[tuple[str, ...]] = (
"-binary-assembly",
@@ -352,7 +352,7 @@ def concern_acknowledgement_error(missing: Sequence[ConcernGroup]) -> str:
return f"{CONCERN_ACKNOWLEDGEMENT_MESSAGE}: {', '.join(listed)}{suffix}"
-def concern_groups(info: "types.PathInfo | None") -> list[ConcernGroup]:
+def concern_groups(info: "datatypes.PathInfo | None") -> list[ConcernGroup]:
if info is None:
return []
counts_by_checker: dict[str, int] = collections.defaultdict(int)
diff --git a/pyproject.toml b/pyproject.toml
index a7adc15a..e6016042 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -140,6 +140,7 @@ ignore = [
"S704", # markupsafe
]
select = [
+ "A005", # stdlib-module-shadowing
"ASYNC", # flake8-async
"C90", # cyclomatic-complexity
"E", # pycodestyle
@@ -154,6 +155,9 @@ select = [
"W"
]
+[tool.ruff.lint.flake8-builtins]
+strict-checking = true
+
[tool.ruff.lint.per-file-ignores]
"atr/analysis.py" = ["RUF001"]
"atr/db/__init__.py" = ["C901"]
diff --git a/scripts/keys_import.py b/scripts/keys_import.py
index 8b019161..9c4d4bdc 100755
--- a/scripts/keys_import.py
+++ b/scripts/keys_import.py
@@ -34,8 +34,8 @@ import atr.cache as cache
import atr.config as config
import atr.db as db
import atr.storage as storage
+import atr.storage.datatypes as datatypes
import atr.storage.outcome as outcome
-import atr.storage.types as types
import atr.util as util
if TYPE_CHECKING:
@@ -102,11 +102,11 @@ def format_exception_location(exc: BaseException) -> str:
return f"{type(exc).__name__} at {filename}:{lineno} in {func}: {exc}"
-def log_outcome_errors(outcomes: outcome.List[types.Key], committee_key: str) -> None:
+def log_outcome_errors(outcomes: outcome.List[datatypes.Key], committee_key: str) -> None:
for error in outcomes.errors():
fingerprint = "unknown"
detail_exception: BaseException = error
- if isinstance(error, types.PublicKeyError):
+ if isinstance(error, datatypes.PublicKeyError):
fingerprint = error.key.key_model.fingerprint
detail_exception = error.original_error
elif isinstance(error, BaseException):
diff --git a/tests/unit/test_checker_stats.py b/tests/unit/test_checker_stats.py
index 915597ac..4b8d370b 100644
--- a/tests/unit/test_checker_stats.py
+++ b/tests/unit/test_checker_stats.py
@@ -22,8 +22,8 @@ import unittest.mock as mock
import atr.models.safe as safe
import atr.models.sql as sql
+import atr.storage.datatypes as datatypes
import atr.storage.readers.releases as releases
-import atr.storage.types as types
def test_checker_stats_counts_and_files_by_status():
@@ -32,7 +32,7 @@ def test_checker_stats_counts_and_files_by_status():
warning = _make_check_result(sql.CheckResultStatus.SUGGESTION, "Warning", primary_rel_path=str(path))
failure = _make_check_result(sql.CheckResultStatus.CONCERN, "Failure", primary_rel_path=str(path))
blocker = _make_check_result(sql.CheckResultStatus.BLOCKER, "Blocker", primary_rel_path=str(path))
- info = types.PathInfo(
+ info = datatypes.PathInfo(
notes={path: [success]},
suggestions={path: [warning]},
concerns={path: [failure]},
@@ -65,8 +65,8 @@ def test_exceptions_bucketed_into_path_info():
path = safe.RelPath("apache-test-1.0-source.tar.gz")
exception = _make_check_result(sql.CheckResultStatus.EXCEPTION, "Error", primary_rel_path=str(path))
release_level = _make_check_result(sql.CheckResultStatus.EXCEPTION, "Tooling failure", primary_rel_path=None)
- info = types.PathInfo()
- subset = types.ChecksSubset(checks=[exception, release_level], info=info, match_ignore=lambda _: False)
+ info = datatypes.PathInfo()
+ subset = datatypes.ChecksSubset(checks=[exception, release_level], info=info, match_ignore=lambda _: False)
reader = _make_reader()
bucket_exceptions = getattr(reader, "_GeneralPublic__exceptions")
diff --git a/tests/unit/test_checks_table.py b/tests/unit/test_checks_table.py
index bdfee1f5..e893bbfc 100644
--- a/tests/unit/test_checks_table.py
+++ b/tests/unit/test_checks_table.py
@@ -21,7 +21,7 @@ import unittest.mock as mock
import atr.models.safe as safe
import atr.models.sql as sql
import atr.render as render
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
def test_highest_severity_precedence() -> None:
@@ -46,13 +46,13 @@ def test_highest_severity_precedence() -> None:
def test_render_exception_banner_empty_returns_none() -> None:
- info = types.PathInfo()
+ info = datatypes.PathInfo()
assert render.render_exception_banner(info) is None
def test_render_exception_banner_path_level() -> None:
path = safe.RelPath("apache-test-1.0-source.tar.gz")
- info = types.PathInfo()
+ info = datatypes.PathInfo()
info.exceptions[path] = [_fake_check_result()]
banner = render.render_exception_banner(info)
assert banner is not None
@@ -63,7 +63,7 @@ def test_render_exception_banner_path_level() -> None:
def test_render_exception_banner_release_level_only() -> None:
- info = types.PathInfo()
+ info = datatypes.PathInfo()
info.release_level_exceptions.append(_fake_check_result())
banner = render.render_exception_banner(info)
assert banner is not None
diff --git a/tests/unit/test_concern_acknowledgements.py b/tests/unit/test_concern_acknowledgements.py
index 90170702..beb4b5fa 100644
--- a/tests/unit/test_concern_acknowledgements.py
+++ b/tests/unit/test_concern_acknowledgements.py
@@ -19,12 +19,12 @@ import collections
from types import SimpleNamespace
import atr.models.sql as sql
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.util as util
def test_concern_groups_and_missing():
- headers_stat = types.CheckerStats(
+ headers_stat = datatypes.CheckerStats(
checker="atr.tasks.checks.license.headers",
counts=collections.Counter(
{
@@ -34,18 +34,18 @@ def test_concern_groups_and_missing():
),
files={sql.CheckResultStatus.CONCERN: {"a": 6}},
)
- paths_stat = types.CheckerStats(
+ paths_stat = datatypes.CheckerStats(
checker="atr.tasks.checks.paths",
counts=collections.Counter({sql.CheckResultStatus.CONCERN: 3}),
files={sql.CheckResultStatus.CONCERN: {"a": 3}},
)
- suggestions_only_stat = types.CheckerStats(
+ suggestions_only_stat = datatypes.CheckerStats(
checker="atr.tasks.checks.targz.structure",
counts=collections.Counter({sql.CheckResultStatus.SUGGESTION: 2}),
files={sql.CheckResultStatus.SUGGESTION: {"a": 2}},
)
release_level = SimpleNamespace(checker="atr.tasks.checks.signature")
- info = types.PathInfo.model_construct(
+ info = datatypes.PathInfo.model_construct(
checker_stats=[headers_stat, paths_stat, suggestions_only_stat],
release_level_concerns=[release_level],
)
diff --git a/tests/unit/test_create_revision.py b/tests/unit/test_create_revision.py
index 8add4c24..51de095e 100644
--- a/tests/unit/test_create_revision.py
+++ b/tests/unit/test_create_revision.py
@@ -24,7 +24,7 @@ import pytest
import atr.models.safe as safe
import atr.models.sql as sql
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.storage.writers.release as release
import atr.storage.writers.revision as revision
@@ -403,7 +403,7 @@ async def test_modify_failed_error_propagates_and_cleans_up(tmp_path: pathlib.Pa
received_args["path"] = path
received_args["old_rev"] = old_rev
(path / "file.txt").path.write_text("Should be cleaned up.")
- raise types.FailedError("Intentional error")
+ raise datatypes.FailedError("Intentional error")
release = mock.MagicMock()
release.phase = sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT
@@ -420,7 +420,7 @@ async def test_modify_failed_error_propagates_and_cleans_up(tmp_path: pathlib.Pa
mock.patch.object(revision.interaction, "latest_revision", new_callable=mock.AsyncMock, return_value=None),
mock.patch.object(revision.paths, "get_tmp_dir", return_value=temp_dir),
):
- with pytest.raises(types.FailedError, match="Intentional error"):
+ with pytest.raises(datatypes.FailedError, match="Intentional error"):
await participant.create_revision_with_quarantine(
safe.ProjectKey("proj"),
safe.VersionKey("1.0"),
@@ -760,7 +760,7 @@ async def test_verify_provenance_sources_unchanged_rejects_missing_signature(tmp
"source_paths": ["artifact.tar.gz"],
},
)
- with pytest.raises(types.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz\.asc"):
+ with pytest.raises(datatypes.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz\.asc"):
await revision._verify_provenance_sources_unchanged(
temp_dir_path=safe.StatePath(tmp_path),
pre_merge_inodes={
@@ -783,7 +783,7 @@ async def test_verify_provenance_sources_unchanged_rejects_missing_source(tmp_pa
"source_paths": ["artifact.tar.gz"],
},
)
- with pytest.raises(types.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz"):
+ with pytest.raises(datatypes.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz"):
await revision._verify_provenance_sources_unchanged(
temp_dir_path=safe.StatePath(tmp_path),
pre_merge_inodes={"artifact.tar.gz": 123},
@@ -809,7 +809,7 @@ async def test_verify_provenance_sources_unchanged_rejects_replaced_signature(tm
"source_paths": ["artifact.tar.gz"],
},
)
- with pytest.raises(types.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz\.asc"):
+ with pytest.raises(datatypes.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz\.asc"):
await revision._verify_provenance_sources_unchanged(
temp_dir_path=safe.StatePath(tmp_path),
pre_merge_inodes={
@@ -836,7 +836,7 @@ async def test_verify_provenance_sources_unchanged_rejects_replaced_source(tmp_p
"source_paths": ["artifact.tar.gz"],
},
)
- with pytest.raises(types.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz"):
+ with pytest.raises(datatypes.FailedError, match=r"concurrent revision replaced artifact\.tar\.gz"):
await revision._verify_provenance_sources_unchanged(
temp_dir_path=safe.StatePath(tmp_path),
pre_merge_inodes={"artifact.tar.gz": stale_inode},
diff --git a/tests/unit/test_create_revision_quarantine.py b/tests/unit/test_create_revision_quarantine.py
index 2d10caf2..512725a6 100644
--- a/tests/unit/test_create_revision_quarantine.py
+++ b/tests/unit/test_create_revision_quarantine.py
@@ -24,7 +24,7 @@ import pytest
import atr.models.safe as safe
import atr.models.sql as sql
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.storage.writers.revision as revision
_QUARANTINE_TOKEN_ALPHABET: Final[str] = "qpzry9x8gf2tvdw0s3jn54khce6mua7b"
@@ -236,7 +236,7 @@ async def test_phase_gate_rejects_mismatched_phase():
with mock.patch.object(revision.db, "session", return_value=mock_session):
with pytest.raises(
- types.PhaseMismatchError,
+ datatypes.PhaseMismatchError,
match="release phase is release_candidate",
):
await participant.create_revision_with_quarantine(
diff --git a/tests/unit/test_release_writer_hashgen.py b/tests/unit/test_release_writer_hashgen.py
index 90e262f8..5a6ddfb4 100644
--- a/tests/unit/test_release_writer_hashgen.py
+++ b/tests/unit/test_release_writer_hashgen.py
@@ -25,7 +25,7 @@ import pytest
import atr.models.attestable as attestable
import atr.models.safe as safe
import atr.models.sql as sql
-import atr.storage.types as types
+import atr.storage.datatypes as datatypes
import atr.storage.writers.release as release
@@ -155,7 +155,7 @@ async def test_generate_hash_file_requires_signature_file(tmp_path: pathlib.Path
"_signature_provenance_metadata_for",
new=mock.AsyncMock(),
) as signature_metadata_mock:
- with pytest.raises(types.FailedError, match=r"requires a detached OpenPGP signature"):
+ with pytest.raises(datatypes.FailedError, match=r"requires a detached OpenPGP signature"):
await participant.generate_hash_file(
safe.ProjectKey("proj"),
safe.VersionKey("1.0"),
@@ -211,7 +211,7 @@ async def test_signature_provenance_metadata_for_requires_completed_check():
mock.patch.object(release.db, "session", return_value=session),
mock.patch.object(release.log, "info") as log_info_mock,
):
- with pytest.raises(types.FailedError, match=r"has not completed yet"):
+ with pytest.raises(datatypes.FailedError, match=r"has not completed yet"):
await release._signature_provenance_metadata_for(
project_key=safe.ProjectKey("proj"),
version_key=safe.VersionKey("1.0"),
@@ -224,7 +224,7 @@ async def test_signature_provenance_metadata_for_requires_completed_check():
@pytest.mark.asyncio
async def test_signature_provenance_metadata_for_requires_parent_revision():
- with pytest.raises(types.FailedError, match=r"requires a parent revision"):
+ with pytest.raises(datatypes.FailedError, match=r"requires a parent revision"):
await release._signature_provenance_metadata_for(
project_key=safe.ProjectKey("proj"),
version_key=safe.VersionKey("1.0"),
@@ -248,7 +248,7 @@ async def test_signature_provenance_metadata_for_requires_successful_check():
mock.patch.object(release.db, "session", return_value=session),
mock.patch.object(release.log, "info") as log_info_mock,
):
- with pytest.raises(types.FailedError, match=r"failed: No valid signature found"):
+ with pytest.raises(datatypes.FailedError, match=r"failed: No valid signature found"):
await release._signature_provenance_metadata_for(
project_key=safe.ProjectKey("proj"),
version_key=safe.VersionKey("1.0"),
diff --git a/tests/unit/test_source_artifact_check.py b/tests/unit/test_source_artifact_check.py
index 6ba8dd82..ca7a5a66 100644
--- a/tests/unit/test_source_artifact_check.py
+++ b/tests/unit/test_source_artifact_check.py
@@ -25,8 +25,8 @@ import pytest
import atr.models.safe as safe
import atr.models.sql as sql
import atr.render as render
+import atr.storage.datatypes as datatypes
import atr.storage.readers.releases as releases
-import atr.storage.types as types
import atr.tasks.checks as checks
import atr.tasks.checks.paths as paths
from tests.unit.recorders import RecorderStub
@@ -50,9 +50,9 @@ async def test_binary_only_artifacts_records_failure(monkeypatch: pytest.MonkeyP
@pytest.mark.asyncio
async def test_blocker_with_path_goes_to_blockers():
- info = types.PathInfo()
+ info = datatypes.PathInfo()
result = _make_check_result(sql.CheckResultStatus.BLOCKER, "Bad path", primary_rel_path="some-file.tar.gz")
- cs = types.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
+ cs = datatypes.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
reader = _make_reader()
await reader._GeneralPublic__blocker(cs) # type: ignore[attr-defined]
assert len(info.release_level_concerns) == 0
@@ -65,9 +65,9 @@ async def test_blocker_with_path_goes_to_blockers():
@pytest.mark.asyncio
async def test_blocker_without_path_goes_to_release_level_blockers():
- info = types.PathInfo()
+ info = datatypes.PathInfo()
result = _make_check_result(sql.CheckResultStatus.BLOCKER, "No source artifact")
- cs = types.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
+ cs = datatypes.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
reader = _make_reader()
await reader._GeneralPublic__blocker(cs) # type: ignore[attr-defined]
assert len(info.release_level_blockers) == 1
@@ -77,9 +77,9 @@ async def test_blocker_without_path_goes_to_release_level_blockers():
@pytest.mark.asyncio
async def test_concern_without_path_goes_to_release_level_concerns():
- info = types.PathInfo()
+ info = datatypes.PathInfo()
result = _make_check_result(sql.CheckResultStatus.CONCERN, "Some failure")
- cs = types.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
+ cs = datatypes.ChecksSubset(checks=[result], info=info, match_ignore=lambda _: False)
reader = _make_reader()
await reader._GeneralPublic__concerns(cs) # type: ignore[attr-defined]
assert len(info.release_level_concerns) == 1
@@ -235,8 +235,8 @@ async def test_no_artifacts_records_failure(monkeypatch: pytest.MonkeyPatch, tmp
def test_render_checks_summary_emits_new_badge_classes():
- info = types.PathInfo()
- stat = types.CheckerStats(
+ info = datatypes.PathInfo()
+ stat = datatypes.CheckerStats(
checker="atr.tasks.checks.paths.check_errors",
counts=collections.Counter(
{
@@ -255,13 +255,13 @@ def test_render_checks_summary_emits_new_badge_classes():
def test_render_checks_summary_returns_none_when_no_errors():
- info = types.PathInfo()
+ info = datatypes.PathInfo()
element = render.render_checks_summary(info, safe.ProjectKey("test"), safe.VersionKey("1.0"))
assert element is None
def test_render_checks_summary_shows_release_level_errors():
- info = types.PathInfo()
+ info = datatypes.PathInfo()
result = _make_check_result(
sql.CheckResultStatus.CONCERN,
"Some path issue",
diff --git a/tests/unit/test_svn_publish_writer.py b/tests/unit/test_svn_publish_writer.py
index a615737f..8595e075 100644
--- a/tests/unit/test_svn_publish_writer.py
+++ b/tests/unit/test_svn_publish_writer.py
@@ -33,7 +33,7 @@ import atr.models.results as results
import atr.models.safe as safe
import atr.models.sql as sql
import atr.storage as storage
-import atr.storage.types as storage_types
+import atr.storage.datatypes as datatypes
import atr.storage.writers.release as release_writer
INTERNAL_PUBLISH_URL: Final[str] = "https://internal.example.invalid/repos/dist/atr"
@@ -129,7 +129,7 @@ async def test_publish_to_svn_execute_maps_existing_svn_path(
await _seed_preview_release(data)
writer = _release_writer(data)
- with pytest.raises(storage_types.FailedError, match="Release file already exists in SVN"):
+ with pytest.raises(datatypes.FailedError, match="Release file already exists in SVN"):
await writer.publish_to_svn_execute(
args.SvnPublish(
asf_uid="alice",
diff --git a/tests/unit/test_tokens_safe.py b/tests/unit/test_tokens_safe.py
index f6a3005e..cd3c4aa3 100644
--- a/tests/unit/test_tokens_safe.py
+++ b/tests/unit/test_tokens_safe.py
@@ -26,8 +26,8 @@ import atr.constants as constants
import atr.db as db
import atr.jwtoken as jwtoken
import atr.models.sql as sql
+import atr.storage.datatypes as datatypes
import atr.storage.readers.tokens
-import atr.storage.types as types
import atr.storage.writers.tokens
@@ -161,7 +161,7 @@ def test_personal_access_token_safe_excludes_token_hash() -> None:
last_used=None,
)
- safe = types.PersonalAccessTokenSafe.from_sql(token)
+ safe = datatypes.PersonalAccessTokenSafe.from_sql(token)
assert safe.id == 5
assert safe.asfuid == "test"
@@ -192,9 +192,9 @@ async def test_reader_pat_methods_return_safe_tokens() -> None:
most_recent = await reader.most_recent_jwt_pat()
assert len(tokens_list) == 1
- assert isinstance(tokens_list[0], types.PersonalAccessTokenSafe)
+ assert isinstance(tokens_list[0], datatypes.PersonalAccessTokenSafe)
assert "token_hash" not in tokens_list[0].model_dump()
- assert isinstance(most_recent, types.PersonalAccessTokenSafe)
+ assert isinstance(most_recent, datatypes.PersonalAccessTokenSafe)
assert most_recent is not None
assert "token_hash" not in most_recent.model_dump()
@@ -253,7 +253,7 @@ async def test_writer_add_token_returns_safe_token() -> None:
label="writer-test",
)
- assert isinstance(safe, types.PersonalAccessTokenSafe)
+ assert isinstance(safe, datatypes.PersonalAccessTokenSafe)
assert safe.id == 101
assert "token_hash" not in safe.model_dump()
@@ -296,7 +296,7 @@ async def test_writer_add_system_token() -> None:
allowed_ip="10.0.0.0/8",
)
- assert isinstance(safe, types.PersonalAccessTokenSafe)
+ assert isinstance(safe, datatypes.PersonalAccessTokenSafe)
assert safe.id == 101
# System PATs have no owning user; created_by records the minting admin.
assert safe.asfuid is None
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org