Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/todo.py: 60%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

98 statements  

1""":rfc:`5545` VTODO component.""" 

2 

3from __future__ import annotations 

4 

5import uuid 

6from datetime import date, datetime, timedelta 

7from typing import TYPE_CHECKING, Literal, Sequence 

8 

9from icalendar.attr import ( 

10 X_MOZ_LASTACK_property, 

11 X_MOZ_SNOOZE_TIME_property, 

12 attendees_property, 

13 categories_property, 

14 class_property, 

15 color_property, 

16 conferences_property, 

17 contacts_property, 

18 create_single_property, 

19 description_property, 

20 exdates_property, 

21 images_property, 

22 get_duration_property, 

23 get_end_property, 

24 get_start_end_duration_with_validation, 

25 get_start_property, 

26 location_property, 

27 organizer_property, 

28 priority_property, 

29 property_del_duration, 

30 property_doc_duration_template, 

31 property_get_duration, 

32 property_set_duration, 

33 rdates_property, 

34 rrules_property, 

35 sequence_property, 

36 set_duration_with_locking, 

37 set_end_with_locking, 

38 set_start_with_locking, 

39 status_property, 

40 summary_property, 

41 uid_property, 

42 url_property, 

43) 

44from icalendar.cal.component import Component 

45 

46if TYPE_CHECKING: 

47 from icalendar.alarms import Alarms 

48 from icalendar.enums import CLASS, STATUS 

49 from icalendar.prop import vCalAddress 

50 from icalendar.prop.conference import Conference 

51 

52 

53class Todo(Component): 

54 """ 

55 A "VTODO" calendar component is a grouping of component 

56 properties that represents an action item or assignment. For 

57 example, it can be used to represent an item of work assigned to 

58 an individual, such as "Prepare for the upcoming conference 

59 seminar on Internet Calendaring". 

60 

61 Examples: 

62 Create a new Todo: 

63 

64 >>> from icalendar import Todo 

65 >>> todo = Todo.new() 

66 >>> print(todo.to_ical()) 

67 BEGIN:VTODO 

68 DTSTAMP:20250517T080612Z 

69 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 

70 END:VTODO 

71 

72 """ 

73 

74 name = "VTODO" 

75 

76 required = ( 

77 "UID", 

78 "DTSTAMP", 

79 ) 

80 singletons = ( 

81 "CLASS", 

82 "COLOR", 

83 "COMPLETED", 

84 "CREATED", 

85 "DESCRIPTION", 

86 "DTSTAMP", 

87 "DTSTART", 

88 "GEO", 

89 "LAST-MODIFIED", 

90 "LOCATION", 

91 "ORGANIZER", 

92 "PERCENT-COMPLETE", 

93 "PRIORITY", 

94 "RECURRENCE-ID", 

95 "SEQUENCE", 

96 "STATUS", 

97 "SUMMARY", 

98 "UID", 

99 "URL", 

100 "DUE", 

101 "DURATION", 

102 ) 

103 exclusive = ( 

104 "DUE", 

105 "DURATION", 

106 ) 

107 multiple = ( 

108 "ATTACH", 

109 "ATTENDEE", 

110 "CATEGORIES", 

111 "COMMENT", 

112 "CONTACT", 

113 "EXDATE", 

114 "RSTATUS", 

115 "RELATED", 

116 "RESOURCES", 

117 "RDATE", 

118 "RRULE", 

119 ) 

120 DTSTART = create_single_property( 

121 "DTSTART", 

122 "dt", 

123 (datetime, date), 

124 date, 

125 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', # noqa: E501 

126 ) 

127 DUE = create_single_property( 

128 "DUE", 

129 "dt", 

130 (datetime, date), 

131 date, 

132 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', # noqa: E501 

133 ) 

134 DURATION = property( 

135 property_get_duration, 

136 property_set_duration, 

137 property_del_duration, 

138 property_doc_duration_template.format(component="VTODO"), 

139 ) 

140 

141 def _get_start_end_duration(self): 

142 """Verify the calendar validity and return the right attributes.""" 

143 return get_start_end_duration_with_validation(self, "DTSTART", "DUE", "VTODO") 

144 

145 @property 

146 def start(self) -> date | datetime: 

147 """The start of the VTODO. 

148 

149 Invalid values raise an InvalidCalendar. 

150 If there is no start, we also raise an IncompleteComponent error. 

151 

152 You can get the start, end and duration of a Todo as follows: 

153 

154 >>> from datetime import datetime 

155 >>> from icalendar import Todo 

156 >>> todo = Todo() 

157 >>> todo.start = datetime(2021, 1, 1, 12) 

158 >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes 

159 >>> todo.duration # 1800 seconds == 30 minutes 

160 datetime.timedelta(seconds=1800) 

161 >>> print(todo.to_ical()) 

162 BEGIN:VTODO 

163 DTSTART:20210101T120000 

164 DUE:20210101T123000 

165 END:VTODO 

166 """ 

167 return get_start_property(self) 

168 

169 @start.setter 

170 def start(self, start: date | datetime | None): 

171 """Set the start.""" 

172 self.DTSTART = start 

173 

174 @property 

175 def end(self) -> date | datetime: 

176 """The end of the todo. 

177 

178 Invalid values raise an InvalidCalendar error. 

179 If there is no end, we also raise an IncompleteComponent error. 

180 """ 

181 return get_end_property(self, "DUE") 

182 

183 @end.setter 

184 def end(self, end: date | datetime | None): 

185 """Set the end.""" 

186 self.DUE = end 

187 

188 @property 

189 def duration(self) -> timedelta: 

190 """The duration of the VTODO. 

191 

192 Returns the DURATION property if set, otherwise calculated from start and end. 

193 You can set the duration to automatically adjust the end time while keeping 

194 start locked. 

195 

196 Setting the duration will: 

197 1. Keep the start time locked (unchanged) 

198 2. Adjust the end time to start + duration 

199 3. Remove any existing DUE property 

200 4. Set the DURATION property 

201 """ 

202 return get_duration_property(self) 

203 

204 @duration.setter 

205 def duration(self, value: timedelta): 

206 if not isinstance(value, timedelta): 

207 raise TypeError(f"Use timedelta, not {type(value).__name__}.") 

208 

209 # Use the set_duration method with default start-locked behavior 

210 self.set_duration(value, locked="start") 

211 

212 def set_duration( 

213 self, duration: timedelta | None, locked: Literal["start", "end"] = "start" 

214 ): 

215 """Set the duration of the event relative to either start or end. 

216 

217 Args: 

218 duration: The duration to set, or None to convert to DURATION property 

219 locked: Which property to keep unchanged ('start' or 'end') 

220 """ 

221 set_duration_with_locking(self, duration, locked, "DUE") 

222 

223 def set_start( 

224 self, start: date | datetime, locked: Literal["duration", "end"] | None = None 

225 ): 

226 """Set the start with explicit locking behavior. 

227 

228 Args: 

229 start: The start time to set 

230 locked: Which property to keep unchanged ('duration', 'end', or None 

231 for auto-detect) 

232 """ 

233 set_start_with_locking(self, start, locked, "DUE") 

234 

235 def set_end( 

236 self, end: date | datetime, locked: Literal["start", "duration"] = "start" 

237 ): 

238 """Set the end of the component, keeping either the start or the duration same. 

239 

240 Args: 

241 end: The end time to set 

242 locked: Which property to keep unchanged ('start' or 'duration') 

243 """ 

244 set_end_with_locking(self, end, locked, "DUE") 

245 

246 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

247 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

248 

249 @property 

250 def alarms(self) -> Alarms: 

251 """Compute the alarm times for this component. 

252 

253 >>> from datetime import datetime 

254 >>> from icalendar import Todo 

255 >>> todo = Todo() # empty without alarms 

256 >>> todo.start = datetime(2024, 10, 26, 10, 21) 

257 >>> len(todo.alarms.times) 

258 0 

259 

260 Note that this only uses DTSTART and DUE, but ignores 

261 RDATE, EXDATE, and RRULE properties. 

262 """ 

263 from icalendar.alarms import Alarms 

264 

265 return Alarms(self) 

266 

267 color = color_property 

268 sequence = sequence_property 

269 categories = categories_property 

270 rdates = rdates_property 

271 exdates = exdates_property 

272 rrules = rrules_property 

273 uid = uid_property 

274 summary = summary_property 

275 description = description_property 

276 classification = class_property 

277 url = url_property 

278 organizer = organizer_property 

279 location = location_property 

280 priority = priority_property 

281 contacts = contacts_property 

282 status = status_property 

283 attendees = attendees_property 

284 images = images_property 

285 conferences = conferences_property 

286 

287 @classmethod 

288 def new( 

289 cls, 

290 /, 

291 attendees: list[vCalAddress] | None = None, 

292 categories: Sequence[str] = (), 

293 classification: CLASS | None = None, 

294 color: str | None = None, 

295 comments: list[str] | str | None = None, 

296 contacts: list[str] | str | None = None, 

297 conferences: list[Conference] | None = None, 

298 created: date | None = None, 

299 description: str | None = None, 

300 end: date | datetime | None = None, 

301 last_modified: date | None = None, 

302 location: str | None = None, 

303 organizer: vCalAddress | str | None = None, 

304 priority: int | None = None, 

305 sequence: int | None = None, 

306 stamp: date | None = None, 

307 start: date | datetime | None = None, 

308 status: STATUS | None = None, 

309 summary: str | None = None, 

310 uid: str | uuid.UUID | None = None, 

311 url: str | None = None, 

312 ): 

313 """Create a new TODO with all required properties. 

314 

315 This creates a new Todo in accordance with :rfc:`5545`. 

316 

317 Arguments: 

318 attendees: The :attr:`attendees` of the todo. 

319 categories: The :attr:`categories` of the todo. 

320 classification: The :attr:`classification` of the todo. 

321 color: The :attr:`color` of the todo. 

322 comments: The :attr:`Component.comments` of the todo. 

323 conferences: The :attr:`conferences` of the todo. 

324 created: The :attr:`Component.created` of the todo. 

325 description: The :attr:`description` of the todo. 

326 end: The :attr:`end` of the todo. 

327 last_modified: The :attr:`Component.last_modified` of the todo. 

328 location: The :attr:`location` of the todo. 

329 organizer: The :attr:`organizer` of the todo. 

330 sequence: The :attr:`sequence` of the todo. 

331 stamp: The :attr:`Component.DTSTAMP` of the todo. 

332 If None, this is set to the current time. 

333 start: The :attr:`start` of the todo. 

334 status: The :attr:`status` of the todo. 

335 summary: The :attr:`summary` of the todo. 

336 uid: The :attr:`uid` of the todo. 

337 If None, this is set to a new :func:`uuid.uuid4`. 

338 url: The :attr:`url` of the todo. 

339 

340 Returns: 

341 :class:`Todo` 

342 

343 Raises: 

344 InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

345 

346 .. warning:: As time progresses, we will be stricter with the validation. 

347 """ 

348 todo = super().new( 

349 stamp=stamp if stamp is not None else cls._utc_now(), 

350 created=created, 

351 last_modified=last_modified, 

352 comments=comments, 

353 ) 

354 todo.summary = summary 

355 todo.description = description 

356 todo.uid = uid if uid is not None else uuid.uuid4() 

357 todo.start = start 

358 todo.end = end 

359 todo.color = color 

360 todo.categories = categories 

361 todo.sequence = sequence 

362 todo.classification = classification 

363 todo.url = url 

364 todo.organizer = organizer 

365 todo.location = location 

366 todo.priority = priority 

367 todo.contacts = contacts 

368 todo.status = status 

369 todo.attendees = attendees 

370 todo.conferences = conferences 

371 if cls._validate_new: 

372 cls._validate_start_and_end(start, end) 

373 return todo 

374 

375 

376__all__ = ["Todo"]