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

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

112 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.""" 

207 self.DTSTART = start 

208 

209 @property 

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

211 """The end of the todo. 

212 

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

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

215 """ 

216 return get_end_property(self, "DUE") 

217 

218 @end.setter 

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

220 """Set the end.""" 

221 self.DUE = end 

222 

223 @property 

224 def duration(self) -> timedelta: 

225 """The duration of the VTODO. 

226 

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

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

229 start locked. 

230 

231 Setting the duration will do the following. 

232 

233 1. Keep the start time locked (unchanged) 

234 2. Adjust the end time to start + duration 

235 3. Remove any existing DUE property 

236 4. Set the DURATION property 

237 """ 

238 return get_duration_property(self) 

239 

240 @duration.setter 

241 def duration(self, value: timedelta): 

242 if not isinstance(value, timedelta): 

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

244 

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

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

247 

248 def set_duration( 

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

250 ): 

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

252 

253 Parameters: 

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

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

256 """ 

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

258 

259 def set_start( 

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

261 ): 

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

263 

264 Parameters: 

265 start: The start time to set 

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

267 for auto-detect) 

268 """ 

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

270 

271 def set_end( 

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

273 ): 

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

275 

276 Parameters: 

277 end: The end time to set 

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

279 """ 

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

281 

282 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

283 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

284 

285 @property 

286 def alarms(self) -> Alarms: 

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

288 

289 >>> from datetime import datetime 

290 >>> from icalendar import Todo 

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

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

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

294 0 

295 

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

297 RDATE, EXDATE, and RRULE properties. 

298 """ 

299 from icalendar.alarms import Alarms 

300 

301 return Alarms(self) 

302 

303 color = color_property 

304 sequence = sequence_property 

305 categories = categories_property 

306 rdates = rdates_property 

307 exdates = exdates_property 

308 rrules = rrules_property 

309 REQUEST_STATUS = REQUEST_STATUS_property 

310 RESOURCES = RESOURCES_property 

311 uid = uid_property 

312 summary = summary_property 

313 description = description_property 

314 classification = class_property 

315 url = url_property 

316 organizer = organizer_property 

317 location = location_property 

318 priority = priority_property 

319 contacts = contacts_property 

320 status = status_property 

321 attendees = attendees_property 

322 attachments = attachments_property 

323 images = images_property 

324 conferences = conferences_property 

325 from icalendar.attr import RECURRENCE_ID 

326 

327 @classmethod 

328 def new( 

329 cls, 

330 /, 

331 attachments: ATTACHMENTS_TYPE_SETTER = None, 

332 attendees: ATTENDEE_TYPE_SETTER = None, 

333 categories: Sequence[str] = (), 

334 classification: CLASS | None = None, 

335 color: str | None = None, 

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

337 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

340 created: date | None = None, 

341 description: str | None = None, 

342 end: date | datetime | None = None, 

343 last_modified: date | None = None, 

344 links: LINKS_TYPE_SETTER = None, 

345 location: str | None = None, 

346 organizer: vCalAddress | str | None = None, 

347 priority: int | None = None, 

348 recurrence_id: date | datetime | None = None, 

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

350 related_to: RELATED_TO_TYPE_SETTER = None, 

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

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

353 sequence: int | None = None, 

354 stamp: date | None = None, 

355 start: date | datetime | None = None, 

356 status: STATUS | None = None, 

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

358 summary: str | None = None, 

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

360 url: str | None = None, 

361 ) -> Self: 

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

363 

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

365 

366 Parameters: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

393 subcomponents: The subcomponents of the todo. 

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

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

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

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

398 

399 Returns: 

400 :class:`Todo` 

401 

402 Raises: 

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

404 according to :rfc:`5545`. 

405 

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

407 """ 

408 todo: Self = super().new( 

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

410 created=created, 

411 last_modified=last_modified, 

412 comments=comments, 

413 links=links, 

414 related_to=related_to, 

415 refids=refids, 

416 concepts=concepts, 

417 subcomponents=subcomponents, 

418 ) 

419 todo.summary = summary 

420 todo.description = description 

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

422 todo.start = start 

423 todo.end = end 

424 todo.color = color 

425 todo.categories = categories 

426 todo.sequence = sequence 

427 todo.classification = classification 

428 todo.url = url 

429 todo.organizer = organizer 

430 todo.location = location 

431 todo.priority = priority 

432 todo.attachments = attachments 

433 todo.contacts = contacts 

434 todo.status = status 

435 todo.REQUEST_STATUS = request_status 

436 todo.RESOURCES = resources 

437 todo.attendees = attendees 

438 todo.conferences = conferences 

439 todo.RECURRENCE_ID = recurrence_id 

440 

441 if cls._validate_new: 

442 cls._validate_start_and_end(start, end) 

443 return todo 

444 

445 @classmethod 

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

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

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

449 

450 

451__all__ = ["Todo"]