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