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

105 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 

8 

9from icalendar.attr import ( 

10 CONCEPTS_TYPE_SETTER, 

11 LINKS_TYPE_SETTER, 

12 RELATED_TO_TYPE_SETTER, 

13 X_MOZ_LASTACK_property, 

14 X_MOZ_SNOOZE_TIME_property, 

15 attendees_property, 

16 categories_property, 

17 class_property, 

18 color_property, 

19 conferences_property, 

20 contacts_property, 

21 create_single_property, 

22 description_property, 

23 exdates_property, 

24 get_duration_property, 

25 get_end_property, 

26 get_start_end_duration_with_validation, 

27 get_start_property, 

28 images_property, 

29 location_property, 

30 organizer_property, 

31 priority_property, 

32 property_del_duration, 

33 property_doc_duration_template, 

34 property_get_duration, 

35 property_set_duration, 

36 rdates_property, 

37 rrules_property, 

38 sequence_property, 

39 set_duration_with_locking, 

40 set_end_with_locking, 

41 set_start_with_locking, 

42 status_property, 

43 summary_property, 

44 uid_property, 

45 url_property, 

46) 

47from icalendar.cal.component import Component 

48from icalendar.cal.examples import get_example 

49 

50if TYPE_CHECKING: 

51 from collections.abc import Iterable, Sequence 

52 

53 from icalendar.alarms import Alarms 

54 from icalendar.enums import CLASS, STATUS 

55 from icalendar.prop import vCalAddress 

56 from icalendar.prop.conference import Conference 

57 

58 

59class Todo(Component): 

60 """ 

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

62 properties that represents an action item or assignment. For 

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

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

65 seminar on Internet Calendaring". 

66 

67 Examples: 

68 Create a new Todo: 

69 

70 >>> from icalendar import Todo 

71 >>> todo = Todo.new() 

72 >>> print(todo.to_ical()) 

73 BEGIN:VTODO 

74 DTSTAMP:20250517T080612Z 

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

76 END:VTODO 

77 

78 Complete the example Todo. 

79 

80 .. code-block:: pycon 

81 

82 >>> from datetime import datetime, timezone 

83 >>> from icalendar import Todo, STATUS 

84 >>> todo = Todo.example() 

85 >>> todo["PERCENT-COMPLETE"] = 100 

86 >>> todo["COMPLETED"] = datetime(2007, 5, 1, 12, tzinfo=timezone.utc) 

87 >>> todo.status = STATUS.COMPLETED 

88 >>> print(todo.to_ical().decode()) 

89 BEGIN:VTODO 

90 CATEGORIES:FAMILY,FINANCE 

91 CLASS:CONFIDENTIAL 

92 COMPLETED:2007-05-01 12:00:00+00:00 

93 DTSTAMP:20070313T123432Z 

94 DUE;VALUE=DATE:20070501 

95 PERCENT-COMPLETE:100 

96 STATUS:COMPLETED 

97 SUMMARY:Submit Quebec Income Tax Return for 2006 

98 UID:20070313T123432Z-456553@example.com 

99 END:VTODO 

100 

101 """ 

102 

103 name = "VTODO" 

104 

105 required = ( 

106 "UID", 

107 "DTSTAMP", 

108 ) 

109 singletons = ( 

110 "CLASS", 

111 "COLOR", 

112 "COMPLETED", 

113 "CREATED", 

114 "DESCRIPTION", 

115 "DTSTAMP", 

116 "DTSTART", 

117 "GEO", 

118 "LAST-MODIFIED", 

119 "LOCATION", 

120 "ORGANIZER", 

121 "PERCENT-COMPLETE", 

122 "PRIORITY", 

123 "RECURRENCE-ID", 

124 "SEQUENCE", 

125 "STATUS", 

126 "SUMMARY", 

127 "UID", 

128 "URL", 

129 "DUE", 

130 "DURATION", 

131 ) 

132 exclusive = ( 

133 "DUE", 

134 "DURATION", 

135 ) 

136 multiple = ( 

137 "ATTACH", 

138 "ATTENDEE", 

139 "CATEGORIES", 

140 "COMMENT", 

141 "CONTACT", 

142 "EXDATE", 

143 "RSTATUS", 

144 "RELATED", 

145 "RESOURCES", 

146 "RDATE", 

147 "RRULE", 

148 ) 

149 DTSTART = create_single_property( 

150 "DTSTART", 

151 "dt", 

152 (datetime, date), 

153 date, 

154 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', 

155 ) 

156 DUE = create_single_property( 

157 "DUE", 

158 "dt", 

159 (datetime, date), 

160 date, 

161 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', 

162 ) 

163 DURATION = property( 

164 property_get_duration, 

165 property_set_duration, 

166 property_del_duration, 

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

168 ) 

169 

170 def _get_start_end_duration(self): 

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

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

173 

174 @property 

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

176 """The start of the VTODO. 

177 

178 Invalid values raise an InvalidCalendar. 

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

180 

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

182 

183 >>> from datetime import datetime 

184 >>> from icalendar import Todo 

185 >>> todo = Todo() 

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

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

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

189 datetime.timedelta(seconds=1800) 

190 >>> print(todo.to_ical()) 

191 BEGIN:VTODO 

192 DTSTART:20210101T120000 

193 DUE:20210101T123000 

194 END:VTODO 

195 """ 

196 return get_start_property(self) 

197 

198 @start.setter 

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

200 """Set the start.""" 

201 self.DTSTART = start 

202 

203 @property 

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

205 """The end of the todo. 

206 

207 Invalid values raise an InvalidCalendar error. 

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

209 """ 

210 return get_end_property(self, "DUE") 

211 

212 @end.setter 

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

214 """Set the end.""" 

215 self.DUE = end 

216 

217 @property 

218 def duration(self) -> timedelta: 

219 """The duration of the VTODO. 

220 

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

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

223 start locked. 

224 

225 Setting the duration will: 

226 

227 1. Keep the start time locked (unchanged) 

228 2. Adjust the end time to start + duration 

229 3. Remove any existing DUE property 

230 4. Set the DURATION property 

231 """ 

232 return get_duration_property(self) 

233 

234 @duration.setter 

235 def duration(self, value: timedelta): 

236 if not isinstance(value, timedelta): 

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

238 

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

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

241 

242 def set_duration( 

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

244 ): 

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

246 

247 Parameters: 

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

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

250 """ 

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

252 

253 def set_start( 

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

255 ): 

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

257 

258 Parameters: 

259 start: The start time to set 

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

261 for auto-detect) 

262 """ 

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

264 

265 def set_end( 

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

267 ): 

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

269 

270 Parameters: 

271 end: The end time to set 

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

273 """ 

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

275 

276 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

277 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

278 

279 @property 

280 def alarms(self) -> Alarms: 

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

282 

283 >>> from datetime import datetime 

284 >>> from icalendar import Todo 

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

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

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

288 0 

289 

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

291 RDATE, EXDATE, and RRULE properties. 

292 """ 

293 from icalendar.alarms import Alarms 

294 

295 return Alarms(self) 

296 

297 color = color_property 

298 sequence = sequence_property 

299 categories = categories_property 

300 rdates = rdates_property 

301 exdates = exdates_property 

302 rrules = rrules_property 

303 uid = uid_property 

304 summary = summary_property 

305 description = description_property 

306 classification = class_property 

307 url = url_property 

308 organizer = organizer_property 

309 location = location_property 

310 priority = priority_property 

311 contacts = contacts_property 

312 status = status_property 

313 attendees = attendees_property 

314 images = images_property 

315 conferences = conferences_property 

316 from icalendar.attr import RECURRENCE_ID 

317 

318 @classmethod 

319 def new( 

320 cls, 

321 /, 

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

323 categories: Sequence[str] = (), 

324 classification: CLASS | None = None, 

325 color: str | None = None, 

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

327 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

330 created: date | None = None, 

331 description: str | None = None, 

332 end: date | datetime | None = None, 

333 last_modified: date | None = None, 

334 links: LINKS_TYPE_SETTER = None, 

335 location: str | None = None, 

336 organizer: vCalAddress | str | None = None, 

337 priority: int | None = None, 

338 recurrence_id: date | datetime | None = None, 

339 refids: list[str] | str | None = None, 

340 related_to: RELATED_TO_TYPE_SETTER = None, 

341 sequence: int | None = None, 

342 stamp: date | None = None, 

343 start: date | datetime | None = None, 

344 status: STATUS | None = None, 

345 subcomponents: Iterable[Component] | None = None, 

346 summary: str | None = None, 

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

348 url: str | None = None, 

349 ): 

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

351 

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

353 

354 Parameters: 

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

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

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

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

359 comments: The :attr:`~icalendar.Component.comments` of the todo. 

360 concepts: The :attr:`~icalendar.Component.concepts` of the todo. 

361 contacts: The :attr:`contacts` of the todo. 

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

363 created: The :attr:`~icalendar.Component.created` of the todo. 

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

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

366 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo. 

367 links: The :attr:`~icalendar.Component.links` of the todo. 

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

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

370 recurrence_id: The :attr:`RECURRENCE_ID` of the todo. 

371 refids: :attr:`~icalendar.Component.refids` of the todo. 

372 related_to: :attr:`~icalendar.Component.related_to` of the todo. 

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

374 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo. 

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

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

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

378 subcomponents: The subcomponents of the todo. 

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

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

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

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

383 

384 Returns: 

385 :class:`Todo` 

386 

387 Raises: 

388 ~error.InvalidCalendar: If the content is not valid 

389 according to :rfc:`5545`. 

390 

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

392 """ 

393 todo: Todo = super().new( 

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

395 created=created, 

396 last_modified=last_modified, 

397 comments=comments, 

398 links=links, 

399 related_to=related_to, 

400 refids=refids, 

401 concepts=concepts, 

402 subcomponents=subcomponents, 

403 ) 

404 todo.summary = summary 

405 todo.description = description 

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

407 todo.start = start 

408 todo.end = end 

409 todo.color = color 

410 todo.categories = categories 

411 todo.sequence = sequence 

412 todo.classification = classification 

413 todo.url = url 

414 todo.organizer = organizer 

415 todo.location = location 

416 todo.priority = priority 

417 todo.contacts = contacts 

418 todo.status = status 

419 todo.attendees = attendees 

420 todo.conferences = conferences 

421 todo.RECURRENCE_ID = recurrence_id 

422 

423 if cls._validate_new: 

424 cls._validate_start_and_end(start, end) 

425 return todo 

426 

427 @classmethod 

428 def example(cls, name: str = "example") -> Todo: 

429 """Return the todo example with the given name.""" 

430 return cls.from_ical(get_example("todos", name)) 

431 

432 

433__all__ = ["Todo"]