Coverage for functions \ flipdare \ generated \ model \ dare_model.py: 96%

201 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# 

3# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

4# 

5# This file is part of Flipdare's proprietary software and contains 

6# confidential and copyrighted material. Unauthorised copying, 

7# modification, distribution, or use of this file is strictly 

8# prohibited without prior written permission from Flipdare Pty Ltd. 

9# 

10# This software includes third-party components licensed under MIT, 

11# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

12# 

13# NOTE: THIS FILE IS AUTO GENERATED. DO NOT EDIT. 

14# 

15# Generated by codegen_models.py 

16# 

17# Modify 'codegen_models.py' 

18# and re-run the script above to update. 

19# 

20from __future__ import annotations 

21from datetime import datetime 

22from google.cloud.firestore_v1.transforms import Sentinel 

23from flipdare.core.firestore_field import FirestoreField, OptionalFirestoreField 

24from flipdare.util.time_util import FirestoreTime 

25from typing import Any, TypedDict, cast, Unpack 

26from enum import StrEnum 

27from pydantic import Field, ConfigDict, TypeAdapter 

28from flipdare.firestore.core.app_base_model import AppBaseModel 

29from flipdare.generated.model.internal.video_model import VideoModel, VideoDict 

30from flipdare.generated.shared.model.dare.dare_status import DareStatus 

31from flipdare.generated.model.internal.dare_event_model import DareEventModel, DareEventDict 

32from flipdare.generated.shared.model.restriction.moderation_decision import ModerationDecision 

33from flipdare.generated.shared.model.issue.issue_progress import IssueProgress 

34from flipdare.generated.shared.model.issue.disputed_progress import DisputedProgress 

35from flipdare.generated.shared.model.app_visibility import AppVisibility 

36from flipdare.generated.model.internal.view_stats_model import ViewStatsModel, ViewStatsDict 

37from flipdare.generated.model.pledge_stats_model import PledgeStatsModel, PledgeStatsDict 

38from flipdare.generated.model.internal.stopwatch_model import StopwatchModel, StopwatchDict 

39from flipdare.generated.shared.model.dare.ballot_result import BallotResult 

40from flipdare.generated.shared.model.dare.ballot_algorithm_type import BallotAlgorithmType 

41from flipdare.core.change_score import ChangeScore 

42from flipdare.app_globals import string_has_alpha, truncate_string 

43from typing import override, Self 

44 

45 

46class DareKeys(StrEnum): 

47 ID = "id" 

48 CREATED_AT = "created_at" 

49 UPDATED_AT = "updated_at" 

50 FROM_UID = "from_uid" 

51 OBJ_ID = "obj_id" 

52 SLUG_CODE = "slug_code" 

53 IS_GROUP_DARE = "is_group_dare" 

54 TITLE = "title" 

55 MESSAGE = "message" 

56 VIDEO = "video" 

57 STATUS = "status" 

58 ACCEPTED_EVENT = "accepted_event" 

59 COMPLETED_EVENT = "completed_event" 

60 MODERATION_DECISION = "moderation_decision" 

61 PRE_FLAG_STATUS = "pre_flag_status" 

62 ISSUE_PROGRESS = "issue_progress" 

63 DISPUTED_PROGRESS = "disputed_progress" 

64 FLAG_ID = "flag_id" 

65 VISIBILITY = "visibility" 

66 TIMER_STARTED_AT = "timer_started_at" 

67 TIMER_DURATION_DAYS = "timer_duration_days" 

68 VIEW_STATS = "view_stats" 

69 PLEDGE_STATS = "pledge_stats" 

70 VOTING_TIMER = "voting_timer" 

71 BALLOT_RESULT = "ballot_result" 

72 BALLOT_ALGORITHM_TYPE = "ballot_algorithm_type" 

73 VERSION = "version" 

74 PROCESSED = "processed" 

75 ERROR_COUNT = "error_count" 

76 THUMBNAIL_CREATED = "thumbnail_created" 

77 HASH_CREATED = "hash_created" 

78 OPTIMIZED_VIDEO = "optimized_video" 

79 SEARCH_INDEXED = "search_indexed" 

80 EMAIL_SENT = "email_sent" 

81 COMPLETE_THUMBNAIL_CREATED = "complete_thumbnail_created" 

82 COMPLETE_HASH_CREATED = "complete_hash_created" 

83 COMPLETE_OPTIMIZED_VIDEO = "complete_optimized_video" 

84 COMPLETE_SEARCH_INDEXED = "complete_search_indexed" 

85 VOTE_STARTED_EMAIL_SENT = "vote_started_email_sent" 

86 VOTE_COMPLETE_EMAIL_SENT = "vote_complete_email_sent" 

87 

88 

89# !! IMPORTANT !! 

90# !! 

91# !! this should only be used in the database to query. 

92# !! 

93class DareInternalKeys(StrEnum): 

94 CREATED_AT = "created_at" 

95 UPDATED_AT = "updated_at" 

96 VERSION = "VERSION" 

97 PROCESSED = "INT_P" 

98 ERROR_COUNT = "INT_E" 

99 THUMBNAIL_CREATED = "INT_U_TC" 

100 HASH_CREATED = "INT_U_HG" 

101 OPTIMIZED_VIDEO = "INT_U_OP" 

102 SEARCH_INDEXED = "INT_U_SI" 

103 EMAIL_SENT = "INT_U_E" 

104 COMPLETE_THUMBNAIL_CREATED = "INT_U_CT" 

105 COMPLETE_HASH_CREATED = "INT_U_CHG" 

106 COMPLETE_OPTIMIZED_VIDEO = "INT_U_COP" 

107 COMPLETE_SEARCH_INDEXED = "INT_U_CSI" 

108 VOTE_STARTED_EMAIL_SENT = "INT_U_VE" 

109 VOTE_COMPLETE_EMAIL_SENT = "INT_U_VCE" 

110 

111 

112class DareModel(AppBaseModel): 

113 """Represents a dare created by a user or a group.""" 

114 

115 model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True) 

116 

117 id: str | None = Field(None, alias="id") 

118 created_at: FirestoreField = Field( 

119 default_factory=cast("Any", lambda: FirestoreTime.server_timestamp()) 

120 ) 

121 updated_at: FirestoreField = Field( 

122 default_factory=cast("Any", lambda: FirestoreTime.server_timestamp()) 

123 ) 

124 from_uid: str 

125 obj_id: str 

126 slug_code: str 

127 is_group_dare: bool = Field(default=False) 

128 title: str 

129 message: str 

130 video: VideoModel 

131 status: DareStatus = Field(default=DareStatus.DRAFT) 

132 accepted_event: DareEventModel | None = None 

133 completed_event: DareEventModel | None = None 

134 moderation_decision: ModerationDecision | None = None 

135 pre_flag_status: DareStatus | None = None 

136 issue_progress: IssueProgress | None = None 

137 disputed_progress: DisputedProgress | None = None 

138 flag_id: str | None = None 

139 visibility: AppVisibility = Field(default=AppVisibility.PUBLIC) 

140 timer_started_at: OptionalFirestoreField = Field(default=None) 

141 timer_duration_days: int = Field(default=45) 

142 view_stats: ViewStatsModel 

143 pledge_stats: PledgeStatsModel 

144 voting_timer: StopwatchModel | None = None 

145 ballot_result: BallotResult | None = None 

146 ballot_algorithm_type: BallotAlgorithmType | None = None 

147 # INTERNAL - Version 

148 version: int = Field(default=1, alias="VERSION") 

149 # INTERNAL - Processed 

150 processed: bool = Field(default=False, alias="INT_P") 

151 # INTERNAL - Error Count 

152 error_count: int = Field(default=0, alias="INT_E") 

153 # INTERNAL - Thumbnail Created 

154 thumbnail_created: bool = Field(default=False, alias="INT_U_TC") 

155 # INTERNAL - Hash Created 

156 hash_created: bool = Field(default=False, alias="INT_U_HG") 

157 # INTERNAL - Optimized Video 

158 optimized_video: bool = Field(default=False, alias="INT_U_OP") 

159 # INTERNAL - Search Indexed 

160 search_indexed: bool = Field(default=False, alias="INT_U_SI") 

161 # INTERNAL - Standalone - Summary Email Sent 

162 email_sent: bool = Field(default=False, alias="INT_U_E") 

163 # INTERNAL - Complete Thumbnail Created 

164 complete_thumbnail_created: bool = Field(default=False, alias="INT_U_CT") 

165 # INTERNAL - Complete Hash Created 

166 complete_hash_created: bool = Field(default=False, alias="INT_U_CHG") 

167 # INTERNAL - Complete Optimized Video 

168 complete_optimized_video: bool = Field(default=False, alias="INT_U_COP") 

169 # INTERNAL - Complete Search Indexed 

170 complete_search_indexed: bool = Field(default=False, alias="INT_U_CSI") 

171 # INTERNAL - Standalone - Vote Started Email Sent 

172 vote_started_email_sent: bool = Field(default=False, alias="INT_U_VE") 

173 # INTERNAL - Standalone - Vote Complete Email Sent 

174 vote_complete_email_sent: bool = Field(default=False, alias="INT_U_VCE") 

175 

176 @classmethod 

177 def validate_partial(cls, **data: Unpack[DareDict]) -> dict[str, Any]: 

178 """ 

179 Uses Unpack to give you autocomplete and static warnings 

180 if you pass an invalid key or type in your code. 

181 

182 Returns a dict with Firestore field names (aliases) for use with batch.update(). 

183 """ 

184 result: dict[str, Any] = {} 

185 for k, v in data.items(): 

186 if k in cls.__pydantic_fields__: 

187 field_info = cls.__pydantic_fields__[k] 

188 validated_value = cast( 

189 "Any", 

190 TypeAdapter(field_info.annotation).validate_python(v), 

191 ) 

192 # Use alias if defined, otherwise use field name 

193 output_key = field_info.alias or k 

194 result[output_key] = validated_value 

195 return result 

196 

197 # ---- Convenience predicates ----------------------------------------- 

198 

199 @property 

200 def human_readable_id(self) -> str: 

201 s = truncate_string(f"Dare-{self.title}", 16) 

202 return s.replace(" ", "-") 

203 

204 @property 

205 def short_description_with_from(self) -> str: 

206 return truncate_string(f"Dare from {self.from_uid}: '{self.title}'", 50) 

207 

208 @property 

209 def short_description(self) -> str: 

210 return truncate_string(self.title, 50) 

211 

212 @property 

213 def can_share(self) -> bool: 

214 return ( 

215 self.visibility == AppVisibility.PUBLIC 

216 and self.flag_id is None 

217 and self.status != DareStatus.DRAFT 

218 ) 

219 

220 @property 

221 @override 

222 def searchable_values(self) -> list[str]: 

223 values = [] 

224 if string_has_alpha(self.title): 

225 values.append(self.title) 

226 if string_has_alpha(self.message): 

227 values.append(self.message) 

228 return values 

229 

230 @override 

231 def calculate_change_score(self, other: Self) -> float: 

232 return ChangeScore(self, other, DARE_FIELD_NAMES).score 

233 

234 

235DARE_FIELD_NAMES: list[str] = list(DareModel.model_fields.keys()) 

236 

237 

238class DareDict(TypedDict, total=False): 

239 id: str | None 

240 created_at: Sentinel | datetime | str 

241 updated_at: Sentinel | datetime | str 

242 from_uid: str 

243 obj_id: str 

244 slug_code: str 

245 is_group_dare: bool | None 

246 title: str 

247 message: str 

248 video: VideoDict 

249 status: DareStatus | None 

250 accepted_event: DareEventDict | None 

251 completed_event: DareEventDict | None 

252 moderation_decision: ModerationDecision | None 

253 pre_flag_status: DareStatus | None 

254 issue_progress: IssueProgress | None 

255 disputed_progress: DisputedProgress | None 

256 flag_id: str | None 

257 visibility: AppVisibility | None 

258 timer_started_at: Sentinel | datetime | str 

259 timer_duration_days: int | None 

260 view_stats: ViewStatsDict 

261 pledge_stats: PledgeStatsDict 

262 voting_timer: StopwatchDict | None 

263 ballot_result: BallotResult | None 

264 ballot_algorithm_type: BallotAlgorithmType | None 

265 VERSION: int | None 

266 INT_P: bool | None 

267 INT_E: int | None 

268 INT_U_TC: bool | None 

269 INT_U_HG: bool | None 

270 INT_U_OP: bool | None 

271 INT_U_SI: bool | None 

272 INT_U_E: bool | None 

273 INT_U_CT: bool | None 

274 INT_U_CHG: bool | None 

275 INT_U_COP: bool | None 

276 INT_U_CSI: bool | None 

277 INT_U_VE: bool | None 

278 INT_U_VCE: bool | None