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

77 

78 name = "VTODO" 

79 

80 required = ( 

81 "UID", 

82 "DTSTAMP", 

83 ) 

84 singletons = ( 

85 "CLASS", 

86 "COLOR", 

87 "COMPLETED", 

88 "CREATED", 

89 "DESCRIPTION", 

90 "DTSTAMP", 

91 "DTSTART", 

92 "GEO", 

93 "LAST-MODIFIED", 

94 "LOCATION", 

95 "ORGANIZER", 

96 "PERCENT-COMPLETE", 

97 "PRIORITY", 

98 "RECURRENCE-ID", 

99 "SEQUENCE", 

100 "STATUS", 

101 "SUMMARY", 

102 "UID", 

103 "URL", 

104 "DUE", 

105 "DURATION", 

106 ) 

107 exclusive = ( 

108 "DUE", 

109 "DURATION", 

110 ) 

111 multiple = ( 

112 "ATTACH", 

113 "ATTENDEE", 

114 "CATEGORIES", 

115 "COMMENT", 

116 "CONTACT", 

117 "EXDATE", 

118 "RSTATUS", 

119 "RELATED", 

120 "RESOURCES", 

121 "RDATE", 

122 "RRULE", 

123 ) 

124 DTSTART = create_single_property( 

125 "DTSTART", 

126 "dt", 

127 (datetime, date), 

128 date, 

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

130 ) 

131 DUE = create_single_property( 

132 "DUE", 

133 "dt", 

134 (datetime, date), 

135 date, 

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

137 ) 

138 DURATION = property( 

139 property_get_duration, 

140 property_set_duration, 

141 property_del_duration, 

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

143 ) 

144 

145 def _get_start_end_duration(self): 

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

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

148 

149 @property 

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

151 """The start of the VTODO. 

152 

153 Invalid values raise an InvalidCalendar. 

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

155 

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

157 

158 >>> from datetime import datetime 

159 >>> from icalendar import Todo 

160 >>> todo = Todo() 

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

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

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

164 datetime.timedelta(seconds=1800) 

165 >>> print(todo.to_ical()) 

166 BEGIN:VTODO 

167 DTSTART:20210101T120000 

168 DUE:20210101T123000 

169 END:VTODO 

170 """ 

171 return get_start_property(self) 

172 

173 @start.setter 

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

175 """Set the start.""" 

176 self.DTSTART = start 

177 

178 @property 

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

180 """The end of the todo. 

181 

182 Invalid values raise an InvalidCalendar error. 

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

184 """ 

185 return get_end_property(self, "DUE") 

186 

187 @end.setter 

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

189 """Set the end.""" 

190 self.DUE = end 

191 

192 @property 

193 def duration(self) -> timedelta: 

194 """The duration of the VTODO. 

195 

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

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

198 start locked. 

199 

200 Setting the duration will: 

201 

202 1. Keep the start time locked (unchanged) 

203 2. Adjust the end time to start + duration 

204 3. Remove any existing DUE property 

205 4. Set the DURATION property 

206 """ 

207 return get_duration_property(self) 

208 

209 @duration.setter 

210 def duration(self, value: timedelta): 

211 if not isinstance(value, timedelta): 

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

213 

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

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

216 

217 def set_duration( 

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

219 ): 

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

221 

222 Args: 

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

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

225 """ 

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

227 

228 def set_start( 

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

230 ): 

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

232 

233 Args: 

234 start: The start time to set 

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

236 for auto-detect) 

237 """ 

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

239 

240 def set_end( 

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

242 ): 

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

244 

245 Args: 

246 end: The end time to set 

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

248 """ 

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

250 

251 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

252 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

253 

254 @property 

255 def alarms(self) -> Alarms: 

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

257 

258 >>> from datetime import datetime 

259 >>> from icalendar import Todo 

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

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

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

263 0 

264 

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

266 RDATE, EXDATE, and RRULE properties. 

267 """ 

268 from icalendar.alarms import Alarms 

269 

270 return Alarms(self) 

271 

272 color = color_property 

273 sequence = sequence_property 

274 categories = categories_property 

275 rdates = rdates_property 

276 exdates = exdates_property 

277 rrules = rrules_property 

278 uid = uid_property 

279 summary = summary_property 

280 description = description_property 

281 classification = class_property 

282 url = url_property 

283 organizer = organizer_property 

284 location = location_property 

285 priority = priority_property 

286 contacts = contacts_property 

287 status = status_property 

288 attendees = attendees_property 

289 images = images_property 

290 conferences = conferences_property 

291 

292 @classmethod 

293 def new( 

294 cls, 

295 /, 

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

297 categories: Sequence[str] = (), 

298 classification: CLASS | None = None, 

299 color: str | None = None, 

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

301 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

304 created: date | None = None, 

305 description: str | None = None, 

306 end: date | datetime | None = None, 

307 last_modified: date | None = None, 

308 links: LINKS_TYPE_SETTER = None, 

309 location: str | None = None, 

310 organizer: vCalAddress | str | None = None, 

311 priority: int | None = None, 

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

313 related_to: RELATED_TO_TYPE_SETTER = None, 

314 sequence: int | None = None, 

315 stamp: date | None = None, 

316 start: date | datetime | None = None, 

317 status: STATUS | None = None, 

318 summary: str | None = None, 

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

320 url: str | None = None, 

321 ): 

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

323 

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

325 

326 Arguments: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

353 

354 Returns: 

355 :class:`Todo` 

356 

357 Raises: 

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

359 

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

361 """ 

362 todo: Todo = super().new( 

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

364 created=created, 

365 last_modified=last_modified, 

366 comments=comments, 

367 links=links, 

368 related_to=related_to, 

369 refids=refids, 

370 concepts=concepts, 

371 ) 

372 todo.summary = summary 

373 todo.description = description 

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

375 todo.start = start 

376 todo.end = end 

377 todo.color = color 

378 todo.categories = categories 

379 todo.sequence = sequence 

380 todo.classification = classification 

381 todo.url = url 

382 todo.organizer = organizer 

383 todo.location = location 

384 todo.priority = priority 

385 todo.contacts = contacts 

386 todo.status = status 

387 todo.attendees = attendees 

388 todo.conferences = conferences 

389 

390 if cls._validate_new: 

391 cls._validate_start_and_end(start, end) 

392 return todo 

393 

394 @classmethod 

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

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

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

398 

399__all__ = ["Todo"]