Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 29%

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

562 statements  

1"""Attributes of Components and properties.""" 

2 

3from __future__ import annotations 

4 

5import itertools 

6from collections.abc import Sequence 

7from datetime import date, datetime, timedelta 

8from typing import TYPE_CHECKING, Literal, TypeAlias 

9 

10from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum 

11from icalendar.error import IncompleteComponent, InvalidCalendar 

12from icalendar.parser_tools import SEQUENCE_TYPES 

13from icalendar.prop import ( 

14 vBinary, 

15 vCalAddress, 

16 vCategory, 

17 vDDDTypes, 

18 vDuration, 

19 vRecur, 

20 vText, 

21 vUid, 

22 vUnknown, 

23 vUri, 

24 vXmlReference, 

25) 

26from icalendar.prop.conference import Conference 

27from icalendar.prop.image import Image 

28from icalendar.timezone import tzp 

29from icalendar.tools import is_date 

30 

31if TYPE_CHECKING: 

32 from collections.abc import Callable 

33 

34 from icalendar.cal import Component 

35 

36 

37def _get_rdates( 

38 self: Component, 

39) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]: 

40 """The RDATE property defines the list of DATE-TIME values for recurring components. 

41 

42 RDATE is defined in :rfc:`5545`. 

43 The return value is a list of tuples ``(start, end)``. 

44 

45 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`, 

46 with and without timezone. 

47 

48 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime` 

49 if the end is specified. 

50 

51 Value Type: 

52 The default value type for this property is DATE-TIME. 

53 The value type can be set to DATE or PERIOD. 

54 

55 Property Parameters: 

56 IANA, non-standard, value data type, and time 

57 zone identifier property parameters can be specified on this 

58 property. 

59 

60 Conformance: 

61 This property can be specified in recurring "VEVENT", 

62 "VTODO", and "VJOURNAL" calendar components as well as in the 

63 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

64 calendar component. 

65 

66 Description: 

67 This property can appear along with the "RRULE" 

68 property to define an aggregate set of repeating occurrences. 

69 When they both appear in a recurring component, the recurrence 

70 instances are defined by the union of occurrences defined by both 

71 the "RDATE" and "RRULE". 

72 

73 The recurrence dates, if specified, are used in computing the 

74 recurrence set. The recurrence set is the complete set of 

75 recurrence instances for a calendar component. The recurrence set 

76 is generated by considering the initial "DTSTART" property along 

77 with the "RRULE", "RDATE", and "EXDATE" properties contained 

78 within the recurring component. The "DTSTART" property defines 

79 the first instance in the recurrence set. The "DTSTART" property 

80 value SHOULD match the pattern of the recurrence rule, if 

81 specified. The recurrence set generated with a "DTSTART" property 

82 value that doesn't match the pattern of the rule is undefined. 

83 The final recurrence set is generated by gathering all of the 

84 start DATE-TIME values generated by any of the specified "RRULE" 

85 and "RDATE" properties, and then excluding any start DATE-TIME 

86 values specified by "EXDATE" properties. This implies that start 

87 DATE-TIME values specified by "EXDATE" properties take precedence 

88 over those specified by inclusion properties (i.e., "RDATE" and 

89 "RRULE"). Where duplicate instances are generated by the "RRULE" 

90 and "RDATE" properties, only one recurrence is considered. 

91 Duplicate instances are ignored. 

92 

93 Example: 

94 Below, we set one RDATE in a list and get the resulting tuple of start and end. 

95 

96 .. code-block:: pycon 

97 

98 >>> from icalendar import Event 

99 >>> from datetime import datetime 

100 >>> event = Event() 

101 

102 # Add a list of recurrence dates 

103 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)]) 

104 >>> event.rdates 

105 [(datetime.datetime(2025, 4, 28, 16, 5), None)] 

106 

107 .. note:: 

108 

109 Modifying the returned list does not change the RDATE value. Assign to 

110 ``rdates`` for the relevant component or use 

111 :meth:`Component.add <icalendar.cal.component.Component.add>` instead. 

112 If you want to compute recurrences, have a look at 

113 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

114 

115 """ 

116 result = [] 

117 rdates = self.get("RDATE", []) 

118 for rdates in (rdates,) if not isinstance(rdates, list) else rdates: 

119 for dts in rdates.dts: 

120 rdate = dts.dt 

121 if isinstance(rdate, tuple): 

122 # we have a period as rdate 

123 if isinstance(rdate[1], timedelta): 

124 result.append((rdate[0], rdate[0] + rdate[1])) 

125 else: 

126 result.append(rdate) 

127 else: 

128 # we have a date/datetime 

129 result.append((rdate, None)) 

130 return result 

131 

132 

133def _set_rdates(self: Component, value) -> None: 

134 """Set the RDATE values, replacing any existing ones. 

135 

136 ``value`` is a list as returned by :attr:`rdates` (each item a date, a 

137 datetime, or a ``(start, end)`` period tuple). Setting an empty list or 

138 :obj:`None` removes the RDATE property. 

139 """ 

140 _del_rdates(self) 

141 if value: 

142 self.add("RDATE", value) 

143 

144 

145def _del_rdates(self: Component) -> None: 

146 """Delete all RDATE values.""" 

147 self.pop("RDATE", None) 

148 

149 

150rdates_property = property(_get_rdates, _set_rdates, _del_rdates) 

151 

152 

153def _get_exdates(self: Component) -> list[date | datetime]: 

154 """EXDATE defines the list of DATE-TIME exceptions for recurring components. 

155 

156 EXDATE is defined in :rfc:`5545`. 

157 

158 Value Type: 

159 The default value type for this property is DATE-TIME. 

160 The value type can be set to DATE. 

161 

162 Property Parameters: 

163 IANA, non-standard, value data type, and time 

164 zone identifier property parameters can be specified on this 

165 property. 

166 

167 Conformance: 

168 This property can be specified in recurring "VEVENT", 

169 "VTODO", and "VJOURNAL" calendar components as well as in the 

170 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

171 calendar component. 

172 

173 Description: 

174 The exception dates, if specified, are used in 

175 computing the recurrence set. The recurrence set is the complete 

176 set of recurrence instances for a calendar component. The 

177 recurrence set is generated by considering the initial "DTSTART" 

178 property along with the "RRULE", "RDATE", and "EXDATE" properties 

179 contained within the recurring component. The "DTSTART" property 

180 defines the first instance in the recurrence set. The "DTSTART" 

181 property value SHOULD match the pattern of the recurrence rule, if 

182 specified. The recurrence set generated with a "DTSTART" property 

183 value that doesn't match the pattern of the rule is undefined. 

184 The final recurrence set is generated by gathering all of the 

185 start DATE-TIME values generated by any of the specified "RRULE" 

186 and "RDATE" properties, and then excluding any start DATE-TIME 

187 values specified by "EXDATE" properties. This implies that start 

188 DATE-TIME values specified by "EXDATE" properties take precedence 

189 over those specified by inclusion properties (i.e., "RDATE" and 

190 "RRULE"). When duplicate instances are generated by the "RRULE" 

191 and "RDATE" properties, only one recurrence is considered. 

192 Duplicate instances are ignored. 

193 

194 The "EXDATE" property can be used to exclude the value specified 

195 in "DTSTART". However, in such cases, the original "DTSTART" date 

196 MUST still be maintained by the calendaring and scheduling system 

197 because the original "DTSTART" value has inherent usage 

198 dependencies by other properties such as the "RECURRENCE-ID". 

199 

200 Example: 

201 Below, we add an exdate in a list and get the resulting list of exdates. 

202 

203 .. code-block:: pycon 

204 

205 >>> from icalendar import Event 

206 >>> from datetime import datetime 

207 >>> event = Event() 

208 

209 # Add a list of excluded dates 

210 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)]) 

211 >>> event.exdates 

212 [datetime.datetime(2025, 4, 28, 16, 5)] 

213 

214 .. note:: 

215 

216 Modifying the returned list does not change the EXDATE value. Assign to 

217 ``exdates`` for the relevant component or use 

218 :meth:`Component.add <icalendar.cal.component.Component.add>` instead. 

219 If you want to compute recurrences, have a look at 

220 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

221 

222 """ 

223 result = [] 

224 exdates = self.get("EXDATE", []) 

225 for exdates in (exdates,) if not isinstance(exdates, list) else exdates: 

226 for dts in exdates.dts: 

227 exdate = dts.dt 

228 # we have a date/datetime 

229 result.append(exdate) 

230 return result 

231 

232 

233def _set_exdates(self: Component, value) -> None: 

234 """Set the EXDATE values, replacing any existing ones. 

235 

236 ``value`` is a list as returned by :attr:`exdates` (each item a date or a 

237 datetime). Setting an empty list or :obj:`None` removes the EXDATE property. 

238 """ 

239 _del_exdates(self) 

240 if value: 

241 self.add("EXDATE", value) 

242 

243 

244def _del_exdates(self: Component) -> None: 

245 """Delete all EXDATE values.""" 

246 self.pop("EXDATE", None) 

247 

248 

249exdates_property = property(_get_exdates, _set_exdates, _del_exdates) 

250 

251 

252def _get_rrules(self: Component) -> list[vRecur]: 

253 """RRULE defines a rule or repeating pattern for recurring components. 

254 

255 RRULE is defined in :rfc:`5545`. 

256 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`. 

257 

258 Property Parameters: 

259 IANA and non-standard property parameters can 

260 be specified on this property. 

261 

262 Conformance: 

263 This property can be specified in recurring "VEVENT", 

264 "VTODO", and "VJOURNAL" calendar components as well as in the 

265 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

266 calendar component, but it SHOULD NOT be specified more than once. 

267 The recurrence set generated with multiple "RRULE" properties is 

268 undefined. 

269 

270 Description: 

271 The recurrence rule, if specified, is used in computing 

272 the recurrence set. The recurrence set is the complete set of 

273 recurrence instances for a calendar component. The recurrence set 

274 is generated by considering the initial "DTSTART" property along 

275 with the "RRULE", "RDATE", and "EXDATE" properties contained 

276 within the recurring component. The "DTSTART" property defines 

277 the first instance in the recurrence set. The "DTSTART" property 

278 value SHOULD be synchronized with the recurrence rule, if 

279 specified. The recurrence set generated with a "DTSTART" property 

280 value not synchronized with the recurrence rule is undefined. The 

281 final recurrence set is generated by gathering all of the start 

282 DATE-TIME values generated by any of the specified "RRULE" and 

283 "RDATE" properties, and then excluding any start DATE-TIME values 

284 specified by "EXDATE" properties. This implies that start DATE- 

285 TIME values specified by "EXDATE" properties take precedence over 

286 those specified by inclusion properties (i.e., "RDATE" and 

287 "RRULE"). Where duplicate instances are generated by the "RRULE" 

288 and "RDATE" properties, only one recurrence is considered. 

289 Duplicate instances are ignored. 

290 

291 The "DTSTART" property specified within the iCalendar object 

292 defines the first instance of the recurrence. In most cases, a 

293 "DTSTART" property of DATE-TIME value type used with a recurrence 

294 rule, should be specified as a date with local time and time zone 

295 reference to make sure all the recurrence instances start at the 

296 same local time regardless of time zone changes. 

297 

298 If the duration of the recurring component is specified with the 

299 "DTEND" or "DUE" property, then the same exact duration will apply 

300 to all the members of the generated recurrence set. Else, if the 

301 duration of the recurring component is specified with the 

302 "DURATION" property, then the same nominal duration will apply to 

303 all the members of the generated recurrence set and the exact 

304 duration of each recurrence instance will depend on its specific 

305 start time. For example, recurrence instances of a nominal 

306 duration of one day will have an exact duration of more or less 

307 than 24 hours on a day where a time zone shift occurs. The 

308 duration of a specific recurrence may be modified in an exception 

309 component or simply by using an "RDATE" property of PERIOD value 

310 type. 

311 

312 Examples: 

313 Daily for 10 occurrences: 

314 

315 .. code-block:: pycon 

316 

317 >>> from icalendar import Event 

318 >>> from datetime import datetime 

319 >>> from zoneinfo import ZoneInfo 

320 >>> event = Event() 

321 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) 

322 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10") 

323 >>> print(event.to_ical()) 

324 BEGIN:VEVENT 

325 DTSTART;TZID=America/New_York:19970902T090000 

326 RRULE:FREQ=DAILY;COUNT=10 

327 END:VEVENT 

328 >>> event.rrules 

329 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})] 

330 

331 Daily until December 24, 1997: 

332 

333 .. code-block:: pycon 

334 

335 >>> from icalendar import Event, vRecur 

336 >>> from datetime import datetime 

337 >>> from zoneinfo import ZoneInfo 

338 >>> event = Event() 

339 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) 

340 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC")))) 

341 >>> print(event.to_ical()) 

342 BEGIN:VEVENT 

343 DTSTART;TZID=America/New_York:19970902T090000 

344 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

345 END:VEVENT 

346 >>> event.rrules 

347 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})] 

348 

349 .. note:: 

350 

351 You cannot modify the RRULE value by modifying the result. 

352 Use :meth:`Component.add <icalendar.cal.component.Component.add>` to add values. 

353 

354 If you want to compute recurrences, have a look at 

355 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

356 

357 """ # noqa: E501 

358 rrules = self.get("RRULE", []) 

359 if not isinstance(rrules, list): 

360 return [rrules] 

361 return rrules 

362 

363 

364rrules_property = property(_get_rrules) 

365 

366 

367def multi_language_text_property( 

368 main_prop: str, compatibility_prop: str | None, doc: str 

369) -> property: 

370 """This creates a text property. 

371 

372 This property can be defined several times with different ``LANGUAGE`` parameters. 

373 

374 Parameters: 

375 main_prop (str): The property to set and get, such as ``NAME`` 

376 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME`` 

377 doc (str): The documentation string 

378 """ 

379 

380 def fget(self: Component) -> str | None: 

381 """Get the property""" 

382 result = self.get(main_prop) 

383 if result is None and compatibility_prop is not None: 

384 result = self.get(compatibility_prop) 

385 if isinstance(result, list): 

386 for item in result: 

387 if "LANGUAGE" not in item.params: 

388 return item 

389 return result 

390 

391 def fset(self: Component, value: str | None): 

392 """Set the property.""" 

393 fdel(self) 

394 if value is not None: 

395 self.add(main_prop, value) 

396 if compatibility_prop is not None: 

397 self.add(compatibility_prop, value) 

398 

399 def fdel(self: Component): 

400 """Delete the property.""" 

401 self.pop(main_prop, None) 

402 if compatibility_prop is not None: 

403 self.pop(compatibility_prop, None) 

404 

405 return property(fget, fset, fdel, doc) 

406 

407 

408def single_int_property( 

409 prop: str, default: int, doc: str, *, min_value: int | None = None 

410) -> property: 

411 """Create a property for an int value that exists only once. 

412 

413 Parameters: 

414 default: Required. The default value. 

415 doc: Required. The documentation string. 

416 prop: Required. The name of the property. 

417 min_value: If set, the value must be greater than or equal to this minimum. 

418 

419 .. versionadded:: 7.3.0 

420 Added the ``min_value`` parameter. 

421 """ 

422 

423 def fget(self: Component) -> int: 

424 """Get the property""" 

425 try: 

426 return int(self.get(prop, default)) 

427 except ValueError as e: 

428 raise InvalidCalendar(f"{prop} must be an int") from e 

429 

430 def fset(self: Component, value: int | None): 

431 """Set the property.""" 

432 if value is not None: 

433 if not isinstance(value, int) or isinstance(value, bool): 

434 raise TypeError(f"{prop} must be an int, got {value!r}") 

435 if min_value is not None and value < min_value: 

436 raise InvalidCalendar(f"{prop} must be >= {min_value}, got {value}") 

437 fdel(self) 

438 if value is not None: 

439 self.add(prop, value) 

440 

441 def fdel(self: Component): 

442 """Delete the property.""" 

443 self.pop(prop, None) 

444 

445 return property(fget, fset, fdel, doc) 

446 

447 

448def single_utc_property(name: str, docs: str) -> property: 

449 """Create a property to access a value of datetime in UTC timezone. 

450 

451 Parameters: 

452 name: name of the property 

453 docs: documentation string 

454 """ 

455 

456 def fget(self: Component) -> datetime | None: 

457 """Get the value.""" 

458 if name not in self: 

459 return None 

460 dt = self.get(name) 

461 if isinstance(dt, (vText, vUnknown)): 

462 # we might be in an attribute that is not typed 

463 value = vDDDTypes.from_ical(dt) 

464 else: 

465 value = getattr(dt, "dt", dt) 

466 if value is None or not isinstance(value, date): 

467 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}") 

468 return tzp.localize_utc(value) 

469 

470 def fset(self: Component, value: datetime | None): 

471 """Set the value""" 

472 if value is None: 

473 fdel(self) 

474 return 

475 if not isinstance(value, date): 

476 raise TypeError(f"{name} takes a datetime in UTC, not {value}") 

477 fdel(self) 

478 self.add(name, tzp.localize_utc(value)) 

479 

480 def fdel(self: Component): 

481 """Delete the property.""" 

482 self.pop(name, None) 

483 

484 return property(fget, fset, fdel, doc=docs) 

485 

486 

487def single_string_property( 

488 name: str, docs: str, other_name: str | list[str] | None = None, default: str = "" 

489) -> property: 

490 """Create a property to access a single string value.""" 

491 other_names = ( 

492 [] 

493 if other_name is None 

494 else [other_name] 

495 if isinstance(other_name, str) 

496 else list(other_name) 

497 ) 

498 

499 def fget(self: Component) -> str: 

500 """Get the value.""" 

501 result = self.get(name, None) 

502 if result is None: 

503 for alias in other_names: 

504 result = self.get(alias, None) 

505 if result is not None: 

506 break 

507 if result is None or result == []: 

508 return default 

509 if isinstance(result, list): 

510 return result[0] 

511 return result 

512 

513 def fset(self: Component, value: str | None): 

514 """Set the value. 

515 

516 Setting the value to None will delete it. 

517 """ 

518 fdel(self) 

519 if value is not None: 

520 self.add(name, value) 

521 

522 def fdel(self: Component): 

523 """Delete the property.""" 

524 self.pop(name, None) 

525 for alias in other_names: 

526 self.pop(alias, None) 

527 

528 return property(fget, fset, fdel, doc=docs) 

529 

530 

531color_property = single_string_property( 

532 "COLOR", 

533 """This property specifies a color used for displaying the component. 

534 

535 This implements :rfc:`7986` ``COLOR`` property. 

536 

537 Property Parameters: 

538 IANA and non-standard property parameters can 

539 be specified on this property. 

540 

541 Conformance: 

542 This property can be specified once in an iCalendar 

543 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components. 

544 

545 Description: 

546 This property specifies a color that clients MAY use 

547 when presenting the relevant data to a user. Typically, this 

548 would appear as the "background" color of events or tasks. The 

549 value is a case-insensitive color name taken from the CSS3 set of 

550 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_. 

551 

552 Example: 

553 ``"turquoise"``, ``"#ffffff"`` 

554 

555 .. code-block:: pycon 

556 

557 >>> from icalendar import Todo 

558 >>> todo = Todo() 

559 >>> todo.color = "green" 

560 >>> print(todo.to_ical()) 

561 BEGIN:VTODO 

562 COLOR:green 

563 END:VTODO 

564 """, 

565) 

566 

567sequence_property = single_int_property( 

568 "SEQUENCE", 

569 0, 

570 """This property defines the revision sequence number of the calendar component within a sequence of revisions. 

571 

572Value Type: 

573 INTEGER 

574 

575Property Parameters: 

576 IANA and non-standard property parameters can be specified on this property. 

577 

578Conformance: 

579 The property can be specified in "VEVENT", "VTODO", or 

580 "VJOURNAL" calendar component. 

581 

582Description: 

583 When a calendar component is created, its sequence 

584 number is 0. It is monotonically incremented by the "Organizer's" 

585 CUA each time the "Organizer" makes a significant revision to the 

586 calendar component. 

587 

588 The "Organizer" includes this property in an iCalendar object that 

589 it sends to an "Attendee" to specify the current version of the 

590 calendar component. 

591 

592 The "Attendee" includes this property in an iCalendar object that 

593 it sends to the "Organizer" to specify the version of the calendar 

594 component to which the "Attendee" is referring. 

595 

596 A change to the sequence number is not the mechanism that an 

597 "Organizer" uses to request a response from the "Attendees". The 

598 "RSVP" parameter on the "ATTENDEE" property is used by the 

599 "Organizer" to indicate that a response from the "Attendees" is 

600 requested. 

601 

602 Recurrence instances of a recurring component MAY have different 

603 sequence numbers. 

604 

605Examples: 

606 The following is an example of this property for a calendar 

607 component that was just created by the "Organizer": 

608 

609 .. code-block:: pycon 

610 

611 >>> from icalendar import Event 

612 >>> event = Event() 

613 >>> event.sequence 

614 0 

615 

616 The following is an example of this property for a calendar 

617 component that has been revised 10 different times by the 

618 "Organizer": 

619 

620 .. code-block:: pycon 

621 

622 >>> from icalendar import Calendar 

623 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal") 

624 >>> event = calendar.events[0] 

625 >>> event.sequence 

626 10 

627 

628 Raises: 

629 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

630 even though ``bool`` subclasses ``int``. 

631 

632 ~icalendar.error.InvalidCalendar: If the value is negative. 

633 

634 .. versionchanged:: 7.3.0 

635 Negative values are no longer accepted. 

636 """, # noqa: E501 

637 min_value=0, 

638) 

639 

640 

641def _get_categories(component: Component) -> list[str]: 

642 """Get all the categories.""" 

643 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES") 

644 if isinstance(categories, list): 

645 _set_categories( 

646 component, 

647 list(itertools.chain.from_iterable(cat.cats for cat in categories)), 

648 ) 

649 return _get_categories(component) 

650 if categories is None: 

651 categories = vCategory([]) 

652 component.add("CATEGORIES", categories) 

653 return categories.cats 

654 

655 

656def _set_categories(component: Component, cats: Sequence[str] | None) -> None: 

657 """Set the categories.""" 

658 if not cats and cats != []: 

659 _del_categories(component) 

660 return 

661 component["CATEGORIES"] = categories = vCategory(cats) 

662 if isinstance(cats, list): 

663 cats.clear() 

664 cats.extend(categories.cats) 

665 categories.cats = cats 

666 

667 

668def _del_categories(component: Component) -> None: 

669 """Delete the categories.""" 

670 component.pop("CATEGORIES", None) 

671 

672 

673categories_property = property( 

674 _get_categories, 

675 _set_categories, 

676 _del_categories, 

677 """This property defines the categories for a component. 

678 

679The categories property is used to specify categories or subtypes of the 

680calendar component. The categories are useful to search for a calendar 

681component of a particular type and category. 

682 

683Within the calendar components, specify categories as a list of strings. 

684You can get, set, and delete categories for a component. 

685 

686This property can be used in icalendar through its Python attributes of: 

687 

688- :attr:`Available.categories <icalendar.cal.available.Available.categories>` 

689- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>` 

690- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>` 

691- :attr:`Event.categories <icalendar.cal.event.Event.categories>` 

692- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>` 

693- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>` 

694 

695The categories property for ``Available`` and ``Availability`` complies with 

696:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with 

697:rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`. 

698 

699Note: 

700 At present, icalendar doesn't take the LANGUAGE parameter as defined 

701 in :rfc:`5545#section-3.2.10` into account. 

702 

703Parameters: 

704 categories(list[str]): A list of categories as strings. 

705 

706Example: 

707 Create an event, add categories to it, print its ical representation, 

708 append another category, and finally compare the result 

709 against its expected value. 

710 

711 .. code-block:: pycon 

712 

713 >>> from icalendar import Event 

714 >>> event = Event() 

715 >>> event.categories = ["Work", "Meeting"] 

716 >>> print(event.to_ical()) 

717 BEGIN:VEVENT 

718 CATEGORIES:Work,Meeting 

719 END:VEVENT 

720 >>> event.categories.append("Lecture") 

721 >>> event.categories == ["Work", "Meeting", "Lecture"] 

722 True 

723 

724See also: 

725 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>` 

726""", 

727) 

728 

729 

730def _get_attendees(self: Component) -> list[vCalAddress]: 

731 """Get attendees.""" 

732 value = self.get("ATTENDEE") 

733 if value is None: 

734 value = [] 

735 self["ATTENDEE"] = value 

736 return value 

737 if isinstance(value, vCalAddress): 

738 return [value] 

739 return value 

740 

741 

742ATTENDEE_TYPE_SETTER: TypeAlias = Sequence[vCalAddress | str] | vCalAddress | str | None 

743 

744 

745def _set_attendees(self: Component, value: ATTENDEE_TYPE_SETTER): 

746 """Set attendees.""" 

747 _del_attendees(self) 

748 if value is None: 

749 return 

750 if isinstance(value, (vCalAddress, str)): 

751 value = [value] 

752 elif not isinstance(value, list): 

753 value = list(value) if isinstance(value, Sequence) else [value] 

754 for index, attendee in enumerate(value): 

755 # vCalAddress subclasses str, so exclude it before normalizing strings 

756 if not isinstance(attendee, vCalAddress) and isinstance(attendee, str): 

757 value[index] = vCalAddress.new(attendee) 

758 self["ATTENDEE"] = value 

759 

760 

761def _del_attendees(self: Component): 

762 """Delete all attendees.""" 

763 self.pop("ATTENDEE", None) 

764 

765 

766attendees_property = property( 

767 _get_attendees, 

768 _set_attendees, 

769 _del_attendees, 

770 """ATTENDEE defines one or more "Attendees" within a calendar component. 

771 

772Conformance: 

773 This property MUST be specified in an iCalendar object 

774 that specifies a group-scheduled calendar entity. This property 

775 MUST NOT be specified in an iCalendar object when publishing the 

776 calendar information (e.g., NOT in an iCalendar object that 

777 specifies the publication of a calendar user's busy time, event, 

778 to-do, or journal). This property is not specified in an 

779 iCalendar object that specifies only a time zone definition or 

780 that defines calendar components that are not group-scheduled 

781 components, but are components only on a single user's calendar. 

782 

783Description: 

784 This property MUST only be specified within calendar 

785 components to specify participants, non-participants, and the 

786 chair of a group-scheduled calendar entity. The property is 

787 specified within an "EMAIL" category of the "VALARM" calendar 

788 component to specify an email address that is to receive the email 

789 type of iCalendar alarm. 

790 

791Examples: 

792 Assign one or more attendee email addresses directly. Strings are 

793 converted to :class:`~icalendar.prop.cal_address.vCalAddress` objects 

794 and receive a ``mailto:`` prefix when needed. 

795 

796 .. code-block:: pycon 

797 

798 >>> from icalendar import Event 

799 >>> event = Event() 

800 >>> event.attendees = [ 

801 ... "me@my-domain.com", 

802 ... "mailto:you@my-domain.com", 

803 ... ] 

804 >>> event.attendees[0] 

805 vCalAddress('mailto:me@my-domain.com') 

806 >>> event.attendees[1] 

807 vCalAddress('mailto:you@my-domain.com') 

808 >>> print(event.to_ical()) 

809 BEGIN:VEVENT 

810 ATTENDEE:mailto:me@my-domain.com 

811 ATTENDEE:mailto:you@my-domain.com 

812 END:VEVENT 

813 

814 Use :meth:`vCalAddress.new 

815 <icalendar.prop.cal_address.vCalAddress.new>` when an attendee needs 

816 parameters such as ``CN``, ``ROLE``, or ``RSVP``. 

817 

818 .. code-block:: pycon 

819 

820 >>> from icalendar import vCalAddress 

821 >>> event.attendees = [ 

822 ... vCalAddress.new( 

823 ... "chair@example.com", 

824 ... cn="Meeting Chair", 

825 ... role="CHAIR", 

826 ... rsvp=True, 

827 ... ) 

828 ... ] 

829""", 

830) 

831 

832uid_property = single_string_property( 

833 "UID", 

834 """UID specifies the persistent, globally unique identifier for a component. 

835 

836We recommend using :func:`uuid.uuid4` to generate new values. 

837 

838Returns: 

839 The value of the UID property as a string or ``""`` if no value is set. 

840 

841Description: 

842 The "UID" itself MUST be a globally unique identifier. 

843 The generator of the identifier MUST guarantee that the identifier 

844 is unique. 

845 

846 This is the method for correlating scheduling messages with the 

847 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component. 

848 The full range of calendar components specified by a recurrence 

849 set is referenced by referring to just the "UID" property value 

850 corresponding to the calendar component. The "RECURRENCE-ID" 

851 property allows the reference to an individual instance within the 

852 recurrence set. 

853 

854 This property is an important method for group-scheduling 

855 applications to match requests with later replies, modifications, 

856 or deletion requests. Calendaring and scheduling applications 

857 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL" 

858 calendar components to assure interoperability with other group- 

859 scheduling applications. This identifier is created by the 

860 calendar system that generates an iCalendar object. 

861 

862 Implementations MUST be able to receive and persist values of at 

863 least 255 octets for this property, but they MUST NOT truncate 

864 values in the middle of a UTF-8 multi-octet sequence. 

865 

866 :rfc:`7986` states that UID can be used, for 

867 example, to identify duplicate calendar streams that a client may 

868 have been given access to. It can be used in conjunction with the 

869 "LAST-MODIFIED" property also specified on the "VCALENDAR" object 

870 to identify the most recent version of a calendar. 

871 

872Conformance: 

873 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO", 

874 and "VJOURNAL" calendar components. 

875 :rfc:`7986` modifies the definition of the "UID" property to 

876 allow it to be defined in an iCalendar object. 

877 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique 

878 identifier to be specified. The value of this property can then be used 

879 to refer uniquely to the "VALARM" component. 

880 

881 This property can be specified once only. 

882 

883Security: 

884 :rfc:`7986` states that UID values MUST NOT include any data that 

885 might identify a user, host, domain, or any other security- or 

886 privacy-sensitive information. It is RECOMMENDED that calendar user 

887 agents now generate "UID" values that are hex-encoded random 

888 Universally Unique Identifier (UUID) values as defined in 

889 Sections 4.4 and 4.5 of :rfc:`4122`. 

890 You can use the :mod:`uuid` module to generate new UUIDs. 

891 

892Compatibility: 

893 For Alarms, ``X-ALARMUID`` is also considered. 

894 

895Examples: 

896 The following is an example of such a property value: 

897 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``. 

898 

899 Set the UID of a calendar: 

900 

901 .. code-block:: pycon 

902 

903 >>> from icalendar import Calendar 

904 >>> from uuid import uuid4 

905 >>> calendar = Calendar() 

906 >>> calendar.uid = uuid4() 

907 >>> print(calendar.to_ical()) 

908 BEGIN:VCALENDAR 

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

910 END:VCALENDAR 

911 

912""", 

913) 

914 

915summary_property = multi_language_text_property( 

916 "SUMMARY", 

917 None, 

918 """SUMMARY defines a short summary or subject for the calendar component. 

919 

920Property Parameters: 

921 IANA, non-standard, alternate text 

922 representation, and language property parameters can be specified 

923 on this property. 

924 

925Conformance: 

926 The property can be specified in "VEVENT", "VTODO", 

927 "VJOURNAL", or "VALARM" calendar components. 

928 

929Description: 

930 This property is used in the "VEVENT", "VTODO", and 

931 "VJOURNAL" calendar components to capture a short, one-line 

932 summary about the activity or journal entry. 

933 

934 This property is used in the "VALARM" calendar component to 

935 capture the subject of an EMAIL category of alarm. 

936 

937Examples: 

938 The following is an example of this property: 

939 

940 .. code-block:: pycon 

941 

942 SUMMARY:Department Party 

943""", 

944) 

945 

946description_property = multi_language_text_property( 

947 "DESCRIPTION", 

948 None, 

949 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property. 

950 

951Property Parameters: 

952 IANA, non-standard, alternate text 

953 representation, and language property parameters can be specified 

954 on this property. 

955 

956Conformance: 

957 The property can be specified in the "VEVENT", "VTODO", 

958 "VJOURNAL", or "VALARM" calendar components. The property can be 

959 specified multiple times only within a "VJOURNAL" calendar 

960 component. 

961 

962Description: 

963 This property is used in the "VEVENT" and "VTODO" to 

964 capture lengthy textual descriptions associated with the activity. 

965 

966 This property is used in the "VALARM" calendar component to 

967 capture the display text for a DISPLAY category of alarm, and to 

968 capture the body text for an EMAIL category of alarm. 

969 

970Examples: 

971 The following is an example of this property with formatted 

972 line breaks in the property value: 

973 

974 .. code-block:: pycon 

975 

976 DESCRIPTION:Meeting to provide technical review for "Phoenix" 

977 design.\\nHappy Face Conference Room. Phoenix design team 

978 MUST attend this meeting.\\nRSVP to team leader. 

979 

980 """, # noqa: E501 

981) 

982 

983 

984def create_single_property( 

985 prop: str, 

986 value_attr: str | None, 

987 value_type: tuple[type], 

988 type_def: type, 

989 doc: str, 

990 vProp: type = vDDDTypes, # noqa: N803 

991 convert: Callable[[object], object] | None = None, 

992): 

993 """Create a single property getter and setter. 

994 

995 Parameters: 

996 prop: The name of the property. 

997 value_attr: The name of the attribute to get the value from. 

998 value_type: The type of the value. 

999 type_def: The type of the property. 

1000 doc: The docstring of the property. 

1001 vProp: The type of the property from :mod:`icalendar.prop`. 

1002 """ 

1003 

1004 def p_get(self: Component): 

1005 default = object() 

1006 result = self.get(prop, default) 

1007 if result is default: 

1008 return None 

1009 if isinstance(result, list): 

1010 raise InvalidCalendar(f"Multiple {prop} defined.") 

1011 value = result if value_attr is None else getattr(result, value_attr, result) 

1012 value = value if convert is None else convert(value) 

1013 if not isinstance(value, value_type): 

1014 raise InvalidCalendar( 

1015 f"{prop} must be either a " 

1016 f"{' or '.join(t.__name__ for t in value_type)}," 

1017 f" not {value}." 

1018 ) 

1019 return value 

1020 

1021 def p_set(self: Component, value) -> None: 

1022 if value is None: 

1023 p_del(self) 

1024 return 

1025 value = convert(value) if convert is not None else value 

1026 if not isinstance(value, value_type): 

1027 raise TypeError( 

1028 f"Use {' or '.join(t.__name__ for t in value_type)}, " 

1029 f"not {type(value).__name__}." 

1030 ) 

1031 self[prop] = vProp(value) 

1032 if prop in self.exclusive: 

1033 for other_prop in self.exclusive: 

1034 if other_prop != prop: 

1035 self.pop(other_prop, None) 

1036 

1037 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None 

1038 

1039 def p_del(self: Component): 

1040 self.pop(prop) 

1041 

1042 p_doc = f"""The {prop} property. 

1043 

1044 {doc} 

1045 

1046 To delete the value, either use ``del`` or set it to ``None``. 

1047 

1048 Raises: 

1049 InvalidCalendar: if the attribute has invalid values. 

1050 """ 

1051 return property(p_get, p_set, p_del, p_doc) 

1052 

1053 

1054X_MOZ_SNOOZE_TIME_property = single_utc_property( 

1055 "X-MOZ-SNOOZE-TIME", 

1056 """The ``X-MOZ-SNOOZE-TIME`` property as a :class:`~datetime.datetime` in UTC. 

1057 

1058 Thunderbird: Alarms before this time are snoozed. 

1059""", 

1060) 

1061X_MOZ_LASTACK_property = single_utc_property( 

1062 "X-MOZ-LASTACK", 

1063 """The ``X-MOZ-LASTACK`` property as a :class:`~datetime.datetime` in UTC. 

1064 

1065 Thunderbird: Alarms before this time are acknowledged. 

1066""", 

1067) 

1068 

1069 

1070def property_get_duration(self: Component) -> timedelta | None: 

1071 """Getter for property DURATION.""" 

1072 default = object() 

1073 duration = self.get("duration", default) 

1074 if duration is default: 

1075 return None 

1076 result = getattr(duration, "td", None) 

1077 if result is None: 

1078 raise InvalidCalendar( 

1079 f"DURATION must be a timedelta, not {type(duration).__name__}." 

1080 ) 

1081 return result 

1082 

1083 

1084def property_set_duration(self: Component, value: timedelta | None): 

1085 """Setter for property DURATION.""" 

1086 if value is None: 

1087 self.pop("duration", None) 

1088 return 

1089 if not isinstance(value, timedelta): 

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

1091 self["duration"] = vDuration(value) 

1092 self.pop("DTEND") 

1093 self.pop("DUE") 

1094 

1095 

1096def property_del_duration(self: Component): 

1097 """Delete property DURATION.""" 

1098 self.pop("DURATION") 

1099 

1100 

1101property_doc_duration_template = """The DURATION property. 

1102 

1103The "DTSTART" property for a "{component}" specifies the inclusive 

1104start of the {component}. 

1105The "DURATION" property in conjunction with the DTSTART property 

1106for a "{component}" calendar component specifies the non-inclusive end 

1107of the event. 

1108 

1109If you would like to calculate the duration of a {component}, do not use this. 

1110Instead use the duration property (lower case). 

1111""" 

1112 

1113 

1114def duration_property(component: str) -> property: 

1115 """Return the duration property.""" 

1116 return property( 

1117 property_get_duration, 

1118 property_set_duration, 

1119 property_del_duration, 

1120 property_doc_duration_template.format(component=component), 

1121 ) 

1122 

1123 

1124def multi_text_property(name: str, docs: str) -> property: 

1125 """Get a property that can occur several times and is text. 

1126 

1127 Examples: Journal.descriptions, Event.comments 

1128 """ 

1129 

1130 def fget(self: Component) -> list[str]: 

1131 """Get the values.""" 

1132 descriptions = self.get(name) 

1133 if descriptions is None: 

1134 return [] 

1135 if not isinstance(descriptions, SEQUENCE_TYPES): 

1136 return [descriptions] 

1137 return descriptions 

1138 

1139 def fset(self: Component, values: str | Sequence[str] | None): 

1140 """Set the values.""" 

1141 fdel(self) 

1142 if values is None: 

1143 return 

1144 if isinstance(values, str): 

1145 self.add(name, values) 

1146 else: 

1147 for description in values: 

1148 self.add(name, description) 

1149 

1150 def fdel(self: Component): 

1151 """Delete the values.""" 

1152 self.pop(name) 

1153 

1154 return property(fget, fset, fdel, docs) 

1155 

1156 

1157descriptions_property = multi_text_property( 

1158 "DESCRIPTION", 

1159 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property. 

1160 

1161Property Parameters: 

1162 IANA, non-standard, alternate text 

1163 representation, and language property parameters can be specified 

1164 on this property. 

1165 

1166Conformance: 

1167 The property can be 

1168 specified multiple times only within a "VJOURNAL" calendar component. 

1169 

1170Description: 

1171 This property is used in the "VJOURNAL" calendar component to 

1172 capture one or more textual journal entries. 

1173 

1174Examples: 

1175 The following is an example of this property with formatted 

1176 line breaks in the property value: 

1177 

1178 .. code-block:: pycon 

1179 

1180 DESCRIPTION:Meeting to provide technical review for "Phoenix" 

1181 design.\\nHappy Face Conference Room. Phoenix design team 

1182 MUST attend this meeting.\\nRSVP to team leader. 

1183 

1184""", # noqa: E501 

1185) 

1186 

1187comments_property = multi_text_property( 

1188 "COMMENT", 

1189 """Specifies a comment to the calendar user. 

1190 

1191This property holds free-text notes attached to a component; calendar clients 

1192display it but never act on it. It is defined in :rfc:`5545#section-3.8.1.4` 

1193and may appear multiple times on ``VEVENT``, ``VTODO``, ``VJOURNAL``, and 

1194``VFREEBUSY`` components as well as on their ``STANDARD`` and ``DAYLIGHT`` 

1195sub-components. For availability components, it is defined in :rfc:`7953`, and 

1196may appear multiple times on ``VAVAILABILITY`` and ``VAVAILABLE``. 

1197 

1198You can get, set, append, and delete comments on a component. The value is a 

1199list of strings; each string is one comment. 

1200 

1201Example: 

1202 Add two comments to an event and then read them back. 

1203 

1204 .. code-block:: pycon 

1205 

1206 >>> from icalendar import Event 

1207 >>> event = Event() 

1208 >>> event.add("COMMENT", "Moved from the planning board.") 

1209 >>> event.add("COMMENT", "Confirmed by phone.") 

1210 >>> [str(c) for c in event.comments] 

1211 ['Moved from the planning board.', 'Confirmed by phone.'] 

1212 >>> str(event.comments[0]) 

1213 'Moved from the planning board.' 

1214""", 

1215) 

1216 

1217RECURRENCE_ID = create_single_property( 

1218 "RECURRENCE-ID", 

1219 "dt", 

1220 (date, datetime), 

1221 date | datetime, 

1222 """ 

1223Identify a specific occurrence of a recurring calendar object. 

1224 

1225This property is used together with ``UID`` and ``SEQUENCE`` to refer to one 

1226particular instance in a recurrence set. The value is the original start 

1227date or datetime of that instance, not the rescheduled time. 

1228 

1229The value is usually a DATE-TIME and must use the same value type as the 

1230``DTSTART`` property in the same component. A DATE value may be used for 

1231all-day items instead. 

1232 

1233This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and 

1234may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components. 

1235""", 

1236 vDDDTypes, 

1237) 

1238 

1239 

1240def _get_organizer(self: Component) -> vCalAddress | None: 

1241 """ORGANIZER defines the organizer for a calendar component. 

1242 

1243 Property Parameters: 

1244 IANA, non-standard, language, common name, 

1245 directory entry reference, and sent-by property parameters can be 

1246 specified on this property. 

1247 

1248 Conformance: 

1249 This property MUST be specified in an iCalendar object 

1250 that specifies a group-scheduled calendar entity. This property 

1251 MUST be specified in an iCalendar object that specifies the 

1252 publication of a calendar user's busy time. This property MUST 

1253 NOT be specified in an iCalendar object that specifies only a time 

1254 zone definition or that defines calendar components that are not 

1255 group-scheduled components, but are components only on a single 

1256 user's calendar. 

1257 

1258 Description: 

1259 This property is specified within the "VEVENT", 

1260 "VTODO", and "VJOURNAL" calendar components to specify the 

1261 organizer of a group-scheduled calendar entity. The property is 

1262 specified within the "VFREEBUSY" calendar component to specify the 

1263 calendar user requesting the free or busy time. When publishing a 

1264 "VFREEBUSY" calendar component, the property is used to specify 

1265 the calendar that the published busy time came from. 

1266 

1267 The property has the property parameters "CN", for specifying the 

1268 common or display name associated with the "Organizer", "DIR", for 

1269 specifying a pointer to the directory information associated with 

1270 the "Organizer", "SENT-BY", for specifying another calendar user 

1271 that is acting on behalf of the "Organizer". The non-standard 

1272 parameters may also be specified on this property. If the 

1273 "LANGUAGE" property parameter is specified, the identified 

1274 language applies to the "CN" parameter value. 

1275 """ 

1276 return self.get("ORGANIZER") 

1277 

1278 

1279def _set_organizer(self: Component, value: vCalAddress | str | None): 

1280 """Set the value.""" 

1281 _del_organizer(self) 

1282 if value is not None: 

1283 self.add("ORGANIZER", value) 

1284 

1285 

1286def _del_organizer(self: Component): 

1287 """Delete the value.""" 

1288 self.pop("ORGANIZER") 

1289 

1290 

1291organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1292 

1293 

1294def single_string_enum_property( 

1295 name: str, enum: type[StrEnum], default: StrEnum, docs: str 

1296) -> property: 

1297 """Create a property to access a single string value and convert it to an enum.""" 

1298 prop = single_string_property(name, docs, default=default) 

1299 

1300 def fget(self: Component) -> StrEnum: 

1301 """Get the value.""" 

1302 value = prop.fget(self) 

1303 if value == default: 

1304 return default 

1305 return enum(str(value)) 

1306 

1307 def fset(self: Component, value: str | StrEnum | None) -> None: 

1308 """Set the value.""" 

1309 if value == "": 

1310 value = None 

1311 prop.fset(self, value) 

1312 

1313 return property(fget, fset, prop.fdel, doc=docs) 

1314 

1315 

1316busy_type_property = single_string_enum_property( 

1317 "BUSYTYPE", 

1318 BUSYTYPE, 

1319 BUSYTYPE.BUSY_UNAVAILABLE, 

1320 """BUSYTYPE specifies the default busy time type. 

1321 

1322Returns: 

1323 :class:`icalendar.enums.BUSYTYPE` 

1324 

1325Description: 

1326 This property is used to specify the default busy time 

1327 type. The values correspond to those used by the "FBTYPE" 

1328 parameter used on a "FREEBUSY" property, with the exception that 

1329 the "FREE" value is not used in this property. If not specified 

1330 on a component that allows this property, the default is "BUSY- 

1331 UNAVAILABLE". 

1332""", 

1333) 

1334 

1335 

1336def _make_repeat_property() -> property: 

1337 from icalendar.config import _clamp_repeat 

1338 

1339 _base = single_int_property( 

1340 "REPEAT", 

1341 0, 

1342 """The number of additional times the alarm is triggered after the 

1343initial trigger. 

1344 

1345Defaults to ``0``, meaning the alarm fires once. Must be paired with 

1346:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`. 

1347The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read. 

1348 

1349Raises: 

1350 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

1351 even though ``bool`` subclasses ``int``. 

1352 

1353 ~icalendar.error.InvalidCalendar: If the value is negative. 

1354 

1355.. versionchanged:: 7.3.0 

1356 Negative values are no longer accepted. 

1357""", 

1358 min_value=0, 

1359 ) 

1360 

1361 def fget(self): 

1362 return _clamp_repeat(_base.fget(self)) 

1363 

1364 return property(fget, _base.fset, _base.fdel, _base.__doc__) 

1365 

1366 

1367repeat_property = _make_repeat_property() 

1368 

1369priority_property = single_int_property( 

1370 "PRIORITY", 

1371 0, 

1372 """ 

1373 

1374Conformance: 

1375 This property can be specified in "VEVENT" and "VTODO" calendar components 

1376 according to :rfc:`5545`. 

1377 :rfc:`7953` adds this property to "VAVAILABILITY". 

1378 

1379Description: 

1380 This priority is specified as an integer in the range 0 

1381 to 9. A value of 0 specifies an undefined priority. A value of 1 

1382 is the highest priority. A value of 2 is the second highest 

1383 priority. Subsequent numbers specify a decreasing ordinal 

1384 priority. A value of 9 is the lowest priority. 

1385 

1386 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and 

1387 "LOW" is mapped into this property such that a property value in 

1388 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is 

1389 the normal or "MEDIUM" priority. A value in the range of 6 to 9 

1390 is "LOW" priority. 

1391 

1392 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ..., 

1393 "C3" is mapped into this property such that a property value of 1 

1394 specifies "A1", a property value of 2 specifies "A2", a property 

1395 value of 3 specifies "A3", and so forth up to a property value of 

1396 9 specifies "C3". 

1397 

1398 Other integer values are reserved for future use. 

1399 

1400 Within a "VEVENT" calendar component, this property specifies a 

1401 priority for the event. This property may be useful when more 

1402 than one event is scheduled for a given time period. 

1403 

1404 Within a "VTODO" calendar component, this property specified a 

1405 priority for the to-do. This property is useful in prioritizing 

1406 multiple action items for a given time period. 

1407 

1408 Raises: 

1409 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

1410 even though ``bool`` subclasses ``int``. 

1411 

1412 ~icalendar.error.InvalidCalendar: If the value is negative. 

1413 

1414 .. versionchanged:: 7.3.0 

1415 Negative values are no longer accepted. 

1416""", 

1417 min_value=0, 

1418) 

1419 

1420class_property = single_string_enum_property( 

1421 "CLASS", 

1422 CLASS, 

1423 CLASS.PUBLIC, 

1424 """CLASS specifies the class of the calendar component. 

1425 

1426Returns: 

1427 :class:`icalendar.enums.CLASS` 

1428 

1429Description: 

1430 An access classification is only one component of the 

1431 general security system within a calendar application. It 

1432 provides a method of capturing the scope of the access the 

1433 calendar owner intends for information within an individual 

1434 calendar entry. The access classification of an individual 

1435 iCalendar component is useful when measured along with the other 

1436 security components of a calendar system (e.g., calendar user 

1437 authentication, authorization, access rights, access role, etc.). 

1438 Hence, the semantics of the individual access classifications 

1439 cannot be completely defined by this memo alone. Additionally, 

1440 due to the "blind" nature of most exchange processes using this 

1441 memo, these access classifications cannot serve as an enforcement 

1442 statement for a system receiving an iCalendar object. Rather, 

1443 they provide a method for capturing the intention of the calendar 

1444 owner for the access to the calendar component. If not specified 

1445 in a component that allows this property, the default value is 

1446 PUBLIC. Applications MUST treat x-name and iana-token values they 

1447 don't recognize the same way as they would the PRIVATE value. 

1448""", 

1449) 

1450 

1451transparency_property = single_string_enum_property( 

1452 "TRANSP", 

1453 TRANSP, 

1454 TRANSP.OPAQUE, 

1455 """TRANSP defines whether or not an event is transparent to busy time searches. 

1456 

1457Returns: 

1458 :class:`icalendar.enums.TRANSP` 

1459 

1460Description: 

1461 Time Transparency is the characteristic of an event 

1462 that determines whether it appears to consume time on a calendar. 

1463 Events that consume actual time for the individual or resource 

1464 associated with the calendar SHOULD be recorded as OPAQUE, 

1465 allowing them to be detected by free/busy time searches. Other 

1466 events, which do not take up the individual's (or resource's) time 

1467 SHOULD be recorded as TRANSPARENT, making them invisible to free/ 

1468 busy time searches. 

1469""", 

1470) 

1471status_property = single_string_enum_property( 

1472 "STATUS", 

1473 STATUS, 

1474 "", 

1475 """STATUS defines the overall status or confirmation for the calendar component. 

1476 

1477Returns: 

1478 :class:`icalendar.enums.STATUS` 

1479 

1480The default value is ``""``. 

1481 

1482Description: 

1483 In a group-scheduled calendar component, the property 

1484 is used by the "Organizer" to provide a confirmation of the event 

1485 to the "Attendees". For example in a "VEVENT" calendar component, 

1486 the "Organizer" can indicate that a meeting is tentative, 

1487 confirmed, or cancelled. In a "VTODO" calendar component, the 

1488 "Organizer" can indicate that an action item needs action, is 

1489 completed, is in process or being worked on, or has been 

1490 cancelled. In a "VJOURNAL" calendar component, the "Organizer" 

1491 can indicate that a journal entry is draft, final, or has been 

1492 cancelled or removed. 

1493""", 

1494) 

1495 

1496url_property = single_string_property( 

1497 "URL", 

1498 """A Uniform Resource Locator (URL) associated with a calendar component. 

1499 

1500This property specifies a URI where a more dynamic rendition of the calendar 

1501information can be found. It is commonly used to reference related resources 

1502or provide additional information about the component. 

1503 

1504According to :rfc:`5545#section-3.8.4.6`, this property can be specified 

1505once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components. 

1506Since :rfc:`7986#section-5.5`, this property can also be defined on a 

1507"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components. 

1508 

1509This property may be used in a calendar component to convey a location 

1510where a more dynamic rendition of the calendar information can be found. 

1511If both the URL property and Content-Location MIME header are specified, 

1512they MUST point to the same resource. 

1513 

1514This differs from the SOURCE property, which identifies where calendar 

1515data can be refreshed from, whereas URL provides an alternative 

1516representation of the current calendar data. 

1517 

1518Examples: 

1519 

1520 Set a URL for an event that references additional information: 

1521 

1522 .. code-block:: pycon 

1523 

1524 >>> from icalendar import Event 

1525 >>> event = Event() 

1526 >>> event.add('url', 'http://example.com/events/meeting-2025') 

1527 >>> print(event.to_ical().decode('utf-8')) 

1528 BEGIN:VEVENT 

1529 URL:http://example.com/events/meeting-2025 

1530 END:VEVENT 

1531 

1532 Set a URL for a calendar: 

1533 

1534 .. code-block:: pycon 

1535 

1536 >>> from icalendar import Calendar 

1537 >>> calendar = Calendar() 

1538 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics') 

1539 >>> print(calendar.to_ical().decode('utf-8')) 

1540 BEGIN:VCALENDAR 

1541 URL:http://example.com/pub/calendars/jsmith/mytime.ics 

1542 END:VCALENDAR 

1543 

1544See also: 

1545 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where 

1546 calendar data can be refreshed. 

1547 

1548 icalendar implementations: 

1549 

1550 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>` 

1551 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>` 

1552 - :attr:`Event.url <icalendar.cal.event.Event.url>` 

1553 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>` 

1554 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>` 

1555 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>` 

1556 

1557 

1558""", 

1559) 

1560 

1561source_property = single_string_property( 

1562 "SOURCE", 

1563 """A URI from where calendar data can be refreshed. 

1564 

1565Description: 

1566 This property identifies a location where a client can 

1567 retrieve updated data for the calendar. Clients SHOULD honor any 

1568 specified "REFRESH-INTERVAL" value when periodically retrieving 

1569 data. Note that this property differs from the "URL" property in 

1570 that "URL" is meant to provide an alternative representation of 

1571 the calendar data rather than the original location of the data. 

1572 

1573Conformance: 

1574 This property can be specified once in an iCalendar object. 

1575 

1576Example: 

1577 The following is an example of this property: 

1578 

1579 .. code-block:: ics 

1580 

1581 SOURCE;VALUE=URI:https://example.com/holidays.ics 

1582 

1583""", 

1584) 

1585 

1586location_property = multi_language_text_property( 

1587 "LOCATION", 

1588 None, 

1589 """The intended venue for the activity defined by a calendar component. 

1590 

1591Property Parameters: 

1592 IANA, non-standard, alternate text 

1593 representation, and language property parameters can be specified 

1594 on this property. 

1595 

1596Conformance: 

1597 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO" 

1598 calendar component. 

1599 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE". 

1600 

1601Description: 

1602 Specific venues such as conference or meeting rooms may 

1603 be explicitly specified using this property. An alternate 

1604 representation may be specified that is a URI that points to 

1605 directory information with more structured specification of the 

1606 location. For example, the alternate representation may specify 

1607 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a 

1608 CID URL :rfc:`2392` pointing to a MIME body part containing a 

1609 Virtual-Information Card (vCard) :rfc:`2426` for the location. 

1610 

1611""", 

1612) 

1613 

1614contacts_property = multi_text_property( 

1615 "CONTACT", 

1616 """Contact information associated with a calendar component. 

1617 

1618The contact property holds free-text information for reaching the person or 

1619organization responsible in a component, such as a name, phone number, or a 

1620reference to more detailed contact data. Calendar clients surface it so 

1621attendees know who to contact about the event or task. 

1622 

1623This property is defined in :rfc:`5545#section-3.8.4.2` and may appear on a 

1624``VEVENT``, ``VTODO``, ``VJOURNAL``, or ``VFREEBUSY`` component. For 

1625availability components it is defined in :rfc:`7953` and may appear on a 

1626``VAVAILABILITY`` or ``VAVAILABLE`` component. An alternate representation may 

1627point to a URI, for example, a vCard per :rfc:`2426`, via the ``ALTREP`` parameter. 

1628 

1629You can get, set, append, and delete contacts on a component. The value is a 

1630list of strings; each string is one contact entry. 

1631 

1632Example: 

1633 Add a contact to an event and then read it back. 

1634 

1635 .. code-block:: pycon 

1636 

1637 >>> from icalendar import Event 

1638 >>> event = Event() 

1639 >>> event.add("CONTACT", "Jim Dolittle, ABC Industries, +1-919-555-1234") 

1640 >>> [str(c) for c in event.contacts] 

1641 ['Jim Dolittle, ABC Industries, +1-919-555-1234'] 

1642 >>> str(event.contacts[0]) 

1643 'Jim Dolittle, ABC Industries, +1-919-555-1234' 

1644""", 

1645) 

1646 

1647 

1648def _timezone_datetime_property(name: str, docs: str): 

1649 """Create a property to access the values with a proper timezone.""" 

1650 

1651 return single_utc_property(name, docs) 

1652 

1653 

1654rfc_7953_dtstart_property = _timezone_datetime_property( 

1655 "DTSTART", 

1656 """Start of the component as a :class:`~datetime.datetime` in UTC. 

1657 

1658 This is almost the same as 

1659 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception: 

1660 The values MUST have a timezone and DATE is not allowed. 

1661 

1662 Description: 

1663 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in 

1664 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be 

1665 "DATE-TIME" values specified as either the date with UTC time or 

1666 the date with local time and a time zone reference. 

1667 

1668 """, 

1669) 

1670 

1671rfc_7953_dtend_property = _timezone_datetime_property( 

1672 "DTEND", 

1673 """End of the component as a :class:`~datetime.datetime` in UTC. 

1674 

1675 This is almost the same as 

1676 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception: 

1677 The values MUST have a timezone and DATE is not allowed. 

1678 

1679 Description: 

1680 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in 

1681 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be 

1682 "DATE-TIME" values specified as either the date with UTC time or 

1683 the date with local time and a time zone reference. 

1684 """, 

1685) 

1686 

1687 

1688@property 

1689def rfc_7953_duration_property(self) -> timedelta | None: 

1690 """Compute the duration of this component. 

1691 

1692 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None. 

1693 Otherwise, the duration is calculated from :attr:`DTSTART` and 

1694 :attr:`DTEND`/:attr:`DURATION`. 

1695 

1696 This is in accordance with :rfc:`7953`: 

1697 If "DTEND" or "DURATION" are not present, then the end time is unbounded. 

1698 """ 

1699 duration = self.DURATION 

1700 if duration: 

1701 return duration 

1702 end = self.DTEND 

1703 if end is None: 

1704 return None 

1705 start = self.DTSTART 

1706 if start is None: 

1707 raise IncompleteComponent("Cannot compute duration without start.") 

1708 return end - start 

1709 

1710 

1711@property 

1712def rfc_7953_end_property(self) -> timedelta | None: 

1713 """Compute the duration of this component. 

1714 

1715 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None. 

1716 Otherwise, the duration is calculated from :attr:`DTSTART` and 

1717 :attr:`DTEND`/:attr:`DURATION`. 

1718 

1719 This is in accordance with :rfc:`7953`: 

1720 If "DTEND" or "DURATION" are not present, then the end time is unbounded. 

1721 """ 

1722 duration = self.DURATION 

1723 if duration: 

1724 start = self.DTSTART 

1725 if start is None: 

1726 raise IncompleteComponent("Cannot compute end without start.") 

1727 return start + duration 

1728 end = self.DTEND 

1729 if end is None: 

1730 return None 

1731 return end 

1732 

1733 

1734@rfc_7953_end_property.setter 

1735def rfc_7953_end_property(self, value: datetime): 

1736 self.DTEND = value 

1737 

1738 

1739@rfc_7953_end_property.deleter 

1740def rfc_7953_end_property(self): 

1741 del self.DTEND 

1742 

1743 

1744def get_start_end_duration_with_validation( 

1745 component: Component, 

1746 start_property: str, 

1747 end_property: str, 

1748 component_name: str, 

1749) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]: 

1750 """ 

1751 Validate the component and return start, end, and duration. 

1752 

1753 This tests validity according to :rfc:`5545` rules 

1754 for ``Event`` and ``Todo`` components. 

1755 

1756 Parameters: 

1757 component: The component to validate, either ``Event`` or ``Todo``. 

1758 start_property: The start property name, ``DTSTART``. 

1759 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1760 ``DUE`` for ``Todo``. 

1761 component_name: The component name for error messages, 

1762 either ``VEVENT`` or ``VTODO``. 

1763 

1764 Returns: 

1765 tuple: (start, end, duration) values from the component. 

1766 

1767 Raises: 

1768 ~error.InvalidCalendar: If the component violates RFC 5545 constraints. 

1769 

1770 """ 

1771 start = getattr(component, start_property, None) 

1772 end = getattr(component, end_property, None) 

1773 duration = component.DURATION 

1774 

1775 # RFC 5545: Only one of end property and DURATION may be present 

1776 if duration is not None and end is not None: 

1777 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1778 msg = ( 

1779 f"Only one of {end_name} and DURATION " 

1780 f"may be in a {component_name}, not both." 

1781 ) 

1782 raise InvalidCalendar(msg) 

1783 

1784 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks 

1785 if ( 

1786 start is not None 

1787 and is_date(start) 

1788 and duration is not None 

1789 and duration.seconds != 0 

1790 ): 

1791 msg = "When DTSTART is a date, DURATION must be of days or weeks." 

1792 raise InvalidCalendar(msg) 

1793 

1794 # RFC 5545: DTSTART and end property must be of the same type 

1795 if start is not None and end is not None and is_date(start) != is_date(end): 

1796 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1797 msg = ( 

1798 f"DTSTART and {end_name} must be of the same type, either date or datetime." 

1799 ) 

1800 raise InvalidCalendar(msg) 

1801 

1802 return start, end, duration 

1803 

1804 

1805def get_start_property(component: Component) -> date | datetime: 

1806 """ 

1807 Get the start property with validation. 

1808 

1809 Parameters: 

1810 component: The component from which to get its start property. 

1811 

1812 Returns: 

1813 The ``DTSTART`` value. 

1814 

1815 Raises: 

1816 ~error.IncompleteComponent: If no ``DTSTART`` is present. 

1817 

1818 """ 

1819 # Trigger validation by calling _get_start_end_duration 

1820 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001 

1821 if start is None: 

1822 msg = "No DTSTART given." 

1823 raise IncompleteComponent(msg) 

1824 return start 

1825 

1826 

1827def get_end_property(component: Component, end_property: str) -> date | datetime: 

1828 """ 

1829 Get the end property with fallback logic for ``Event`` and ``Todo`` components. 

1830 

1831 Parameters: 

1832 component: The component to get end from 

1833 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1834 ``DUE`` for ``Todo``. 

1835 

1836 Returns: 

1837 The computed end value. 

1838 

1839 Raises: 

1840 ~error.IncompleteComponent: If the provided information is incomplete 

1841 to compute the end property. 

1842 

1843 """ 

1844 # Trigger validation by calling _get_start_end_duration 

1845 start, end, duration = component._get_start_end_duration() # noqa: SLF001 

1846 

1847 if end is None and duration is None: 

1848 if start is None: 

1849 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1850 msg = f"No {end_name} or DURATION+DTSTART given." 

1851 raise IncompleteComponent(msg) 

1852 

1853 # Default behavior differs for Event vs Todo: 

1854 # Event: date gets +1 day, datetime gets same time 

1855 # Todo: both date and datetime get same time (issue #898) 

1856 if end_property == "DTEND" and is_date(start): 

1857 return start + timedelta(days=1) 

1858 return start 

1859 

1860 if duration is not None: 

1861 if start is not None: 

1862 if component.name == "VEVENT" and duration.total_seconds() <= 0: 

1863 return start 

1864 return start + duration 

1865 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1866 msg = f"No {end_name} or DURATION+DTSTART given." 

1867 raise IncompleteComponent(msg) 

1868 

1869 return end 

1870 

1871 

1872def get_duration_property(component: Component) -> timedelta: 

1873 """ 

1874 Get the duration property with fallback calculation from start and end. 

1875 

1876 Parameters: 

1877 component: The component from which to get its duration property. 

1878 

1879 Returns: 

1880 The duration as a timedelta. 

1881 

1882 """ 

1883 # First check if DURATION property is explicitly set 

1884 if "DURATION" in component: 

1885 return component["DURATION"].dt 

1886 

1887 # Fall back to calculated duration from start and end 

1888 return component.end - component.start 

1889 

1890 

1891def set_duration_with_locking( 

1892 component: Component, 

1893 duration: timedelta | None, 

1894 locked: Literal["start", "end"], 

1895 end_property: str, 

1896) -> None: 

1897 """ 

1898 Set the duration with explicit locking behavior for ``Event`` and ``Todo``. 

1899 

1900 Parameters: 

1901 component: The component to modify, either ``Event`` or ``Todo``. 

1902 duration: The duration to set, or ``None`` to convert to ``DURATION`` property. 

1903 locked: Which property to keep unchanged, either ``start`` or ``end``. 

1904 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1905 ``DUE`` for ``Todo``. 

1906 

1907 """ 

1908 # Convert to DURATION property if duration is None 

1909 if duration is None: 

1910 if "DURATION" in component: 

1911 return # Already has DURATION property 

1912 current_duration = component.duration 

1913 component.DURATION = current_duration 

1914 return 

1915 

1916 if not isinstance(duration, timedelta): 

1917 msg = f"Use timedelta, not {type(duration).__name__}." 

1918 raise TypeError(msg) 

1919 

1920 # Validate date/duration compatibility 

1921 start = component.DTSTART 

1922 if start is not None and is_date(start) and duration.seconds != 0: 

1923 msg = "When DTSTART is a date, DURATION must be of days or weeks." 

1924 raise InvalidCalendar(msg) 

1925 

1926 if locked == "start": 

1927 # Keep start locked, adjust end 

1928 if start is None: 

1929 msg = "Cannot set duration without DTSTART. Set start time first." 

1930 raise IncompleteComponent(msg) 

1931 component.pop(end_property, None) # Remove end property 

1932 component.DURATION = duration 

1933 elif locked == "end": 

1934 # Keep end locked, adjust start 

1935 current_end = component.end 

1936 component.DTSTART = current_end - duration 

1937 component.pop(end_property, None) # Remove end property 

1938 component.DURATION = duration 

1939 else: 

1940 msg = f"locked must be 'start' or 'end', not {locked!r}" 

1941 raise ValueError(msg) 

1942 

1943 

1944def set_start_with_locking( 

1945 component: Component, 

1946 start: date | datetime, 

1947 locked: Literal["duration", "end"] | None, 

1948 end_property: str, 

1949) -> None: 

1950 """ 

1951 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components. 

1952 

1953 Parameters: 

1954 component: The component to modify, either ``Event`` or ``Todo``. 

1955 start: The start time to set. 

1956 locked: Which property to keep unchanged, either ``duration``, ``end``, 

1957 or ``None`` for auto-detect. 

1958 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1959 ``DUE`` for ``Todo``. 

1960 

1961 """ 

1962 if locked is None: 

1963 # Auto-detect based on existing properties 

1964 if "DURATION" in component: 

1965 locked = "duration" 

1966 elif end_property in component: 

1967 locked = "end" 

1968 else: 

1969 # Default to duration if no existing properties 

1970 locked = "duration" 

1971 

1972 if locked == "duration": 

1973 # Keep duration locked, adjust end 

1974 current_duration = ( 

1975 component.duration 

1976 if "DURATION" in component or end_property in component 

1977 else None 

1978 ) 

1979 component.DTSTART = start 

1980 if current_duration is not None: 

1981 component.pop(end_property, None) # Remove end property 

1982 component.DURATION = current_duration 

1983 elif locked == "end": 

1984 # Keep end locked, adjust duration 

1985 current_end = component.end 

1986 component.DTSTART = start 

1987 component.pop("DURATION", None) # Remove duration property 

1988 setattr(component, end_property, current_end) 

1989 else: 

1990 msg = f"locked must be 'duration', 'end', or None, not {locked!r}" 

1991 raise ValueError(msg) 

1992 

1993 

1994def set_end_with_locking( 

1995 component: Component, 

1996 end: date | datetime, 

1997 locked: Literal["start", "duration"], 

1998 end_property: str, 

1999) -> None: 

2000 """ 

2001 Set the end with explicit locking behavior for Event and Todo components. 

2002 

2003 Parameters: 

2004 component: The component to modify, either ``Event`` or ``Todo``. 

2005 end: The end time to set. 

2006 locked: Which property to keep unchanged, either ``start`` or ``duration``. 

2007 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE`` 

2008 for ``Todo``. 

2009 

2010 """ 

2011 if locked == "start": 

2012 # Keep start locked, adjust duration 

2013 component.pop("DURATION", None) # Remove duration property 

2014 setattr(component, end_property, end) 

2015 elif locked == "duration": 

2016 # Keep duration locked, adjust start 

2017 current_duration = component.duration 

2018 component.DTSTART = end - current_duration 

2019 component.pop(end_property, None) # Remove end property 

2020 component.DURATION = current_duration 

2021 else: 

2022 msg = f"locked must be 'start' or 'duration', not {locked!r}" 

2023 raise ValueError(msg) 

2024 

2025 

2026def _get_images(self: Component) -> list[Image]: 

2027 """IMAGE specifies an image associated with the calendar or a calendar component. 

2028 

2029 Description: 

2030 This property specifies an image for an iCalendar 

2031 object or a calendar component via a URI or directly with inline 

2032 data that can be used by calendar user agents when presenting the 

2033 calendar data to a user. Multiple properties MAY be used to 

2034 specify alternative sets of images with, for example, varying 

2035 media subtypes, resolutions, or sizes. When multiple properties 

2036 are present, calendar user agents SHOULD display only one of them, 

2037 picking one that provides the most appropriate image quality, or 

2038 display none. The "DISPLAY" parameter is used to indicate the 

2039 intended display mode for the image. The "ALTREP" parameter, 

2040 defined in :rfc:`5545`, can be used to provide a "clickable" image 

2041 where the URI in the parameter value can be "launched" by a click 

2042 on the image in the calendar user agent. 

2043 

2044 Conformance: 

2045 This property can be specified multiple times in an 

2046 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar 

2047 components. 

2048 

2049 .. note:: 

2050 

2051 At the present moment, this property is read-only. If you require a setter, 

2052 please open an issue or a pull request. 

2053 """ 

2054 images = self.get("IMAGE", []) 

2055 if not isinstance(images, SEQUENCE_TYPES): 

2056 images = [images] 

2057 return [Image.from_property_value(img) for img in images] 

2058 

2059 

2060images_property = property(_get_images) 

2061 

2062 

2063def _get_conferences(self: Component) -> list[Conference]: 

2064 """Return the CONFERENCE properties as a list. 

2065 

2066 Purpose: 

2067 This property specifies information for accessing a conferencing system. 

2068 

2069 Conformance: 

2070 This property can be specified multiple times in a 

2071 "VEVENT" or "VTODO" calendar component. 

2072 

2073 Description: 

2074 This property specifies information for accessing a 

2075 conferencing system for attendees of a meeting or task. This 

2076 might be for a telephone-based conference number dial-in with 

2077 access codes included (such as a tel: URI :rfc:`3966` or a sip: or 

2078 sips: URI :rfc:`3261`), for a web-based video chat (such as an http: 

2079 or https: URI :rfc:`7230`), or for an instant messaging group chat 

2080 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a 

2081 conferencing system is not available, a data: URI :rfc:`2397` 

2082 containing a text description can be used. 

2083 

2084 A conference system can be a bidirectional communication channel 

2085 or a uni-directional "broadcast feed". 

2086 

2087 The "FEATURE" property parameter is used to describe the key 

2088 capabilities of the conference system to allow a client to choose 

2089 the ones that give the required level of interaction from a set of 

2090 multiple properties. 

2091 

2092 The "LABEL" property parameter is used to convey additional 

2093 details on the use of the URI. For example, the URIs or access 

2094 codes for the moderator and attendee of a teleconference system 

2095 could be different, and the "LABEL" property parameter could be 

2096 used to "tag" each "CONFERENCE" property to indicate which is 

2097 which. 

2098 

2099 The "LANGUAGE" property parameter can be used to specify the 

2100 language used for text values used with this property (as per 

2101 Section 3.2.10 of :rfc:`5545`). 

2102 

2103 Example: 

2104 The following are examples of this property: 

2105 

2106 .. code-block:: ics 

2107 

2108 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR; 

2109 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321 

2110 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

2111 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123 

2112 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

2113 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123 

2114 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

2115 LABEL=Chat room:xmpp:chat-123@conference.example.com 

2116 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO; 

2117 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456 

2118 

2119 Get all conferences: 

2120 

2121 .. code-block:: pycon 

2122 

2123 >>> from icalendar import Event 

2124 >>> event = Event() 

2125 >>> event.conferences 

2126 [] 

2127 

2128 Set a conference: 

2129 

2130 .. code-block:: pycon 

2131 

2132 >>> from icalendar import Event, Conference 

2133 >>> event = Event() 

2134 >>> event.conferences = [ 

2135 ... Conference( 

2136 ... "tel:+1-412-555-0123,,,654321", 

2137 ... feature="PHONE,MODERATOR", 

2138 ... label="Moderator dial-in", 

2139 ... language="EN", 

2140 ... ) 

2141 ... ] 

2142 >>> print(event.to_ical()) 

2143 BEGIN:VEVENT 

2144 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V 

2145 ALUE=URI:tel:+1-412-555-0123,,,654321 

2146 END:VEVENT 

2147 

2148 """ 

2149 conferences = self.get("CONFERENCE", []) 

2150 if not isinstance(conferences, SEQUENCE_TYPES): 

2151 conferences = [conferences] 

2152 return [Conference.from_uri(conference) for conference in conferences] 

2153 

2154 

2155def _set_conferences(self: Component, conferences: list[Conference] | None): 

2156 """Set the conferences.""" 

2157 _del_conferences(self) 

2158 for conference in conferences or []: 

2159 self.add("CONFERENCE", conference.to_uri()) 

2160 

2161 

2162def _del_conferences(self: Component): 

2163 """Delete all conferences.""" 

2164 self.pop("CONFERENCE") 

2165 

2166 

2167conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

2168 

2169 

2170def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]: 

2171 """LINK properties as a list. 

2172 

2173 Purpose: 

2174 LINK provides a reference to external information related to a component. 

2175 

2176 Property Parameters: 

2177 The VALUE parameter is required. 

2178 Non-standard, link relation type, format type, label, and language parameters 

2179 can also be specified on this property. 

2180 The LABEL parameter is defined in :rfc:`7986`. 

2181 

2182 Conformance: 

2183 This property can be specified zero or more times in any iCalendar component. 

2184 LINK is specified in :rfc:`9253`. 

2185 The LINKREL parameter is required. 

2186 

2187 Description: 

2188 When used in a component, the value of this property points to 

2189 additional information related to the component. 

2190 For example, it may reference the originating web server. 

2191 

2192 This property is a serialization of the model in :rfc:`8288`, 

2193 where the link target is carried in the property value, 

2194 the link context is the containing calendar entity, 

2195 and the link relation type and any target attributes 

2196 are carried in iCalendar property parameters. 

2197 

2198 The LINK property parameters map to :rfc:`8288` attributes as follows: 

2199 

2200 LABEL 

2201 This parameter maps to the "title" 

2202 attribute defined in Section 3.4.1 of :rfc:`8288`. 

2203 LABEL is used to label the destination 

2204 of a link such that it can be used as a human-readable identifier 

2205 (e.g., a menu entry) in the language indicated by the LANGUAGE 

2206 (if present). 

2207 LANGUAGE 

2208 This parameter maps to the "hreflang" attribute defined in Section 3.4.1 

2209 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``. 

2210 LINKREL 

2211 This parameter maps to the link relation type defined in Section 2.1 of 

2212 :rfc:`8288`. See `Registered Link Relation Types 

2213 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_. 

2214 FMTTYPE 

2215 This parameter maps to the "type" attribute defined in Section 3.4.1 of 

2216 :rfc:`8288`. 

2217 

2218 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`. 

2219 

2220 Examples: 

2221 The following is an example of this property, 

2222 which provides a reference to the source for the calendar object. 

2223 

2224 .. code-block:: ics 

2225 

2226 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI: 

2227 https://example.com/events 

2228 

2229 The following is an example of this property, 

2230 which provides a reference to an entity from which this one was derived. 

2231 The link relation is a vendor-defined value. 

2232 

2233 .. code-block:: ics 

2234 

2235 LINK;LINKREL="https://example.com/linkrel/derivedFrom"; 

2236 VALUE=URI: 

2237 https://example.com/tasks/01234567-abcd1234.ics 

2238 

2239 The following is an example of this property, 

2240 which provides a reference to a fragment of an XML document. 

2241 The link relation is a vendor-defined value. 

2242 

2243 .. code-block:: ics 

2244 

2245 LINK;LINKREL="https://example.com/linkrel/costStructure"; 

2246 VALUE=XML-REFERENCE: 

2247 https://example.com/xmlDocs/bidFramework.xml 

2248 #xpointer(descendant::CostStruc/range-to( 

2249 following::CostStrucEND[1])) 

2250 

2251 Set a link :class:`icalendar.prop.uri.vUri` to the event page: 

2252 

2253 .. code-block:: pycon 

2254 

2255 >>> from icalendar import Event, vUri 

2256 >>> from datetime import datetime 

2257 >>> link = vUri( 

2258 ... "http://example.com/event-page", 

2259 ... params={"LINKREL":"SOURCE"} 

2260 ... ) 

2261 >>> event = Event.new( 

2262 ... start=datetime(2025, 9, 17, 12, 0), 

2263 ... summary="An Example Event with a page" 

2264 ... ) 

2265 >>> event.links = [link] 

2266 >>> print(event.to_ical()) 

2267 BEGIN:VEVENT 

2268 SUMMARY:An Example Event with a page 

2269 DTSTART:20250917T120000 

2270 DTSTAMP:20250517T080612Z 

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

2272 LINK;LINKREL="SOURCE":http://example.com/event-page 

2273 END:VEVENT 

2274 

2275 """ 

2276 links = self.get("LINK", []) 

2277 if not isinstance(links, list): 

2278 links = [links] 

2279 return links 

2280 

2281 

2282LINKS_TYPE_SETTER: TypeAlias = ( 

2283 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference] 

2284) 

2285 

2286 

2287def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None: 

2288 """Set the LINKs.""" 

2289 _del_links(self) 

2290 if links is None: 

2291 return 

2292 if isinstance(links, (str, vUri, vUid, vXmlReference)): 

2293 links = [links] 

2294 for link in links: 

2295 if type(link) is str: 

2296 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901 

2297 self.add("LINK", link) 

2298 

2299 

2300def _del_links(self: Component) -> None: 

2301 """Delete all links.""" 

2302 self.pop("LINK") 

2303 

2304 

2305links_property = property(_get_links, _set_links, _del_links) 

2306 

2307RELATED_TO_TYPE_SETTER: TypeAlias = ( 

2308 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid] 

2309) 

2310 

2311 

2312def _get_related_to(self: Component) -> list[vText | vUri | vUid]: 

2313 """RELATED-TO properties as a list. 

2314 

2315 Purpose: 

2316 This property is used to represent a relationship or reference 

2317 between one calendar component and another. 

2318 :rfc:`9523` allows URI or UID values and a GAP parameter. 

2319 

2320 Value Type: 

2321 :rfc:`5545`: TEXT 

2322 :rfc:`9253`: URI, UID 

2323 

2324 Conformance: 

2325 Since :rfc:`5545`. this property can be specified in the "VEVENT", 

2326 "VTODO", and "VJOURNAL" calendar components. 

2327 Since :rfc:`9523`, this property MAY be specified in any 

2328 iCalendar component. 

2329 

2330 Description (:rfc:`5545`): 

2331 The property value consists of the persistent, globally 

2332 unique identifier of another calendar component. This value would 

2333 be represented in a calendar component by the "UID" property. 

2334 

2335 By default, the property value points to another calendar 

2336 component that has a PARENT relationship to the referencing 

2337 object. The "RELTYPE" property parameter is used to either 

2338 explicitly state the default PARENT relationship type to the 

2339 referenced calendar component or to override the default PARENT 

2340 relationship type and specify either a CHILD or SIBLING 

2341 relationship. The PARENT relationship indicates that the calendar 

2342 component is a subordinate of the referenced calendar component. 

2343 The CHILD relationship indicates that the calendar component is a 

2344 superior of the referenced calendar component. The SIBLING 

2345 relationship indicates that the calendar component is a peer of 

2346 the referenced calendar component. 

2347 

2348 Changes to a calendar component referenced by this property can 

2349 have an implicit impact on the related calendar component. For 

2350 example, if a group event changes its start or end date or time, 

2351 then the related, dependent events will need to have their start 

2352 and end dates changed in a corresponding way. Similarly, if a 

2353 PARENT calendar component is cancelled or deleted, then there is 

2354 an implied impact to the related CHILD calendar components. This 

2355 property is intended only to provide information on the 

2356 relationship of calendar components. It is up to the target 

2357 calendar system to maintain any property implications of this 

2358 relationship. 

2359 

2360 Description (:rfc:`9253`): 

2361 By default or when VALUE=UID is specified, the property value 

2362 consists of the persistent, globally unique identifier of another 

2363 calendar component. This value would be represented in a calendar 

2364 component by the UID property. 

2365 

2366 By default, the property value 

2367 points to another calendar component that has a PARENT relationship 

2368 to the referencing object. The RELTYPE property parameter is used 

2369 to either explicitly state the default PARENT relationship type to 

2370 the referenced calendar component or to override the default 

2371 PARENT relationship type and specify either a CHILD or SIBLING 

2372 relationship or a temporal relationship. 

2373 

2374 The PARENT relationship 

2375 indicates that the calendar component is a subordinate of the 

2376 referenced calendar component. The CHILD relationship indicates 

2377 that the calendar component is a superior of the referenced calendar 

2378 component. The SIBLING relationship indicates that the calendar 

2379 component is a peer of the referenced calendar component. 

2380 

2381 To preserve backwards compatibility, the value type MUST 

2382 be UID when the PARENT, SIBLING, or CHILD relationships 

2383 are specified. 

2384 

2385 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH, 

2386 or STARTTOSTART relationships define temporal relationships, as 

2387 specified in the RELTYPE parameter definition. 

2388 

2389 The FIRST and NEXT 

2390 define ordering relationships between calendar components. 

2391 

2392 The DEPENDS-ON relationship indicates that the current calendar 

2393 component depends on the referenced calendar component in some manner. 

2394 For example, a task may be blocked waiting on the other, 

2395 referenced, task. 

2396 

2397 The REFID and CONCEPT relationships establish 

2398 a reference from the current component to the referenced component. 

2399 Changes to a calendar component referenced by this property 

2400 can have an implicit impact on the related calendar component. 

2401 For example, if a group event changes its start or end date or 

2402 time, then the related, dependent events will need to have their 

2403 start and end dates and times changed in a corresponding way. 

2404 Similarly, if a PARENT calendar component is canceled or deleted, 

2405 then there is an implied impact to the related CHILD calendar 

2406 components. This property is intended only to provide information 

2407 on the relationship of calendar components. 

2408 

2409 Deletion of the target component, for example, the target of a 

2410 FIRST, NEXT, or temporal relationship, can result in broken links. 

2411 

2412 It is up to the target calendar system to maintain any property 

2413 implications of these relationships. 

2414 

2415 Examples: 

2416 :rfc:`5545` examples of this property: 

2417 

2418 .. code-block:: ics 

2419 

2420 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com 

2421 

2422 .. code-block:: ics 

2423 

2424 RELATED-TO:19960401-080045-4000F192713-0052@example.com 

2425 

2426 :rfc:`9253` examples of this property: 

2427 

2428 .. code-block:: ics 

2429 

2430 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH: 

2431 https://example.com/caldav/user/jb/cal/ 

2432 19960401-080045-4000F192713.ics 

2433 

2434 See also :class:`icalendar.enums.RELTYPE`. 

2435 

2436 """ 

2437 result = self.get("RELATED-TO", []) 

2438 if not isinstance(result, list): 

2439 return [result] 

2440 return result 

2441 

2442 

2443def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None: 

2444 """Set the RELATED-TO properties.""" 

2445 _del_related_to(self) 

2446 if values is None: 

2447 return 

2448 if not isinstance(values, list): 

2449 values = [values] 

2450 for value in values: 

2451 self.add("RELATED-TO", value) 

2452 

2453 

2454def _del_related_to(self: Component): 

2455 """Delete the RELATED-TO properties.""" 

2456 self.pop("RELATED-TO", None) 

2457 

2458 

2459related_to_property = property(_get_related_to, _set_related_to, _del_related_to) 

2460 

2461 

2462def _get_concepts(self: Component) -> list[vUri]: 

2463 """CONCEPT 

2464 

2465 Purpose: 

2466 CONCEPT defines the formal categories for a calendar component. 

2467 

2468 Conformance: 

2469 Since :rfc:`9253`, 

2470 this property can be specified zero or more times in any iCalendar component. 

2471 

2472 Description: 

2473 This property is used to specify formal categories or classifications of 

2474 the calendar component. The values are useful in searching for a calendar 

2475 component of a particular type and category. 

2476 

2477 This categorization is distinct from the more informal "tagging" of components 

2478 provided by the existing CATEGORIES property. It is expected that the value of 

2479 the CONCEPT property will reference an external resource that provides 

2480 information about the categorization. 

2481 

2482 In addition, a structured URI value allows for hierarchical categorization of 

2483 events. 

2484 

2485 Possible category resources are the various proprietary systems, for example, 

2486 the Library of Congress, or an open source of categorization data. 

2487 

2488 Examples: 

2489 The following is an example of this property. 

2490 It points to a server acting as the source for the calendar object. 

2491 

2492 .. code-block:: ics 

2493 

2494 CONCEPT:https://example.com/event-types/arts/music 

2495 

2496 .. seealso:: 

2497 

2498 :attr:`icalendar.prop.categories.vCategory` 

2499 """ 

2500 concepts = self.get("CONCEPT", []) 

2501 if not isinstance(concepts, list): 

2502 concepts = [concepts] 

2503 return concepts 

2504 

2505 

2506CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None 

2507 

2508 

2509def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER): 

2510 """Set the concepts.""" 

2511 _del_concepts(self) 

2512 if concepts is None: 

2513 return 

2514 if not isinstance(concepts, list): 

2515 concepts = [concepts] 

2516 for value in concepts: 

2517 self.add("CONCEPT", value) 

2518 

2519 

2520def _del_concepts(self: Component): 

2521 """Delete the concepts.""" 

2522 self.pop("CONCEPT", None) 

2523 

2524 

2525concepts_property = property(_get_concepts, _set_concepts, _del_concepts) 

2526 

2527 

2528def multi_string_property(name: str, doc: str): 

2529 """A property for an iCalendar Property that can occur multiple times.""" 

2530 

2531 def fget(self: Component) -> list[str]: 

2532 """Get the values of a multi-string property.""" 

2533 value = self.get(name, []) 

2534 if not isinstance(value, list): 

2535 value = [value] 

2536 return value 

2537 

2538 def fset(self: Component, value: list[str] | str | None) -> None: 

2539 """Set the values of a multi-string property.""" 

2540 fdel(self) 

2541 if value is None: 

2542 return 

2543 if not isinstance(value, list): 

2544 value = [value] 

2545 for value in value: 

2546 self.add(name, value) 

2547 

2548 def fdel(self: Component): 

2549 """Delete the values of a multi-string property.""" 

2550 self.pop(name, None) 

2551 

2552 return property(fget, fset, fdel, doc=doc) 

2553 

2554 

2555refids_property = multi_string_property( 

2556 "REFID", 

2557 """REFID 

2558 

2559Purpose: 

2560 REFID acts as a key for associated iCalendar entities. 

2561 

2562Conformance: 

2563 Since :rfc:`9253`, 

2564 this property can be specified zero or more times in any iCalendar component. 

2565 

2566Description: 

2567 The value of this property is free-form text that creates an 

2568 identifier for associated components. 

2569 All components that use the same REFID value are associated through 

2570 that value and can be located or retrieved as a group. 

2571 For example, all of the events in a travel itinerary 

2572 would have the same REFID value, so as to be grouped together. 

2573 

2574Examples: 

2575 The following is an example of this property. 

2576 

2577 .. code-block:: ics 

2578 

2579 REFID:itinerary-2014-11-17 

2580 

2581 Use a REFID to associate several VTODOs: 

2582 

2583 .. code-block:: pycon 

2584 

2585 >>> from icalendar import Todo 

2586 >>> todo_1 = Todo.new( 

2587 ... summary="turn off stove", 

2588 ... refids=["travel", "alps"] 

2589 ... ) 

2590 >>> todo_2 = Todo.new( 

2591 ... summary="pack backpack", 

2592 ... refids=["travel", "alps"] 

2593 ... ) 

2594 >>> todo_1.refids == todo_2.refids 

2595 True 

2596 

2597.. note:: 

2598 

2599 When you assign a list to this property, the returned list 

2600 is the same object stored in the component. Modifying it (``append()``, 

2601 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2602 the component stores. 

2603 

2604 However, if you assign a single string value to this property, the returned 

2605 list is a temporary copy, and changes to this list don't affect the component. 

2606""", 

2607) 

2608 

2609REQUEST_STATUS_property = multi_string_property( 

2610 "REQUEST-STATUS", 

2611 """This property defines the status code returned for a scheduling request. 

2612 

2613You can assign a single string, a list of strings, or ``None`` to this 

2614property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2615an empty list removes all REQUEST-STATUS values, as does deleting the 

2616property. 

2617 

2618The value consists of a short return status code component, a longer 

2619return status description component, and optionally a status-specific 

2620data component, separated by semicolons (statcode;statdesc[;extdata]). 

2621The return status components are defined in :rfc:`5545#section-3.8.8.3`. 

2622 

2623The REQUEST-STATUS property can be specified in the following 

2624icalendar components as ``REQUEST_STATUS``. 

2625 

2626- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>` 

2627- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>` 

2628- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>` 

2629- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>` 

2630 

2631Note: 

2632 When you assign a list to this property, the returned list 

2633 is the same object stored in the component. Modifying it (``append()``, 

2634 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2635 the component stores. 

2636 

2637 However, if you assign a single string value to this property, the returned 

2638 list is a temporary copy, and changes to this list don't affect the component. 

2639 

2640Parameters: 

2641 request_status(str | list[str] | None): 

2642 Either a single status string, a list of status strings, or 

2643 ``None`` to set the component's status code returned for a 

2644 scheduling request. 

2645 

2646Example: 

2647 Add a request status to an event: 

2648 

2649 .. code-block:: pycon 

2650 

2651 >>> from icalendar import Event 

2652 >>> event = Event.new(request_status="2.0;Success") 

2653 >>> event.REQUEST_STATUS == ["2.0;Success"] 

2654 True 

2655""", 

2656) 

2657 

2658RESOURCES_property = multi_string_property( 

2659 "RESOURCES", 

2660 """This property defines resources for a calendar component. 

2661 

2662You can assign a single string, a list of strings, or ``None`` to this 

2663property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2664an empty list removes all RESOURCES values, as does deleting the property. 

2665 

2666The value is a comma-separated list of resources, such as equipment, 

2667facilities, or other things that the component needs. Each item of the 

2668list becomes its own RESOURCES property value. See 

2669:rfc:`5545#section-3.8.1.10` for the specification. 

2670 

2671The RESOURCES property can be specified in the following 

2672icalendar components as ``resources`` using the component's 

2673``new()`` constructor. 

2674 

2675- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>` 

2676- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>` 

2677 

2678Parameters: 

2679 resources(str | list[str] | None): 

2680 Either a single resource string, a list of resource strings, 

2681 or ``None`` to set the component's resources. 

2682 

2683Note: 

2684 When you assign a list to this property, the returned list 

2685 is the same object stored in the component. Modifying it (``append()``, 

2686 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2687 the component stores. 

2688 

2689 However, if you assign a single string value to this property, the returned 

2690 list is a temporary copy, and changes to this list don't affect the component. 

2691 

2692Example: 

2693 Add resources to an event: 

2694 

2695 .. code-block:: pycon 

2696 

2697 >>> from icalendar import Event 

2698 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"]) 

2699 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"] 

2700 True 

2701""", 

2702) 

2703 

2704 

2705ATTACHMENTS_TYPE_SETTER: TypeAlias = ( 

2706 str | bytes | vUri | vBinary | None | list[str | bytes | vUri | vBinary] 

2707) 

2708 

2709 

2710def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary: 

2711 """Convert one attachment value.""" 

2712 if isinstance(value, (vUri, vBinary)): 

2713 return value 

2714 if isinstance(value, str): 

2715 return vUri(value) 

2716 if isinstance(value, bytes): 

2717 return vBinary(value) 

2718 raise TypeError( 

2719 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}." 

2720 ) 

2721 

2722 

2723def _get_attachments(self: Component) -> list[vUri | vBinary]: 

2724 """Get all the attachments""" 

2725 attachments = self.get("ATTACH", []) 

2726 if not isinstance(attachments, SEQUENCE_TYPES): 

2727 return [attachments] 

2728 return list(attachments) 

2729 

2730 

2731def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None: 

2732 """Set attachments properties""" 

2733 if value is None: 

2734 _del_attachments(self) 

2735 return 

2736 if not isinstance(value, list): 

2737 value = [value] 

2738 attachments = [_normalize_attachment(attachment) for attachment in value] 

2739 _del_attachments(self) 

2740 for attachment in attachments: 

2741 self.add("ATTACH", attachment) 

2742 

2743 

2744def _del_attachments(self: Component) -> None: 

2745 """Delete all attachments""" 

2746 self.pop("ATTACH", None) 

2747 

2748 

2749attachments_property = property( 

2750 _get_attachments, 

2751 _set_attachments, 

2752 _del_attachments, 

2753 """This property defines the attachments for a component. 

2754 

2755Setting this property replaces all existing attachments. A :class:`str` 

2756is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is 

2757converted to :class:`~icalendar.prop.binary.vBinary`. Values that are 

2758already :class:`~icalendar.prop.uri.vUri` or 

2759:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their 

2760parameters are preserved. Setting ``None`` or an empty list removes all 

2761attachments, as does deleting the property. 

2762 

2763Parameters: 

2764 attachments(str | bytes | vUri | vBinary | list | None): 

2765 A single attachment, or a list of attachments to set. Accepts 

2766 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`, 

2767 and :class:`~icalendar.prop.binary.vBinary`, individually or 

2768 mixed together in a list. 

2769 

2770Example: 

2771 Attach a URI to an event, then replace it with a URI and inline 

2772 binary data together: 

2773 

2774 .. code-block:: pycon 

2775 

2776 >>> from icalendar import Event, vUri, vBinary 

2777 >>> event = Event() 

2778 >>> event.attachments 

2779 [] 

2780 >>> event.attachments = ["https://example.com/agenda.pdf"] 

2781 >>> print(event.to_ical().decode()) 

2782 BEGIN:VEVENT 

2783 ATTACH:https://example.com/agenda.pdf 

2784 END:VEVENT 

2785 >>> event.attachments = [ 

2786 ... vUri( 

2787 ... "https://example.com/agenda.pdf", 

2788 ... params={"FMTTYPE": "application/pdf"}, 

2789 ... ), 

2790 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},), 

2791 ... ] 

2792 >>> len(event.attachments) 

2793 2 

2794 

2795.. note:: 

2796 

2797 An alarm as an audio action must not contain more than one attachment. 

2798 

2799 List modifications do not modify the component. Methods such as 

2800 ``append()``, ``extend()``, and ``remove()``, as well as item 

2801 assignment, act on a copy. Assign the list back to the property, or 

2802 use :meth:`Component.add <icalendar.cal.component.Component.add>` 

2803 with a typed value instead. 

2804 

2805.. seealso:: 

2806 

2807 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH`` 

2808 property. 

2809""", 

2810) 

2811 

2812 

2813__all__ = [ 

2814 "ATTACHMENTS_TYPE_SETTER", 

2815 "ATTENDEE_TYPE_SETTER", 

2816 "CONCEPTS_TYPE_SETTER", 

2817 "LINKS_TYPE_SETTER", 

2818 "RECURRENCE_ID", 

2819 "RELATED_TO_TYPE_SETTER", 

2820 "REQUEST_STATUS_property", 

2821 "RESOURCES_property", 

2822 "attachments_property", 

2823 "attendees_property", 

2824 "busy_type_property", 

2825 "categories_property", 

2826 "class_property", 

2827 "color_property", 

2828 "comments_property", 

2829 "concepts_property", 

2830 "conferences_property", 

2831 "contacts_property", 

2832 "create_single_property", 

2833 "description_property", 

2834 "descriptions_property", 

2835 "duration_property", 

2836 "exdates_property", 

2837 "get_duration_property", 

2838 "get_end_property", 

2839 "get_start_end_duration_with_validation", 

2840 "get_start_property", 

2841 "images_property", 

2842 "links_property", 

2843 "location_property", 

2844 "multi_language_text_property", 

2845 "multi_string_property", 

2846 "organizer_property", 

2847 "priority_property", 

2848 "property_del_duration", 

2849 "property_doc_duration_template", 

2850 "property_get_duration", 

2851 "property_set_duration", 

2852 "rdates_property", 

2853 "refids_property", 

2854 "related_to_property", 

2855 "repeat_property", 

2856 "rfc_7953_dtend_property", 

2857 "rfc_7953_dtstart_property", 

2858 "rfc_7953_duration_property", 

2859 "rfc_7953_end_property", 

2860 "rrules_property", 

2861 "sequence_property", 

2862 "set_duration_with_locking", 

2863 "set_end_with_locking", 

2864 "set_start_with_locking", 

2865 "single_int_property", 

2866 "single_utc_property", 

2867 "source_property", 

2868 "status_property", 

2869 "summary_property", 

2870 "transparency_property", 

2871 "uid_property", 

2872 "url_property", 

2873]