This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new b17cc7f Allow configurations without LDAP credentials to do more
b17cc7f is described below
commit b17cc7fe6b499b8fe1ba145e9840988540428e58
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Oct 24 14:50:12 2025 +0100
Allow configurations without LDAP credentials to do more
---
atr/blueprints/admin/admin.py | 8 +++++---
atr/blueprints/api/api.py | 22 +++++++++++++++++++++-
atr/docs/storage-interface.html | 4 ++--
atr/docs/storage-interface.md | 4 ++--
atr/models/api.py | 8 ++++++++
atr/principal.py | 26 ++++++++++++++++++--------
atr/routes/announce.py | 2 +-
atr/routes/compose.py | 3 +--
atr/routes/draft.py | 12 ++++++------
atr/routes/finish.py | 6 +++---
atr/routes/keys.py | 2 +-
atr/routes/preview.py | 2 +-
atr/routes/projects.py | 17 ++++++++---------
atr/routes/revisions.py | 2 +-
atr/routes/start.py | 2 +-
atr/routes/upload.py | 2 +-
atr/routes/vote.py | 3 +--
atr/routes/voting.py | 2 +-
atr/storage/__init__.py | 29 +++++++++++------------------
19 files changed, 93 insertions(+), 63 deletions(-)
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index 46b2fcb..e1ffa68 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -44,6 +44,7 @@ import atr.ldap as ldap
import atr.log as log
import atr.models.sql as sql
import atr.principal as principal
+import atr.route as route
import atr.routes.mapping as mapping
import atr.storage as storage
import atr.storage.outcome as outcome
@@ -370,7 +371,8 @@ async def admin_delete_release() -> str | response.Response:
await quart.flash("No releases selected for deletion.",
"warning")
return
quart.redirect(quart.url_for("admin.admin_delete_release"))
- await _delete_releases(releases_to_delete, asf_uid)
+ committer_session = route.CommitterSession(web_session)
+ await _delete_releases(committer_session, releases_to_delete)
# Redirecting back to the deletion page will refresh the list of
releases too
return quart.redirect(quart.url_for("admin.admin_delete_release"))
@@ -787,7 +789,7 @@ async def _check_keys(fix: bool = False) -> str:
return message
-async def _delete_releases(releases_to_delete: list[str], asf_uid: str) ->
None:
+async def _delete_releases(session: route.CommitterSession,
releases_to_delete: list[str]) -> None:
success_count = 0
fail_count = 0
error_messages = []
@@ -800,7 +802,7 @@ async def _delete_releases(releases_to_delete: list[str],
asf_uid: str) -> None:
)
if release.committee is None:
raise RuntimeError(f"Release {release_name} has no
committee")
- async with storage.write(asf_uid) as write:
+ async with storage.write(session) as write:
wafa = write.as_foundation_admin(release.committee.name)
await wafa.release.delete(release.project.name,
release.version)
success_count += 1
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 83f3689..ca371f6 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -36,6 +36,7 @@ import atr.db.interaction as interaction
import atr.jwtoken as jwtoken
import atr.models as models
import atr.models.sql as sql
+import atr.principal as principal
import atr.storage as storage
import atr.storage.outcome as outcome
import atr.storage.types as types
@@ -1094,6 +1095,25 @@ async def tasks_list(query_args:
models.api.TasksListQuery) -> DictResponse:
).model_dump(), 200
[email protected]("/user/info")
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_response(models.api.UserInfoResults, 200)
+async def user_info() -> DictResponse:
+ """
+ Get information about a user.
+ """
+ asf_uid = _jwt_asf_uid()
+ authorisation = await principal.Authorisation(asf_uid)
+ participant_of = authorisation.participant_of()
+ member_of = authorisation.member_of()
+ return models.api.UserInfoResults(
+ endpoint="/user/info",
+ participant_of=list(participant_of),
+ member_of=list(member_of),
+ ).model_dump(), 200
+
+
@api.BLUEPRINT.route("/users/list")
@quart_schema.validate_response(models.api.UsersListResults, 200)
async def users_list() -> DictResponse:
@@ -1265,7 +1285,7 @@ def _jwt_asf_uid() -> str:
claims = getattr(quart.g, "jwt_claims", {})
asf_uid = claims.get("sub")
if not isinstance(asf_uid, str):
- raise base.ASFQuartException("Invalid token subject", errorcode=401)
+ raise base.ASFQuartException(f"Invalid token subject: {asf_uid!r},
type: {type(asf_uid)}", errorcode=401)
return asf_uid
diff --git a/atr/docs/storage-interface.html b/atr/docs/storage-interface.html
index 98818c2..26792a5 100644
--- a/atr/docs/storage-interface.html
+++ b/atr/docs/storage-interface.html
@@ -19,12 +19,12 @@
<p>Reading from storage is a work in progress. There are some existing
methods, but most of the functionality is currently in <code>db</code> or
<code>db.interaction</code>, and much work is required to migrate this to the
storage interface. We have given this less priority because reads are generally
safe, with the exception of a few components such as user tokens, which should
be given greater migration priority.</p>
<h2 id="how-do-we-write-to-storage">How do we write to storage?</h2>
<p>To write to storage we open a write session, request specific permissions,
use the exposed functionality, and then handle the outcome. Here is an actual
example from <a
href="/ref/atr/routes/start.py"><code>routes/start.py</code></a>:</p>
-<pre><code class="language-python">async with storage.write(session.uid) as
write:
+<pre><code class="language-python">async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
new_release, _project = await wacp.release.start(project_name, version)
</code></pre>
<p>The <code>wacp</code> object, short for <code>w</code>rite <code>a</code>s
<code>c</code>ommittee <code>p</code>articipant, provides access to
domain-specific writers: <code>announce</code>, <code>checks</code>,
<code>distributions</code>, <code>keys</code>, <code>policy</code>,
<code>project</code>, <code>release</code>, <code>sbom</code>,
<code>ssh</code>, <code>tokens</code>, and <code>vote</code>.</p>
-<p>The write session takes an optional ASF UID, typically
<code>session.uid</code> from the logged-in user. If you omit the UID, the
session determines it automatically from the current request context. The write
object checks LDAP memberships and raises <a
href="/ref/atr/storage/__init__.py:AccessError"><code>storage.AccessError</code></a>
if the user is not authorized for the requested permission level.</p>
+<p>The write session takes an optional <a
href="/ref/atr/route.py:CommitterSession"><code>CommitterSession</code></a> or
ASF UID, typically <code>session.uid</code> from the logged-in user. If you
omit the UID, the session determines it automatically from the current request
context. The write object checks LDAP memberships and raises <a
href="/ref/atr/storage/__init__.py:AccessError"><code>storage.AccessError</code></a>
if the user is not authorized for the requested permission level.</p>
<p>Because projects belong to committees, we provide <a
href="/ref/atr/storage/__init__.py:as_project_committee_member"><code>write.as_project_committee_member(project_name)</code></a>
and <a
href="/ref/atr/storage/__init__.py:as_project_committee_participant"><code>write.as_project_committee_participant(project_name)</code></a>,
which look up the project's committee and authenticate the user as a member or
participant of that committee. This is convenient when, for example, the URL
prov [...]
<p>Here is a more complete example from <a
href="/ref/atr/blueprints/api/api.py"><code>blueprints/api/api.py</code></a>
that shows the classic three step pattern:</p>
<pre><code class="language-python">async with storage.write(asf_uid) as write:
diff --git a/atr/docs/storage-interface.md b/atr/docs/storage-interface.md
index fe7fad4..d2def41 100644
--- a/atr/docs/storage-interface.md
+++ b/atr/docs/storage-interface.md
@@ -32,14 +32,14 @@ Reading from storage is a work in progress. There are some
existing methods, but
To write to storage we open a write session, request specific permissions, use
the exposed functionality, and then handle the outcome. Here is an actual
example from [`routes/start.py`](/ref/atr/routes/start.py):
```python
-async with storage.write(session.uid) as write:
+async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
new_release, _project = await wacp.release.start(project_name, version)
```
The `wacp` object, short for `w`rite `a`s `c`ommittee `p`articipant, provides
access to domain-specific writers: `announce`, `checks`, `distributions`,
`keys`, `policy`, `project`, `release`, `sbom`, `ssh`, `tokens`, and `vote`.
-The write session takes an optional ASF UID, typically `session.uid` from the
logged-in user. If you omit the UID, the session determines it automatically
from the current request context. The write object checks LDAP memberships and
raises [`storage.AccessError`](/ref/atr/storage/__init__.py:AccessError) if the
user is not authorized for the requested permission level.
+The write session takes an optional
[`CommitterSession`](/ref/atr/route.py:CommitterSession) or ASF UID, typically
`session.uid` from the logged-in user. If you omit the UID, the session
determines it automatically from the current request context. The write object
checks LDAP memberships and raises
[`storage.AccessError`](/ref/atr/storage/__init__.py:AccessError) if the user
is not authorized for the requested permission level.
Because projects belong to committees, we provide
[`write.as_project_committee_member(project_name)`](/ref/atr/storage/__init__.py:as_project_committee_member)
and
[`write.as_project_committee_participant(project_name)`](/ref/atr/storage/__init__.py:as_project_committee_participant),
which look up the project's committee and authenticate the user as a member or
participant of that committee. This is convenient when, for example, the URL
provides a project name.
diff --git a/atr/models/api.py b/atr/models/api.py
index 23d4dab..716fbce 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -453,6 +453,12 @@ class TasksListResults(schema.Strict):
count: int = schema.example(10)
+class UserInfoResults(schema.Strict):
+ endpoint: Literal["/user/info"] = schema.alias("endpoint")
+ participant_of: list[str] = schema.example(["committee_name_a",
"committee_name_b"])
+ member_of: list[str] = schema.example(["committee_name_a"])
+
+
class UsersListResults(schema.Strict):
endpoint: Literal["/users/list"] = schema.alias("endpoint")
users: Sequence[str] = schema.example(["user1", "user2"])
@@ -534,6 +540,7 @@ type Results = Annotated[
| SshKeyDeleteResults
| SshKeysListResults
| TasksListResults
+ | UserInfoResults
| UsersListResults
| VoteResolveResults
| VoteStartResults
@@ -591,6 +598,7 @@ validate_ssh_key_add = validator(SshKeyAddResults)
validate_ssh_key_delete = validator(SshKeyDeleteResults)
validate_ssh_keys_list = validator(SshKeysListResults)
validate_tasks_list = validator(TasksListResults)
+validate_user_info = validator(UserInfoResults)
validate_users_list = validator(UsersListResults)
validate_vote_resolve = validator(VoteResolveResults)
validate_vote_start = validator(VoteStartResults)
diff --git a/atr/principal.py b/atr/principal.py
index ec3f75a..dd70885 100644
--- a/atr/principal.py
+++ b/atr/principal.py
@@ -27,6 +27,7 @@ import asfquart.session as session
import atr.config as config
import atr.ldap as ldap
import atr.log as log
+import atr.route as route
LDAP_CHAIRS_BASE = "cn=pmc-chairs,ou=groups,ou=services,dc=apache,dc=org"
LDAP_DN = "uid=%s,ou=people,dc=apache,dc=org"
@@ -51,6 +52,15 @@ class CommitterError(Exception):
self.origin = origin
+class ArgumentNoneType:
+ pass
+
+
+ArgumentNone = ArgumentNoneType()
+
+type UID = route.CommitterSession | str | None | ArgumentNoneType
+
+
def attr_to_list(attr):
"""Converts a list of bytestring attribute values to a unique list of
strings."""
return list(set([value for value in attr or []]))
@@ -325,15 +335,15 @@ class AsyncObject:
class Authorisation(AsyncObject):
- class __ArgumentNoneType:
- pass
-
- __ArgumentNone = __ArgumentNoneType()
-
- async def __init__(self, asf_uid: str | None | __ArgumentNoneType =
__ArgumentNone):
+ async def __init__(self, asf_uid: UID = ArgumentNone):
match asf_uid:
- case Authorisation.__ArgumentNone:
- asfquart_session = await session.read()
+ case ArgumentNoneType() | route.CommitterSession():
+ match asf_uid:
+ case route.CommitterSession():
+ asfquart_session = asf_uid._session
+ case _:
+ asfquart_session = await session.read()
+ # asfquart_session = await session.read()
if asfquart_session is None:
raise AuthenticationError("No ASFQuart session found")
self.__authoriser = authoriser_asfquart
diff --git a/atr/routes/announce.py b/atr/routes/announce.py
index d15909a..0fa8f8d 100644
--- a/atr/routes/announce.py
+++ b/atr/routes/announce.py
@@ -141,7 +141,7 @@ async def selected_post(
download_path_suffix = _download_path_suffix_validated(announce_form)
try:
- async with storage.write_as_project_committee_member(project_name,
session.uid) as wacm:
+ async with storage.write_as_project_committee_member(project_name,
session) as wacm:
await wacm.announce.release(
project_name,
version_name,
diff --git a/atr/routes/compose.py b/atr/routes/compose.py
index 88803b3..3fc4d77 100644
--- a/atr/routes/compose.py
+++ b/atr/routes/compose.py
@@ -55,8 +55,7 @@ async def check(
paths = [path async for path in util.paths_recursive(base_path)]
paths.sort()
- asf_uid = session.uid if (session is not None) else None
- async with storage.read(asf_uid) as read:
+ async with storage.read(session) as read:
ragp = read.as_general_public()
info = await ragp.releases.path_info(release, paths)
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index dcac593..265156d 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -97,7 +97,7 @@ async def delete(session: route.CommitterSession) ->
response.Response:
await session.check_access(project_name)
# Delete the metadata from the database
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
await wacp.release.delete(
project_name, version_name,
phase=sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT, include_downloads=False
@@ -132,7 +132,7 @@ async def delete_file(session: route.CommitterSession,
project_name: str, versio
rel_path_to_delete = pathlib.Path(str(form.file_path.data))
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
metadata_files_deleted = await
wacp.release.delete_file(project_name, version_name, rel_path_to_delete)
except Exception as e:
@@ -162,7 +162,7 @@ async def fresh(session: route.CommitterSession,
project_name: str, version_name
# This doesn't make sense unless the checks themselves have been updated
# Therefore we only show the button for this to admins
description = "Empty revision to restart all checks for the whole release
candidate draft"
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
async with wacp.revision.create_and_manage(
project_name, version_name, session.uid, description=description
@@ -195,7 +195,7 @@ async def hashgen(
rel_path = pathlib.Path(file_path)
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
await wacp.release.generate_hash_file(project_name, version_name,
rel_path, hash_type)
@@ -230,7 +230,7 @@ async def sbomgen(
try:
description = "SBOM generation through web interface"
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
async with wacp.revision.create_and_manage(
project_name, version_name, session.uid,
description=description
@@ -288,7 +288,7 @@ async def svnload(session: route.CommitterSession,
project_name: str, version_na
)
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
await wacp.release.import_from_svn(
project_name,
diff --git a/atr/routes/finish.py b/atr/routes/finish.py
index 295ad9a..2388044 100644
--- a/atr/routes/finish.py
+++ b/atr/routes/finish.py
@@ -251,7 +251,7 @@ async def _delete_empty_directory(
respond: Respond,
) -> tuple[quart_response.Response, int] | response.Response:
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_member(project_name)
creation_error = await
wacp.release.delete_empty_directory(project_name, version_name,
dir_to_delete_rel)
except Exception:
@@ -272,7 +272,7 @@ async def _move_file_to_revision(
respond: Respond,
) -> tuple[quart_response.Response, int] | response.Response:
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_member(project_name)
creation_error, moved_files_names, skipped_files_names = await
wacp.release.move_file(
project_name, version_name, source_files_rel, target_dir_rel
@@ -313,7 +313,7 @@ async def _remove_rc_tags(
respond: Respond,
) -> tuple[quart_response.Response, int] | response.Response:
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_member(project_name)
creation_error, renamed_count, error_messages = await
wacp.release.remove_rc_tags(
project_name, version_name
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index 31770c4..9bafb90 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -359,7 +359,7 @@ async def ssh_add(session: route.CommitterSession) ->
response.Response | str:
if await form.validate_on_submit():
key: str = util.unwrap(form.key.data)
try:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wafc = write.as_foundation_committer()
fingerprint = await wafc.ssh.add_key(key, session.uid)
except util.SshFingerprintError as e:
diff --git a/atr/routes/preview.py b/atr/routes/preview.py
index 0d9c156..87889e7 100644
--- a/atr/routes/preview.py
+++ b/atr/routes/preview.py
@@ -101,7 +101,7 @@ async def delete(session: route.CommitterSession) ->
response.Response:
return await session.redirect(root.index, error="Missing required
parameters")
# Check that the user has access to the project
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
await wacp.release.delete(
project_name, version_name,
phase=sql.ReleasePhase.RELEASE_PREVIEW, include_downloads=False
diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index 165e418..e3e662c 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -250,7 +250,7 @@ You must start with your committee label, and you must use
lower case.
"""
if await form.validate_on_submit():
- return await _project_add(form, session.uid)
+ return await _project_add(form, session)
return await template.render("project-add-project.html", form=form,
committee_name=committee.display_name)
@@ -265,7 +265,7 @@ async def delete(session: route.CommitterSession) ->
response.Response:
if not project_name:
return await session.redirect(projects, error="Missing project name
for deletion.")
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacm = await write.as_project_committee_member(project_name)
try:
await wacm.project.delete(project_name)
@@ -328,16 +328,15 @@ async def view(session: route.CommitterSession, name:
str) -> response.Response
if can_edit and (quart.request.method == "POST"):
form_data = await quart.request.form
- asf_uid = util.unwrap(session.uid)
if "submit_metadata" in form_data:
- edited_metadata, metadata_form = await _metadata_edit(asf_uid,
project, form_data)
+ edited_metadata, metadata_form = await _metadata_edit(session,
project, form_data)
if edited_metadata is True:
return quart.redirect(util.as_url(view, name=project.name))
elif "submit_policy" in form_data:
policy_form = await ReleasePolicyForm.create_form(data=form_data)
if await policy_form.validate_on_submit():
policy_data =
policy.ReleasePolicyData.model_validate(policy_form.data)
- async with storage.write(asf_uid) as write:
+ async with storage.write(session) as write:
wacm = await
write.as_project_committee_member(project.name)
try:
await wacm.policy.edit(project, policy_data)
@@ -407,7 +406,7 @@ async def _metadata_category_remove(
async def _metadata_edit(
- asf_uid: str, project: sql.Project, form_data: dict[str, str]
+ session: route.CommitterSession, project: sql.Project, form_data:
dict[str, str]
) -> tuple[bool, ProjectMetadataForm]:
metadata_form = await ProjectMetadataForm.create_form(data=form_data)
@@ -429,7 +428,7 @@ async def _metadata_edit(
category_to_add = metadata_form.category_to_add.data
language_to_add = metadata_form.language_to_add.data
- async with storage.write(asf_uid) as write:
+ async with storage.write(session) as write:
wacm = await write.as_project_committee_member(project.name)
if (action_type == "add_category") and category_to_add:
@@ -508,13 +507,13 @@ async def _policy_form_create(project: sql.Project) ->
ReleasePolicyForm:
return policy_form
-async def _project_add(form: AddForm, asf_id: str) -> response.Response:
+async def _project_add(form: AddForm, session: route.CommitterSession) ->
response.Response:
form_values = await _project_add_validate(form)
if form_values is None:
return quart.redirect(util.as_url(add_project,
committee_name=form.committee_name.data))
committee_name, display_name, label = form_values
- async with storage.write(asf_id) as write:
+ async with storage.write(session) as write:
wacm = await write.as_project_committee_member(committee_name)
try:
await wacm.project.create(committee_name, display_name, label)
diff --git a/atr/routes/revisions.py b/atr/routes/revisions.py
index 0a23405..872026c 100644
--- a/atr/routes/revisions.py
+++ b/atr/routes/revisions.py
@@ -121,7 +121,7 @@ async def selected_post(session: route.CommitterSession,
project_name: str, vers
)
description = f"Copy of revision {selected_revision_number} through web
interface"
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
async with wacp.revision.create_and_manage(
project_name, version_name, session.uid, description=description
diff --git a/atr/routes/start.py b/atr/routes/start.py
index 8c9b080..99ff31a 100644
--- a/atr/routes/start.py
+++ b/atr/routes/start.py
@@ -61,7 +61,7 @@ async def selected(session: route.CommitterSession,
project_name: str) -> respon
project_name = str(form.project_name.data)
version = str(form.version_name.data)
# We already have the project, so we only need to get the new
release
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await
write.as_project_committee_participant(project_name)
new_release, _project = await wacp.release.start(project_name,
version)
# Redirect to the new draft's overview page on success
diff --git a/atr/routes/upload.py b/atr/routes/upload.py
index d2010bf..65785e5 100644
--- a/atr/routes/upload.py
+++ b/atr/routes/upload.py
@@ -74,7 +74,7 @@ async def selected(session: route.CommitterSession,
project_name: str, version_n
file_name = pathlib.Path(form.file_name.data)
file_data = form.file_data.data
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await
write.as_project_committee_participant(project_name)
number_of_files = await
wacp.release.upload_files(project_name, version_name, file_name, file_data)
return await session.redirect(
diff --git a/atr/routes/vote.py b/atr/routes/vote.py
index d4999b2..29b9fd1 100644
--- a/atr/routes/vote.py
+++ b/atr/routes/vote.py
@@ -89,8 +89,7 @@ async def selected(
# Move task_mid_get here?
task_mid = interaction.task_mid_get(latest_vote_task)
- asf_uid = session.uid if (session is not None) else None
- async with storage.write(asf_uid) as write:
+ async with storage.write(session) as write:
wagp = write.as_general_public()
archive_url = await wagp.cache.get_message_archive_url(task_mid)
diff --git a/atr/routes/voting.py b/atr/routes/voting.py
index b5c94c1..5a30456 100644
--- a/atr/routes/voting.py
+++ b/atr/routes/voting.py
@@ -117,7 +117,7 @@ async def start_vote_manual(
session: route.CommitterSession,
data: db.Session,
) -> response.Response | str:
- async with storage.write(session.uid) as write:
+ async with storage.write(session) as write:
wacp = await
write.as_project_committee_participant(release.project_name)
# This verifies the state and sets the phase to RELEASE_CANDIDATE
error = await wacp.release.promote_to_candidate(release.name,
selected_revision_number, vote_manual=True)
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index 39e6f49..cd77e89 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -397,16 +397,9 @@ class Write:
# Context managers
-class ArgumentNoneType:
- pass
-
-
-ArgumentNone = ArgumentNoneType()
-
-
@contextlib.asynccontextmanager
-async def read(asf_uid: str | None | ArgumentNoneType = ArgumentNone) ->
AsyncGenerator[Read]:
- if asf_uid is ArgumentNone:
+async def read(asf_uid: principal.UID = principal.ArgumentNone) ->
AsyncGenerator[Read]:
+ if asf_uid is principal.ArgumentNone:
authorisation = await principal.Authorisation()
else:
authorisation = await principal.Authorisation(asf_uid)
@@ -417,7 +410,7 @@ async def read(asf_uid: str | None | ArgumentNoneType =
ArgumentNone) -> AsyncGe
@contextlib.asynccontextmanager
async def read_as_foundation_committer(
- asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+ asf_uid: principal.UID = principal.ArgumentNone,
) -> AsyncGenerator[ReadAsFoundationCommitter]:
async with read(asf_uid) as r:
yield r.as_foundation_committer()
@@ -425,15 +418,15 @@ async def read_as_foundation_committer(
@contextlib.asynccontextmanager
async def read_as_general_public(
- asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+ asf_uid: principal.UID = principal.ArgumentNone,
) -> AsyncGenerator[ReadAsGeneralPublic]:
async with read(asf_uid) as r:
yield r.as_general_public()
@contextlib.asynccontextmanager
-async def read_and_write(asf_uid: str | None | ArgumentNoneType =
ArgumentNone) -> AsyncGenerator[tuple[Read, Write]]:
- if asf_uid is ArgumentNone:
+async def read_and_write(asf_uid: principal.UID = principal.ArgumentNone) ->
AsyncGenerator[tuple[Read, Write]]:
+ if asf_uid is principal.ArgumentNone:
authorisation = await principal.Authorisation()
else:
authorisation = await principal.Authorisation(asf_uid)
@@ -445,8 +438,8 @@ async def read_and_write(asf_uid: str | None |
ArgumentNoneType = ArgumentNone)
@contextlib.asynccontextmanager
-async def write(asf_uid: str | None | ArgumentNoneType = ArgumentNone) ->
AsyncGenerator[Write]:
- if asf_uid is ArgumentNone:
+async def write(asf_uid: principal.UID = principal.ArgumentNone) ->
AsyncGenerator[Write]:
+ if asf_uid is principal.ArgumentNone:
authorisation = await principal.Authorisation()
else:
authorisation = await principal.Authorisation(asf_uid)
@@ -458,7 +451,7 @@ async def write(asf_uid: str | None | ArgumentNoneType =
ArgumentNone) -> AsyncG
@contextlib.asynccontextmanager
async def write_as_committee_member(
committee_name: str,
- asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+ asf_uid: principal.UID = principal.ArgumentNone,
) -> AsyncGenerator[WriteAsCommitteeMember]:
async with write(asf_uid) as w:
yield w.as_committee_member(committee_name)
@@ -467,7 +460,7 @@ async def write_as_committee_member(
@contextlib.asynccontextmanager
async def write_as_committee_participant(
committee_name: str,
- asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+ asf_uid: principal.UID = principal.ArgumentNone,
) -> AsyncGenerator[WriteAsCommitteeParticipant]:
async with write(asf_uid) as w:
yield w.as_committee_participant(committee_name)
@@ -476,7 +469,7 @@ async def write_as_committee_participant(
@contextlib.asynccontextmanager
async def write_as_project_committee_member(
project_name: str,
- asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+ asf_uid: principal.UID = principal.ArgumentNone,
) -> AsyncGenerator[WriteAsCommitteeMember]:
async with write(asf_uid) as w:
yield await w.as_project_committee_member(project_name)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]