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

98 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 

48 

49if TYPE_CHECKING: 

50 from icalendar.alarms import Alarms 

51 from icalendar.enums import CLASS, STATUS 

52 from icalendar.prop import vCalAddress 

53 from icalendar.prop.conference import Conference 

54 

55 

56class Todo(Component): 

57 """ 

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

59 properties that represents an action item or assignment. For 

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

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

62 seminar on Internet Calendaring". 

63 

64 Examples: 

65 Create a new Todo: 

66 

67 >>> from icalendar import Todo 

68 >>> todo = Todo.new() 

69 >>> print(todo.to_ical()) 

70 BEGIN:VTODO 

71 DTSTAMP:20250517T080612Z 

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

73 END:VTODO 

74 

75 """ 

76 

77 name = "VTODO" 

78 

79 required = ( 

80 "UID", 

81 "DTSTAMP", 

82 ) 

83 singletons = ( 

84 "CLASS", 

85 "COLOR", 

86 "COMPLETED", 

87 "CREATED", 

88 "DESCRIPTION", 

89 "DTSTAMP", 

90 "DTSTART", 

91 "GEO", 

92 "LAST-MODIFIED", 

93 "LOCATION", 

94 "ORGANIZER", 

95 "PERCENT-COMPLETE", 

96 "PRIORITY", 

97 "RECURRENCE-ID", 

98 "SEQUENCE", 

99 "STATUS", 

100 "SUMMARY", 

101 "UID", 

102 "URL", 

103 "DUE", 

104 "DURATION", 

105 ) 

106 exclusive = ( 

107 "DUE", 

108 "DURATION", 

109 ) 

110 multiple = ( 

111 "ATTACH", 

112 "ATTENDEE", 

113 "CATEGORIES", 

114 "COMMENT", 

115 "CONTACT", 

116 "EXDATE", 

117 "RSTATUS", 

118 "RELATED", 

119 "RESOURCES", 

120 "RDATE", 

121 "RRULE", 

122 ) 

123 DTSTART = create_single_property( 

124 "DTSTART", 

125 "dt", 

126 (datetime, date), 

127 date, 

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

129 ) 

130 DUE = create_single_property( 

131 "DUE", 

132 "dt", 

133 (datetime, date), 

134 date, 

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

136 ) 

137 DURATION = property( 

138 property_get_duration, 

139 property_set_duration, 

140 property_del_duration, 

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

142 ) 

143 

144 def _get_start_end_duration(self): 

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

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

147 

148 @property 

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

150 """The start of the VTODO. 

151 

152 Invalid values raise an InvalidCalendar. 

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

154 

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

156 

157 >>> from datetime import datetime 

158 >>> from icalendar import Todo 

159 >>> todo = Todo() 

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

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

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

163 datetime.timedelta(seconds=1800) 

164 >>> print(todo.to_ical()) 

165 BEGIN:VTODO 

166 DTSTART:20210101T120000 

167 DUE:20210101T123000 

168 END:VTODO 

169 """ 

170 return get_start_property(self) 

171 

172 @start.setter 

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

174 """Set the start.""" 

175 self.DTSTART = start 

176 

177 @property 

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

179 """The end of the todo. 

180 

181 Invalid values raise an InvalidCalendar error. 

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

183 """ 

184 return get_end_property(self, "DUE") 

185 

186 @end.setter 

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

188 """Set the end.""" 

189 self.DUE = end 

190 

191 @property 

192 def duration(self) -> timedelta: 

193 """The duration of the VTODO. 

194 

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

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

197 start locked. 

198 

199 Setting the duration will: 

200 1. Keep the start time locked (unchanged) 

201 2. Adjust the end time to start + duration 

202 3. Remove any existing DUE property 

203 4. Set the DURATION property 

204 """ 

205 return get_duration_property(self) 

206 

207 @duration.setter 

208 def duration(self, value: timedelta): 

209 if not isinstance(value, timedelta): 

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

211 

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

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

214 

215 def set_duration( 

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

217 ): 

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

219 

220 Args: 

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

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

223 """ 

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

225 

226 def set_start( 

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

228 ): 

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

230 

231 Args: 

232 start: The start time to set 

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

234 for auto-detect) 

235 """ 

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

237 

238 def set_end( 

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

240 ): 

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

242 

243 Args: 

244 end: The end time to set 

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

246 """ 

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

248 

249 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

250 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

251 

252 @property 

253 def alarms(self) -> Alarms: 

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

255 

256 >>> from datetime import datetime 

257 >>> from icalendar import Todo 

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

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

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

261 0 

262 

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

264 RDATE, EXDATE, and RRULE properties. 

265 """ 

266 from icalendar.alarms import Alarms 

267 

268 return Alarms(self) 

269 

270 color = color_property 

271 sequence = sequence_property 

272 categories = categories_property 

273 rdates = rdates_property 

274 exdates = exdates_property 

275 rrules = rrules_property 

276 uid = uid_property 

277 summary = summary_property 

278 description = description_property 

279 classification = class_property 

280 url = url_property 

281 organizer = organizer_property 

282 location = location_property 

283 priority = priority_property 

284 contacts = contacts_property 

285 status = status_property 

286 attendees = attendees_property 

287 images = images_property 

288 conferences = conferences_property 

289 

290 @classmethod 

291 def new( 

292 cls, 

293 /, 

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

295 categories: Sequence[str] = (), 

296 classification: CLASS | None = None, 

297 color: str | None = None, 

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

299 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

302 created: date | None = None, 

303 description: str | None = None, 

304 end: date | datetime | None = None, 

305 last_modified: date | None = None, 

306 links: LINKS_TYPE_SETTER = None, 

307 location: str | None = None, 

308 organizer: vCalAddress | str | None = None, 

309 priority: int | None = None, 

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

311 related_to: RELATED_TO_TYPE_SETTER = None, 

312 sequence: int | None = None, 

313 stamp: date | None = None, 

314 start: date | datetime | None = None, 

315 status: STATUS | None = None, 

316 summary: str | None = None, 

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

318 url: str | None = None, 

319 ): 

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

321 

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

323 

324 Arguments: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

351 

352 Returns: 

353 :class:`Todo` 

354 

355 Raises: 

356 InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

357 

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

359 """ 

360 todo: Todo = super().new( 

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

362 created=created, 

363 last_modified=last_modified, 

364 comments=comments, 

365 links=links, 

366 related_to=related_to, 

367 refids=refids, 

368 concepts=concepts, 

369 ) 

370 todo.summary = summary 

371 todo.description = description 

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

373 todo.start = start 

374 todo.end = end 

375 todo.color = color 

376 todo.categories = categories 

377 todo.sequence = sequence 

378 todo.classification = classification 

379 todo.url = url 

380 todo.organizer = organizer 

381 todo.location = location 

382 todo.priority = priority 

383 todo.contacts = contacts 

384 todo.status = status 

385 todo.attendees = attendees 

386 todo.conferences = conferences 

387 

388 if cls._validate_new: 

389 cls._validate_start_and_end(start, end) 

390 return todo 

391 

392 

393__all__ = ["Todo"]