Skip to content

Commit 25ffd79

Browse files
committed
fix(interfaces): add TypedDicts for dict return types; add SnapshotStorage protocol
1 parent 77b0c72 commit 25ffd79

2 files changed

Lines changed: 83 additions & 15 deletions

File tree

src/ghdcbot/core/interfaces.py

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections.abc import Iterable, Sequence
44
from datetime import datetime
5-
from typing import Literal, Protocol
5+
from typing import Literal, Protocol, TypedDict
66

77
from ghdcbot.config.models import IdentityMapping
88
from ghdcbot.core.models import (
@@ -14,6 +14,60 @@
1414
)
1515

1616

17+
class IdentityLinkDict(TypedDict, total=False):
18+
discord_user_id: str
19+
github_user: str
20+
verified: int
21+
verification_code: str | None
22+
expires_at: str | None
23+
created_at: str
24+
verified_at: str | None
25+
unlinked_at: str | None
26+
27+
28+
class UnlinkResultDict(TypedDict):
29+
discord_user_id: str
30+
github_user: str
31+
verified_at: str
32+
unlinked_at: str
33+
34+
35+
class IdentityStatusDict(TypedDict):
36+
github_user: str | None
37+
status: Literal["verified", "verified_stale", "pending", "not_linked"]
38+
verified_at: str | None
39+
is_stale: bool
40+
41+
42+
class IssueRequestDict(TypedDict):
43+
request_id: str
44+
discord_user_id: str
45+
github_user: str
46+
owner: str
47+
repo: str
48+
issue_number: int
49+
issue_url: str
50+
created_at: str
51+
status: str
52+
53+
54+
class AuditEventDict(TypedDict, total=False):
55+
event_type: str
56+
timestamp: str
57+
context: dict
58+
59+
60+
class NotificationRecordDict(TypedDict):
61+
dedupe_key: str
62+
event_type: str
63+
github_user: str
64+
discord_user_id: str
65+
repo: str
66+
target: str | None
67+
channel_id: str | None
68+
sent_at: str
69+
70+
1771
class GitHubReader(Protocol):
1872
def list_contributions(self, since: datetime) -> Iterable[ContributionEvent]:
1973
"""Yield contributions since the given timestamp."""
@@ -90,24 +144,30 @@ def create_identity_claim(
90144
) -> None:
91145
"""Create or refresh a pending identity claim for (discord_user_id, github_user)."""
92146

93-
def get_identity_link(self, discord_user_id: str, github_user: str) -> dict | None:
147+
def get_identity_link(
148+
self, discord_user_id: str, github_user: str
149+
) -> IdentityLinkDict | None:
94150
"""Return identity link row for (discord_user_id, github_user), or None."""
95151

96152
def mark_identity_verified(self, discord_user_id: str, github_user: str) -> None:
97153
"""Mark an identity claim as verified."""
98154

99-
def unlink_identity(self, discord_user_id: str, cooldown_hours: int) -> dict | None:
155+
def unlink_identity(
156+
self, discord_user_id: str, cooldown_hours: int
157+
) -> UnlinkResultDict | None:
100158
"""Unlink the verified identity for a Discord user. Returns unlink info or None."""
101159

102160
def list_verified_identity_mappings(self) -> list[IdentityMapping]:
103161
"""Return all verified identity mappings."""
104162

105-
def get_identity_links_for_discord_user(self, discord_user_id: str) -> list[dict]:
163+
def get_identity_links_for_discord_user(
164+
self, discord_user_id: str
165+
) -> list[IdentityLinkDict]:
106166
"""Return all identity link rows for a Discord user (verified and pending)."""
107167

108168
def get_identity_status(
109169
self, discord_user_id: str, max_age_days: int | None = None
110-
) -> dict:
170+
) -> IdentityStatusDict:
111171
"""Return current identity status dict for a Discord user."""
112172

113173
# Issue requests
@@ -124,10 +184,10 @@ def insert_issue_request(
124184
) -> None:
125185
"""Store a new issue assignment request with status pending."""
126186

127-
def list_pending_issue_requests(self) -> list[dict]:
187+
def list_pending_issue_requests(self) -> list[IssueRequestDict]:
128188
"""Return all pending issue requests ordered by created_at ascending."""
129189

130-
def get_issue_request(self, request_id: str) -> dict | None:
190+
def get_issue_request(self, request_id: str) -> IssueRequestDict | None:
131191
"""Return a single issue request by request_id, or None."""
132192

133193
def update_issue_request_status(
@@ -139,10 +199,10 @@ def update_issue_request_status(
139199

140200
# Audit log
141201

142-
def append_audit_event(self, event: dict) -> None:
202+
def append_audit_event(self, event: AuditEventDict) -> None:
143203
"""Append an audit event (append-only)."""
144204

145-
def list_audit_events(self) -> list[dict]:
205+
def list_audit_events(self) -> list[AuditEventDict]:
146206
"""Return all audit events."""
147207

148208
# Notifications
@@ -160,7 +220,7 @@ def mark_notification_sent(
160220
) -> None:
161221
"""Record that a notification was sent (deduplication tracking)."""
162222

163-
def list_recent_notifications(self, limit: int = 1000) -> list[dict]:
223+
def list_recent_notifications(self, limit: int = 1000) -> list[NotificationRecordDict]:
164224
"""Return recent sent notifications ordered by sent_at descending."""
165225

166226

src/ghdcbot/engine/snapshots.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,28 @@
1818
import uuid
1919
from datetime import datetime, timezone
2020
from pathlib import Path
21-
from typing import Any
21+
from typing import Any, Protocol
2222

2323
from ghdcbot.config.models import BotConfig, IdentityMapping
24-
from ghdcbot.core.interfaces import Storage
24+
from ghdcbot.core.interfaces import AuditEventDict, IssueRequestDict, NotificationRecordDict
2525
from ghdcbot.core.models import ContributionEvent, ContributionSummary, Score
2626

27+
28+
class SnapshotStorage(Protocol):
29+
"""Narrow protocol for the storage methods used by the snapshot writer."""
30+
31+
def append_audit_event(self, event: AuditEventDict) -> None: ...
32+
def list_pending_issue_requests(self) -> list[IssueRequestDict]: ...
33+
def list_recent_notifications(self, limit: int = 1000) -> list[NotificationRecordDict]: ...
34+
2735
logger = logging.getLogger("Snapshots")
2836

2937
# Snapshot schema version (increment when breaking changes)
3038
SCHEMA_VERSION = "1.0.0"
3139

3240

3341
def write_snapshots_to_github(
34-
storage: Storage,
42+
storage: SnapshotStorage,
3543
config: BotConfig,
3644
github_writer: Any,
3745
identity_mappings: list[IdentityMapping],
@@ -84,7 +92,7 @@ def write_snapshots_to_github(
8492

8593

8694
def _write_snapshots(
87-
storage: Storage,
95+
storage: SnapshotStorage,
8896
config: BotConfig,
8997
github_writer: Any,
9098
snapshot_config: Any,
@@ -153,7 +161,7 @@ def _write_snapshots(
153161

154162

155163
def _collect_snapshot_data(
156-
storage: Storage,
164+
storage: SnapshotStorage,
157165
config: BotConfig,
158166
identity_mappings: list[IdentityMapping],
159167
scores: list[Score],

0 commit comments

Comments
 (0)