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

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

102 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 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 icalendar.alarms import Alarms 

52 from icalendar.enums import CLASS, STATUS 

53 from icalendar.prop import vCalAddress 

54 from icalendar.prop.conference import Conference 

55 

56 

57class Todo(Component): 

58 """ 

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

60 properties that represents an action item or assignment. For 

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

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

63 seminar on Internet Calendaring". 

64 

65 Examples: 

66 Create a new Todo: 

67 

68 >>> from icalendar import Todo 

69 >>> todo = Todo.new() 

70 >>> print(todo.to_ical()) 

71 BEGIN:VTODO 

72 DTSTAMP:20250517T080612Z 

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

74 END:VTODO 

75 

76 Complete the example Todo. 

77 

78 .. code-block:: pycon 

79 

80 >>> from datetime import datetime, timezone 

81 >>> from icalendar import Todo, STATUS 

82 >>> todo = Todo.example() 

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

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

85 >>> todo.status = STATUS.COMPLETED 

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

87 BEGIN:VTODO 

88 CATEGORIES:FAMILY,FINANCE 

89 CLASS:CONFIDENTIAL 

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

91 DTSTAMP:20070313T123432Z 

92 DUE;VALUE=DATE:20070501 

93 PERCENT-COMPLETE:100 

94 STATUS:COMPLETED 

95 SUMMARY:Submit Quebec Income Tax Return for 2006 

96 UID:20070313T123432Z-456553@example.com 

97 END:VTODO 

98 

99 """ 

100 

101 name = "VTODO" 

102 

103 required = ( 

104 "UID", 

105 "DTSTAMP", 

106 ) 

107 singletons = ( 

108 "CLASS", 

109 "COLOR", 

110 "COMPLETED", 

111 "CREATED", 

112 "DESCRIPTION", 

113 "DTSTAMP", 

114 "DTSTART", 

115 "GEO", 

116 "LAST-MODIFIED", 

117 "LOCATION", 

118 "ORGANIZER", 

119 "PERCENT-COMPLETE", 

120 "PRIORITY", 

121 "RECURRENCE-ID", 

122 "SEQUENCE", 

123 "STATUS", 

124 "SUMMARY", 

125 "UID", 

126 "URL", 

127 "DUE", 

128 "DURATION", 

129 ) 

130 exclusive = ( 

131 "DUE", 

132 "DURATION", 

133 ) 

134 multiple = ( 

135 "ATTACH", 

136 "ATTENDEE", 

137 "CATEGORIES", 

138 "COMMENT", 

139 "CONTACT", 

140 "EXDATE", 

141 "RSTATUS", 

142 "RELATED", 

143 "RESOURCES", 

144 "RDATE", 

145 "RRULE", 

146 ) 

147 DTSTART = create_single_property( 

148 "DTSTART", 

149 "dt", 

150 (datetime, date), 

151 date, 

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

153 ) 

154 DUE = create_single_property( 

155 "DUE", 

156 "dt", 

157 (datetime, date), 

158 date, 

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

160 ) 

161 DURATION = property( 

162 property_get_duration, 

163 property_set_duration, 

164 property_del_duration, 

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

166 ) 

167 

168 def _get_start_end_duration(self): 

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

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

171 

172 @property 

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

174 """The start of the VTODO. 

175 

176 Invalid values raise an InvalidCalendar. 

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

178 

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

180 

181 >>> from datetime import datetime 

182 >>> from icalendar import Todo 

183 >>> todo = Todo() 

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

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

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

187 datetime.timedelta(seconds=1800) 

188 >>> print(todo.to_ical()) 

189 BEGIN:VTODO 

190 DTSTART:20210101T120000 

191 DUE:20210101T123000 

192 END:VTODO 

193 """ 

194 return get_start_property(self) 

195 

196 @start.setter 

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

198 """Set the start.""" 

199 self.DTSTART = start 

200 

201 @property 

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

203 """The end of the todo. 

204 

205 Invalid values raise an InvalidCalendar error. 

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

207 """ 

208 return get_end_property(self, "DUE") 

209 

210 @end.setter 

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

212 """Set the end.""" 

213 self.DUE = end 

214 

215 @property 

216 def duration(self) -> timedelta: 

217 """The duration of the VTODO. 

218 

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

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

221 start locked. 

222 

223 Setting the duration will: 

224 

225 1. Keep the start time locked (unchanged) 

226 2. Adjust the end time to start + duration 

227 3. Remove any existing DUE property 

228 4. Set the DURATION property 

229 """ 

230 return get_duration_property(self) 

231 

232 @duration.setter 

233 def duration(self, value: timedelta): 

234 if not isinstance(value, timedelta): 

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

236 

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

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

239 

240 def set_duration( 

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

242 ): 

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

244 

245 Args: 

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

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

248 """ 

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

250 

251 def set_start( 

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

253 ): 

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

255 

256 Args: 

257 start: The start time to set 

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

259 for auto-detect) 

260 """ 

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

262 

263 def set_end( 

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

265 ): 

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

267 

268 Args: 

269 end: The end time to set 

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

271 """ 

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

273 

274 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

275 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

276 

277 @property 

278 def alarms(self) -> Alarms: 

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

280 

281 >>> from datetime import datetime 

282 >>> from icalendar import Todo 

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

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

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

286 0 

287 

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

289 RDATE, EXDATE, and RRULE properties. 

290 """ 

291 from icalendar.alarms import Alarms 

292 

293 return Alarms(self) 

294 

295 color = color_property 

296 sequence = sequence_property 

297 categories = categories_property 

298 rdates = rdates_property 

299 exdates = exdates_property 

300 rrules = rrules_property 

301 uid = uid_property 

302 summary = summary_property 

303 description = description_property 

304 classification = class_property 

305 url = url_property 

306 organizer = organizer_property 

307 location = location_property 

308 priority = priority_property 

309 contacts = contacts_property 

310 status = status_property 

311 attendees = attendees_property 

312 images = images_property 

313 conferences = conferences_property 

314 

315 @classmethod 

316 def new( 

317 cls, 

318 /, 

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

320 categories: Sequence[str] = (), 

321 classification: CLASS | None = None, 

322 color: str | None = None, 

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

324 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

327 created: date | None = None, 

328 description: str | None = None, 

329 end: date | datetime | None = None, 

330 last_modified: date | None = None, 

331 links: LINKS_TYPE_SETTER = None, 

332 location: str | None = None, 

333 organizer: vCalAddress | str | None = None, 

334 priority: int | None = None, 

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

336 related_to: RELATED_TO_TYPE_SETTER = None, 

337 sequence: int | None = None, 

338 stamp: date | None = None, 

339 start: date | datetime | None = None, 

340 status: STATUS | None = None, 

341 summary: str | None = None, 

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

343 url: str | None = None, 

344 ): 

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

346 

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

348 

349 Arguments: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

376 

377 Returns: 

378 :class:`Todo` 

379 

380 Raises: 

381 ~error.InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

382 

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

384 """ 

385 todo: Todo = super().new( 

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

387 created=created, 

388 last_modified=last_modified, 

389 comments=comments, 

390 links=links, 

391 related_to=related_to, 

392 refids=refids, 

393 concepts=concepts, 

394 ) 

395 todo.summary = summary 

396 todo.description = description 

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

398 todo.start = start 

399 todo.end = end 

400 todo.color = color 

401 todo.categories = categories 

402 todo.sequence = sequence 

403 todo.classification = classification 

404 todo.url = url 

405 todo.organizer = organizer 

406 todo.location = location 

407 todo.priority = priority 

408 todo.contacts = contacts 

409 todo.status = status 

410 todo.attendees = attendees 

411 todo.conferences = conferences 

412 

413 if cls._validate_new: 

414 cls._validate_start_and_end(start, end) 

415 return todo 

416 

417 

418 @classmethod 

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

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

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

422 

423__all__ = ["Todo"]