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

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

114 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 ATTACHMENTS_TYPE_SETTER, 

11 ATTENDEE_TYPE_SETTER, 

12 CONCEPTS_TYPE_SETTER, 

13 LINKS_TYPE_SETTER, 

14 RELATED_TO_TYPE_SETTER, 

15 REQUEST_STATUS_property, 

16 RESOURCES_property, 

17 X_MOZ_LASTACK_property, 

18 X_MOZ_SNOOZE_TIME_property, 

19 attachments_property, 

20 attendees_property, 

21 categories_property, 

22 class_property, 

23 color_property, 

24 conferences_property, 

25 contacts_property, 

26 create_single_property, 

27 description_property, 

28 exdates_property, 

29 get_duration_property, 

30 get_end_property, 

31 get_start_end_duration_with_validation, 

32 get_start_property, 

33 images_property, 

34 location_property, 

35 organizer_property, 

36 priority_property, 

37 property_del_duration, 

38 property_doc_duration_template, 

39 property_get_duration, 

40 property_set_duration, 

41 rdates_property, 

42 rrules_property, 

43 sequence_property, 

44 set_duration_with_locking, 

45 set_end_with_locking, 

46 set_start_with_locking, 

47 status_property, 

48 summary_property, 

49 uid_property, 

50 url_property, 

51) 

52from icalendar.cal.component import Component 

53from icalendar.cal.examples import get_example 

54 

55if TYPE_CHECKING: 

56 from collections.abc import Iterable, Sequence 

57 

58 from icalendar.alarms import Alarms 

59 from icalendar.compatibility import Self 

60 from icalendar.enums import CLASS, STATUS 

61 from icalendar.prop import vCalAddress 

62 from icalendar.prop.conference import Conference 

63 

64 

65class Todo(Component): 

66 """ 

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

68 properties that represents an action item or assignment. For 

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

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

71 seminar on Internet Calendaring". 

72 

73 Examples: 

74 Create a new Todo: 

75 

76 >>> from icalendar import Todo 

77 >>> todo = Todo.new() 

78 >>> print(todo.to_ical()) 

79 BEGIN:VTODO 

80 DTSTAMP:20250517T080612Z 

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

82 END:VTODO 

83 

84 Complete the example Todo. 

85 

86 .. code-block:: pycon 

87 

88 >>> from datetime import datetime, timezone 

89 >>> from icalendar import Todo, STATUS 

90 >>> todo = Todo.example() 

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

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

93 >>> todo.status = STATUS.COMPLETED 

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

95 BEGIN:VTODO 

96 CATEGORIES:FAMILY,FINANCE 

97 CLASS:CONFIDENTIAL 

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

99 DTSTAMP:20070313T123432Z 

100 DUE;VALUE=DATE:20070501 

101 PERCENT-COMPLETE:100 

102 STATUS:COMPLETED 

103 SUMMARY:Submit Quebec Income Tax Return for 2006 

104 UID:20070313T123432Z-456553@example.com 

105 END:VTODO 

106 

107 """ 

108 

109 name = "VTODO" 

110 

111 required = ( 

112 "UID", 

113 "DTSTAMP", 

114 ) 

115 singletons = ( 

116 "CLASS", 

117 "COLOR", 

118 "COMPLETED", 

119 "CREATED", 

120 "DESCRIPTION", 

121 "DTSTAMP", 

122 "DTSTART", 

123 "GEO", 

124 "LAST-MODIFIED", 

125 "LOCATION", 

126 "ORGANIZER", 

127 "PERCENT-COMPLETE", 

128 "PRIORITY", 

129 "RECURRENCE-ID", 

130 "SEQUENCE", 

131 "STATUS", 

132 "SUMMARY", 

133 "UID", 

134 "URL", 

135 "DUE", 

136 "DURATION", 

137 ) 

138 exclusive = ( 

139 "DUE", 

140 "DURATION", 

141 ) 

142 multiple = ( 

143 "ATTACH", 

144 "ATTENDEE", 

145 "CATEGORIES", 

146 "COMMENT", 

147 "CONTACT", 

148 "EXDATE", 

149 "REQUEST-STATUS", 

150 "RELATED", 

151 "RESOURCES", 

152 "RDATE", 

153 "RRULE", 

154 ) 

155 DTSTART = create_single_property( 

156 "DTSTART", 

157 "dt", 

158 (datetime, date), 

159 date, 

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

161 ) 

162 DUE = create_single_property( 

163 "DUE", 

164 "dt", 

165 (datetime, date), 

166 date, 

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

168 ) 

169 DURATION = property( 

170 property_get_duration, 

171 property_set_duration, 

172 property_del_duration, 

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

174 ) 

175 

176 def _get_start_end_duration(self): 

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

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

179 

180 @property 

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

182 """The start of the VTODO. 

183 

184 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar`. 

185 If there is no start, we also raise an :exc:`~icalendar.error.IncompleteComponent` error. 

186 

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

188 

189 >>> from datetime import datetime 

190 >>> from icalendar import Todo 

191 >>> todo = Todo() 

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

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

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

195 datetime.timedelta(seconds=1800) 

196 >>> print(todo.to_ical()) 

197 BEGIN:VTODO 

198 DTSTART:20210101T120000 

199 DUE:20210101T123000 

200 END:VTODO 

201 """ 

202 return get_start_property(self) 

203 

204 @start.setter 

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

206 """Set the start of the VTODO. 

207 

208 Sets the ``DTSTART`` property. Pass ``None`` to remove it. 

209 

210 Parameters: 

211 start: Required. The start as :class:`datetime.date` or 

212 :class:`datetime.datetime`, or ``None`` to remove ``DTSTART``. 

213 

214 See also: 

215 :attr:`end`, :attr:`duration` 

216 """ 

217 self.DTSTART = start 

218 

219 @property 

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

221 """The end of the todo. 

222 

223 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar` error. 

224 If there is no end, we also raise an :exc:`~icalendar.error.IncompleteComponent` error. 

225 """ 

226 return get_end_property(self, "DUE") 

227 

228 @end.setter 

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

230 """Set the end of the VTODO. 

231 

232 Sets the ``DUE`` property. Pass ``None`` to remove it. 

233 

234 Parameters: 

235 end: Required. The end as :class:`datetime.date` or 

236 :class:`datetime.datetime`, or ``None`` to remove ``DUE``. 

237 

238 See also: 

239 :attr:`start`, :attr:`duration` 

240 """ 

241 self.DUE = end 

242 

243 @property 

244 def duration(self) -> timedelta: 

245 """The duration of the VTODO. 

246 

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

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

249 start locked. 

250 

251 Setting the duration will do the following. 

252 

253 1. Keep the start time locked (unchanged) 

254 2. Adjust the end time to start + duration 

255 3. Remove any existing DUE property 

256 4. Set the DURATION property 

257 """ 

258 if "DURATION" not in self and "DUE" in self and "DTSTART" not in self: 

259 return timedelta(0) 

260 return get_duration_property(self) 

261 

262 @duration.setter 

263 def duration(self, value: timedelta): 

264 if not isinstance(value, timedelta): 

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

266 

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

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

269 

270 def set_duration( 

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

272 ): 

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

274 

275 Parameters: 

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

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

278 """ 

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

280 

281 def set_start( 

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

283 ): 

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

285 

286 Parameters: 

287 start: The start time to set 

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

289 for auto-detect) 

290 """ 

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

292 

293 def set_end( 

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

295 ): 

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

297 

298 Parameters: 

299 end: The end time to set 

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

301 """ 

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

303 

304 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

305 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

306 

307 @property 

308 def alarms(self) -> Alarms: 

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

310 

311 >>> from datetime import datetime 

312 >>> from icalendar import Todo 

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

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

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

316 0 

317 

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

319 RDATE, EXDATE, and RRULE properties. 

320 """ 

321 from icalendar.alarms import Alarms 

322 

323 return Alarms(self) 

324 

325 color = color_property 

326 sequence = sequence_property 

327 categories = categories_property 

328 rdates = rdates_property 

329 exdates = exdates_property 

330 rrules = rrules_property 

331 REQUEST_STATUS = REQUEST_STATUS_property 

332 RESOURCES = RESOURCES_property 

333 uid = uid_property 

334 summary = summary_property 

335 description = description_property 

336 classification = class_property 

337 url = url_property 

338 organizer = organizer_property 

339 location = location_property 

340 priority = priority_property 

341 contacts = contacts_property 

342 status = status_property 

343 attendees = attendees_property 

344 attachments = attachments_property 

345 images = images_property 

346 conferences = conferences_property 

347 from icalendar.attr import RECURRENCE_ID 

348 

349 @classmethod 

350 def new( 

351 cls, 

352 /, 

353 attachments: ATTACHMENTS_TYPE_SETTER = None, 

354 attendees: ATTENDEE_TYPE_SETTER = None, 

355 categories: Sequence[str] = (), 

356 classification: CLASS | None = None, 

357 color: str | None = None, 

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

359 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

362 created: date | None = None, 

363 description: str | None = None, 

364 end: date | datetime | None = None, 

365 last_modified: date | None = None, 

366 links: LINKS_TYPE_SETTER = None, 

367 location: str | None = None, 

368 organizer: vCalAddress | str | None = None, 

369 priority: int | None = None, 

370 recurrence_id: date | datetime | None = None, 

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

372 related_to: RELATED_TO_TYPE_SETTER = None, 

373 request_status: list[str] | str | None = None, 

374 resources: list[str] | str | None = None, 

375 sequence: int | None = None, 

376 stamp: date | None = None, 

377 start: date | datetime | None = None, 

378 status: STATUS | None = None, 

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

380 summary: str | None = None, 

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

382 url: str | None = None, 

383 ) -> Self: 

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

385 

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

387 

388 Parameters: 

389 attachments: The :attr:`attachments` of the todo. 

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

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

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

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

394 comments: The :attr:`~icalendar.cal.component.Component.comments` of the todo. 

395 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the todo. 

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

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

398 created: The :attr:`~icalendar.cal.component.Component.created` of the todo. 

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

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

401 last_modified: The :attr:`~icalendar.cal.component.Component.last_modified` of the todo. 

402 links: The :attr:`~icalendar.cal.component.Component.links` of the todo. 

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

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

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

406 refids: :attr:`~icalendar.cal.component.Component.refids` of the todo. 

407 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the todo. 

408 request_status: The :attr:`REQUEST_STATUS` of the todo. 

409 resources: The :attr:`RESOURCES` of the todo. 

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

411 stamp: The :attr:`~icalendar.cal.component.Component.DTSTAMP` of the todo. 

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

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

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

415 subcomponents: The subcomponents of the todo. 

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

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

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

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

420 

421 Returns: 

422 :class:`Todo` 

423 

424 Raises: 

425 ~icalendar.error.InvalidCalendar: If the content is not valid 

426 according to :rfc:`5545`. 

427 

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

429 """ 

430 todo: Self = super().new( 

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

432 created=created, 

433 last_modified=last_modified, 

434 comments=comments, 

435 links=links, 

436 related_to=related_to, 

437 refids=refids, 

438 concepts=concepts, 

439 subcomponents=subcomponents, 

440 ) 

441 todo.summary = summary 

442 todo.description = description 

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

444 todo.start = start 

445 todo.end = end 

446 todo.color = color 

447 todo.categories = categories 

448 todo.sequence = sequence 

449 todo.classification = classification 

450 todo.url = url 

451 todo.organizer = organizer 

452 todo.location = location 

453 todo.priority = priority 

454 todo.attachments = attachments 

455 todo.contacts = contacts 

456 todo.status = status 

457 todo.REQUEST_STATUS = request_status 

458 todo.RESOURCES = resources 

459 todo.attendees = attendees 

460 todo.conferences = conferences 

461 todo.RECURRENCE_ID = recurrence_id 

462 

463 if cls._validate_new: 

464 cls._validate_start_and_end(start, end) 

465 return todo 

466 

467 @classmethod 

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

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

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

471 

472 

473__all__ = ["Todo"]