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
« 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
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"
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"
112class DareModel(AppBaseModel):
113 """Represents a dare created by a user or a group."""
115 model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
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")
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.
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
197 # ---- Convenience predicates -----------------------------------------
199 @property
200 def human_readable_id(self) -> str:
201 s = truncate_string(f"Dare-{self.title}", 16)
202 return s.replace(" ", "-")
204 @property
205 def short_description_with_from(self) -> str:
206 return truncate_string(f"Dare from {self.from_uid}: '{self.title}'", 50)
208 @property
209 def short_description(self) -> str:
210 return truncate_string(self.title, 50)
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 )
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
230 @override
231 def calculate_change_score(self, other: Self) -> float:
232 return ChangeScore(self, other, DARE_FIELD_NAMES).score
235DARE_FIELD_NAMES: list[str] = list(DareModel.model_fields.keys())
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