11import logging
22from datetime import datetime
3- from typing import Any , Literal , TypedDict
3+ from typing import Any , Literal
44
55from django .contrib .auth .models import User
66from django .contrib .postgres .aggregates import ArrayAgg
1616)
1717from django .db .models .functions import Cast , Coalesce , Concat , JSONObject , Replace
1818from pghistory .models import EventQuerySet
19+ from pydantic import BaseModel
1920
2021from shared .models import (
2122 CVEDerivationClusterProposalStatusEvent , # type: ignore
2526logger = logging .getLogger (__name__ )
2627
2728
28- class ChangeEvent (TypedDict ):
29+ class ChangeEvent (BaseModel ):
2930 """
3031 The common structure of a suggestion change event (except the `action`
3132 field, which is omitted here because of a Pydantic limitation: we want to
@@ -41,7 +42,7 @@ class ChangeEvent(TypedDict):
4142 username : str
4243
4344
44- class PackageData (TypedDict ):
45+ class PackageData (BaseModel ):
4546 """
4647 A package in a package change event.
4748 """
@@ -115,15 +116,13 @@ def _annotate_username(self, query: EventQuerySet) -> EventQuerySet:
115116 )
116117
117118 def get_raw_events (
118- self , suggestion_ids : list [str | None ]
119+ self , suggestion_ids : list [int | None ]
119120 ) -> list [PackageChangeEvent | SuggestionChangeEvent ]:
120121 """
121122 Combine the different types of events related to a list of suggestions
122123 in a single list and order them by timestamp. Multiple log entries
123124 constituting one logical edit from the user aren't aggregated in this
124125 method. This is left to `get_dict`.
125-
126- See `get_dict` for the schema of the output.
127126 """
128127
129128 raw_events = []
@@ -141,13 +140,13 @@ def get_raw_events(
141140
142141 for status_event in status_qs .all ().iterator ():
143142 raw_events .append (
144- {
145- " suggestion_id" : status_event .pgh_obj_id ,
146- " timestamp" : status_event .pgh_created_at ,
147- " username" : status_event .username ,
148- " action" : status_event .pgh_label ,
149- " status_value" : status_event .status ,
150- }
143+ SuggestionChangeEvent (
144+ suggestion_id = status_event .pgh_obj_id ,
145+ timestamp = status_event .pgh_created_at ,
146+ username = status_event .username ,
147+ action = status_event .pgh_label ,
148+ status_value = status_event .status ,
149+ )
151150 )
152151
153152 package_qs = self ._annotate_username (
@@ -182,21 +181,21 @@ def get_raw_events(
182181
183182 for package_event in package_qs .all ().iterator ():
184183 raw_events .append (
185- {
186- " suggestion_id" : package_event .proposal_id ,
187- " timestamp" : package_event .pgh_created_at ,
188- " username" : package_event .username ,
189- " action" : package_event .pgh_label ,
190- " package_names" : package_event .package_names ,
191- " package_count" : package_event .package_count ,
192- }
184+ PackageChangeEvent (
185+ suggestion_id = package_event .proposal_id ,
186+ timestamp = package_event .pgh_created_at ,
187+ username = package_event .username ,
188+ action = package_event .pgh_label ,
189+ package_names = package_event .package_names ,
190+ package_count = package_event .package_count ,
191+ )
193192 )
194193
195- return sorted (raw_events , key = lambda event : event [ " timestamp" ] )
194+ return sorted (raw_events , key = lambda event : event . timestamp )
196195
197196 def get_dict (
198- self , suggestion_ids : list [str | None ]
199- ) -> dict [str | None , dict [str , Any ]]:
197+ self , suggestion_ids : list [int | None ]
198+ ) -> dict [int , list [ dict [str , Any ] ]]:
200199 """
201200 Aggregate the different types of events related to a given suggestion in
202201 a unified list of dicts, ordered by timestamp and with bulk actions
@@ -205,10 +204,12 @@ def get_dict(
205204
206205 raw_events = self .get_raw_events (suggestion_ids )
207206
208- grouped_activity_log = {}
207+ grouped_activity_log : dict [
208+ int , list [PackageChangeEvent | SuggestionChangeEvent ]
209+ ] = {}
209210
210211 for event in raw_events :
211- suggestion_id = event .get ( " suggestion_id" )
212+ suggestion_id = event .suggestion_id
212213
213214 if suggestion_id in grouped_activity_log :
214215 grouped_activity_log [suggestion_id ].append (event )
@@ -217,38 +218,35 @@ def get_dict(
217218
218219 # Second pass to fold repeated package actions by user,
219220 # needed because with htmx we're sending item-wise changes that we still want to display in bulk
220- folded_activity_log = {}
221+ folded_activity_log : dict [int , list [dict [str , Any ]]] = {}
222+
221223 for suggestion_id , events in grouped_activity_log .items ():
222- suggestion_log = []
224+ suggestion_log : list [ PackageChangeEvent | SuggestionChangeEvent ] = []
223225
224226 accumulator = None
225227 for event in events :
226228 # Bulk events that are subject to folding are currently
227229 # - package editions
228230 # - maintainers editions (soon to be logged)
229- if event [ " action" ] .startswith ("derivations" ):
231+ if event . action .startswith ("derivations" ):
230232 if not accumulator :
231233 accumulator = event
232234 else :
233235 if (
234- event [ " action" ] == accumulator [ " action" ]
235- and event [ " username" ] == accumulator [ " username" ]
236+ event . action == accumulator . action
237+ and event . username == accumulator . username
236238 ):
237239 # For now, this is the only remaining possibility,
238240 # but we'll add maintainer edits soon.
239- if event ["action" ].startswith ("derivations" ):
240- accumulator ["package_names" ] = (
241- accumulator ["package_names" ]
242- + event ["package_names" ]
241+ if event .action .startswith ("derivations" ):
242+ accumulator .package_names = (
243+ accumulator .package_names + event .package_names
243244 )
244- accumulator ["package_count" ] = (
245- accumulator ["package_count" ]
246- + event ["package_count" ]
245+ accumulator .package_count = (
246+ accumulator .package_count + event .package_count
247247 )
248-
249- accumulator ["timestamp" ] = event [
250- "timestamp"
251- ] # Keep latest timestamp
248+ # Keep latest timestamp
249+ accumulator .timestamp = event .timestamp
252250 else :
253251 suggestion_log .append (accumulator )
254252 accumulator = event
@@ -261,6 +259,8 @@ def get_dict(
261259 if accumulator :
262260 suggestion_log .append (accumulator )
263261
264- folded_activity_log [suggestion_id ] = suggestion_log
262+ folded_activity_log [suggestion_id ] = [
263+ event .model_dump () for event in suggestion_log
264+ ]
265265
266266 return folded_activity_log
0 commit comments