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.attr.property_list import PropertyListView
11from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum
12from icalendar.error import IncompleteComponent, InvalidCalendar
13from icalendar.parser_tools import SEQUENCE_TYPES
14from icalendar.prop import (
15 vBinary,
16 vCalAddress,
17 vCategory,
18 vDDDTypes,
19 vDuration,
20 vRecur,
21 vText,
22 vUid,
23 vUnknown,
24 vUri,
25 vXmlReference,
26)
27from icalendar.prop.conference import Conference
28from icalendar.prop.image import Image
29from icalendar.timezone import tzp
30from icalendar.tools import is_date
31
32if TYPE_CHECKING:
33 from collections.abc import Callable
34
35 from icalendar.cal import Component
36 from icalendar.cal.event import Event
37 from icalendar.cal.todo import Todo
38
39
40def _get_rdates(
41 self: Component,
42) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]:
43 """The RDATE property defines the list of DATE-TIME values for recurring components.
44
45 RDATE is defined in :rfc:`5545`.
46 The return value is a list of tuples ``(start, end)``.
47
48 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
49 with and without timezone.
50
51 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
52 if the end is specified.
53
54 Value Type:
55 The default value type for this property is DATE-TIME.
56 The value type can be set to DATE or PERIOD.
57
58 Property Parameters:
59 IANA, non-standard, value data type, and time
60 zone identifier property parameters can be specified on this
61 property.
62
63 Conformance:
64 This property can be specified in recurring "VEVENT",
65 "VTODO", and "VJOURNAL" calendar components as well as in the
66 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
67 calendar component.
68
69 Description:
70 This property can appear along with the "RRULE"
71 property to define an aggregate set of repeating occurrences.
72 When they both appear in a recurring component, the recurrence
73 instances are defined by the union of occurrences defined by both
74 the "RDATE" and "RRULE".
75
76 The recurrence dates, if specified, are used in computing the
77 recurrence set. The recurrence set is the complete set of
78 recurrence instances for a calendar component. The recurrence set
79 is generated by considering the initial "DTSTART" property along
80 with the "RRULE", "RDATE", and "EXDATE" properties contained
81 within the recurring component. The "DTSTART" property defines
82 the first instance in the recurrence set. The "DTSTART" property
83 value SHOULD match the pattern of the recurrence rule, if
84 specified. The recurrence set generated with a "DTSTART" property
85 value that doesn't match the pattern of the rule is undefined.
86 The final recurrence set is generated by gathering all of the
87 start DATE-TIME values generated by any of the specified "RRULE"
88 and "RDATE" properties, and then excluding any start DATE-TIME
89 values specified by "EXDATE" properties. This implies that start
90 DATE-TIME values specified by "EXDATE" properties take precedence
91 over those specified by inclusion properties (i.e., "RDATE" and
92 "RRULE"). Where duplicate instances are generated by the "RRULE"
93 and "RDATE" properties, only one recurrence is considered.
94 Duplicate instances are ignored.
95
96 Example:
97 Below, we set one RDATE in a list and get the resulting tuple of start and end.
98
99 .. code-block:: pycon
100
101 >>> from icalendar import Event
102 >>> from datetime import datetime
103 >>> event = Event()
104
105 # Add a list of recurrence dates
106 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
107 >>> event.rdates
108 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
109
110 .. note::
111
112 Modifying the returned list does not change the RDATE value. Assign to
113 ``rdates`` for the relevant component or use
114 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
115 If you want to compute recurrences, have a look at
116 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
117
118 """
119 result = []
120 rdates = self.get("RDATE", [])
121 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
122 for dts in rdates.dts:
123 rdate = dts.dt
124 if isinstance(rdate, tuple):
125 # we have a period as rdate
126 if isinstance(rdate[1], timedelta):
127 result.append((rdate[0], rdate[0] + rdate[1]))
128 else:
129 result.append(rdate)
130 else:
131 # we have a date/datetime
132 result.append((rdate, None))
133 return result
134
135
136def _set_rdates(self: Component, value) -> None:
137 """Set the RDATE values, replacing any existing ones.
138
139 ``value`` is a list as returned by :attr:`rdates` (each item a date, a
140 datetime, or a ``(start, end)`` period tuple). Setting an empty list or
141 :obj:`None` removes the RDATE property.
142 """
143 _del_rdates(self)
144 if value:
145 self.add("RDATE", value)
146
147
148def _del_rdates(self: Component) -> None:
149 """Delete all RDATE values."""
150 self.pop("RDATE", None)
151
152
153rdates_property = property(_get_rdates, _set_rdates, _del_rdates)
154
155
156def _get_exdates(self: Component) -> list[date | datetime]:
157 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
158
159 EXDATE is defined in :rfc:`5545`.
160
161 Value Type:
162 The default value type for this property is DATE-TIME.
163 The value type can be set to DATE.
164
165 Property Parameters:
166 IANA, non-standard, value data type, and time
167 zone identifier property parameters can be specified on this
168 property.
169
170 Conformance:
171 This property can be specified in recurring "VEVENT",
172 "VTODO", and "VJOURNAL" calendar components as well as in the
173 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
174 calendar component.
175
176 Description:
177 The exception dates, if specified, are used in
178 computing the recurrence set. The recurrence set is the complete
179 set of recurrence instances for a calendar component. The
180 recurrence set is generated by considering the initial "DTSTART"
181 property along with the "RRULE", "RDATE", and "EXDATE" properties
182 contained within the recurring component. The "DTSTART" property
183 defines the first instance in the recurrence set. The "DTSTART"
184 property value SHOULD match the pattern of the recurrence rule, if
185 specified. The recurrence set generated with a "DTSTART" property
186 value that doesn't match the pattern of the rule is undefined.
187 The final recurrence set is generated by gathering all of the
188 start DATE-TIME values generated by any of the specified "RRULE"
189 and "RDATE" properties, and then excluding any start DATE-TIME
190 values specified by "EXDATE" properties. This implies that start
191 DATE-TIME values specified by "EXDATE" properties take precedence
192 over those specified by inclusion properties (i.e., "RDATE" and
193 "RRULE"). When duplicate instances are generated by the "RRULE"
194 and "RDATE" properties, only one recurrence is considered.
195 Duplicate instances are ignored.
196
197 The "EXDATE" property can be used to exclude the value specified
198 in "DTSTART". However, in such cases, the original "DTSTART" date
199 MUST still be maintained by the calendaring and scheduling system
200 because the original "DTSTART" value has inherent usage
201 dependencies by other properties such as the "RECURRENCE-ID".
202
203 Example:
204 Below, we add an exdate in a list and get the resulting list of exdates.
205
206 .. code-block:: pycon
207
208 >>> from icalendar import Event
209 >>> from datetime import datetime
210 >>> event = Event()
211
212 # Add a list of excluded dates
213 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
214 >>> event.exdates
215 [datetime.datetime(2025, 4, 28, 16, 5)]
216
217 .. note::
218
219 Modifying the returned list does not change the EXDATE value. Assign to
220 ``exdates`` for the relevant component or use
221 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
222 If you want to compute recurrences, have a look at
223 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
224
225 """
226 result = []
227 exdates = self.get("EXDATE", [])
228 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
229 for dts in exdates.dts:
230 exdate = dts.dt
231 # we have a date/datetime
232 result.append(exdate)
233 return result
234
235
236def _set_exdates(self: Component, value) -> None:
237 """Set the EXDATE values, replacing any existing ones.
238
239 ``value`` is a list as returned by :attr:`exdates` (each item a date or a
240 datetime). Setting an empty list or :obj:`None` removes the EXDATE property.
241 """
242 _del_exdates(self)
243 if value:
244 self.add("EXDATE", value)
245
246
247def _del_exdates(self: Component) -> None:
248 """Delete all EXDATE values."""
249 self.pop("EXDATE", None)
250
251
252exdates_property = property(_get_exdates, _set_exdates, _del_exdates)
253
254
255def _get_rrules(self: Component) -> list[vRecur]:
256 """RRULE defines a rule or repeating pattern for recurring components.
257
258 RRULE is defined in :rfc:`5545`.
259 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
260
261 Property Parameters:
262 IANA and non-standard property parameters can
263 be specified on this property.
264
265 Conformance:
266 This property can be specified in recurring "VEVENT",
267 "VTODO", and "VJOURNAL" calendar components as well as in the
268 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
269 calendar component, but it SHOULD NOT be specified more than once.
270 The recurrence set generated with multiple "RRULE" properties is
271 undefined.
272
273 Description:
274 The recurrence rule, if specified, is used in computing
275 the recurrence set. The recurrence set is the complete set of
276 recurrence instances for a calendar component. The recurrence set
277 is generated by considering the initial "DTSTART" property along
278 with the "RRULE", "RDATE", and "EXDATE" properties contained
279 within the recurring component. The "DTSTART" property defines
280 the first instance in the recurrence set. The "DTSTART" property
281 value SHOULD be synchronized with the recurrence rule, if
282 specified. The recurrence set generated with a "DTSTART" property
283 value not synchronized with the recurrence rule is undefined. The
284 final recurrence set is generated by gathering all of the start
285 DATE-TIME values generated by any of the specified "RRULE" and
286 "RDATE" properties, and then excluding any start DATE-TIME values
287 specified by "EXDATE" properties. This implies that start DATE-
288 TIME values specified by "EXDATE" properties take precedence over
289 those specified by inclusion properties (i.e., "RDATE" and
290 "RRULE"). Where duplicate instances are generated by the "RRULE"
291 and "RDATE" properties, only one recurrence is considered.
292 Duplicate instances are ignored.
293
294 The "DTSTART" property specified within the iCalendar object
295 defines the first instance of the recurrence. In most cases, a
296 "DTSTART" property of DATE-TIME value type used with a recurrence
297 rule, should be specified as a date with local time and time zone
298 reference to make sure all the recurrence instances start at the
299 same local time regardless of time zone changes.
300
301 If the duration of the recurring component is specified with the
302 "DTEND" or "DUE" property, then the same exact duration will apply
303 to all the members of the generated recurrence set. Else, if the
304 duration of the recurring component is specified with the
305 "DURATION" property, then the same nominal duration will apply to
306 all the members of the generated recurrence set and the exact
307 duration of each recurrence instance will depend on its specific
308 start time. For example, recurrence instances of a nominal
309 duration of one day will have an exact duration of more or less
310 than 24 hours on a day where a time zone shift occurs. The
311 duration of a specific recurrence may be modified in an exception
312 component or simply by using an "RDATE" property of PERIOD value
313 type.
314
315 Examples:
316 Daily for 10 occurrences:
317
318 .. code-block:: pycon
319
320 >>> from icalendar import Event
321 >>> from datetime import datetime
322 >>> from zoneinfo import ZoneInfo
323 >>> event = Event()
324 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
325 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
326 >>> print(event.to_ical())
327 BEGIN:VEVENT
328 DTSTART;TZID=America/New_York:19970902T090000
329 RRULE:FREQ=DAILY;COUNT=10
330 END:VEVENT
331 >>> event.rrules
332 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
333
334 Daily until December 24, 1997:
335
336 .. code-block:: pycon
337
338 >>> from icalendar import Event, vRecur
339 >>> from datetime import datetime
340 >>> from zoneinfo import ZoneInfo
341 >>> event = Event()
342 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
343 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
344 >>> print(event.to_ical())
345 BEGIN:VEVENT
346 DTSTART;TZID=America/New_York:19970902T090000
347 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
348 END:VEVENT
349 >>> event.rrules
350 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
351
352 .. note::
353
354 You cannot modify the RRULE value by modifying the result.
355 Use :meth:`Component.add <icalendar.cal.component.Component.add>` to add values.
356
357 If you want to compute recurrences, have a look at
358 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
359
360 """ # noqa: E501
361 rrules = self.get("RRULE", [])
362 if not isinstance(rrules, list):
363 return [rrules]
364 return rrules
365
366
367rrules_property = property(_get_rrules)
368
369
370def multi_language_text_property(
371 main_prop: str, compatibility_prop: str | None, doc: str
372) -> property:
373 """This creates a text property.
374
375 This property can be defined several times with different ``LANGUAGE`` parameters.
376
377 Parameters:
378 main_prop (str): The property to set and get, such as ``NAME``
379 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
380 doc (str): The documentation string
381 """
382
383 def fget(self: Component) -> str | None:
384 """Get the property"""
385 result = self.get(main_prop)
386 if result is None and compatibility_prop is not None:
387 result = self.get(compatibility_prop)
388 if isinstance(result, list):
389 for item in result:
390 if "LANGUAGE" not in item.params:
391 return item
392 return result
393
394 def fset(self: Component, value: str | None):
395 """Set the property."""
396 fdel(self)
397 if value is not None:
398 self.add(main_prop, value)
399 if compatibility_prop is not None:
400 self.add(compatibility_prop, value)
401
402 def fdel(self: Component):
403 """Delete the property."""
404 self.pop(main_prop, None)
405 if compatibility_prop is not None:
406 self.pop(compatibility_prop, None)
407
408 return property(fget, fset, fdel, doc)
409
410
411def single_int_property(
412 prop: str, default: int, doc: str, *, min_value: int | None = None
413) -> property:
414 """Create a property for an int value that exists only once.
415
416 Parameters:
417 default: Required. The default value.
418 doc: Required. The documentation string.
419 prop: Required. The name of the property.
420 min_value: If set, the value must be greater than or equal to this minimum.
421
422 .. versionadded:: 7.3.0
423 Added the ``min_value`` parameter.
424 """
425
426 def fget(self: Component) -> int:
427 """Get the property"""
428 try:
429 return int(self.get(prop, default))
430 except ValueError as e:
431 raise InvalidCalendar(f"{prop} must be an int") from e
432
433 def fset(self: Component, value: int | None):
434 """Set the property."""
435 if value is not None:
436 if not isinstance(value, int) or isinstance(value, bool):
437 raise TypeError(f"{prop} must be an int, got {value!r}")
438 if min_value is not None and value < min_value:
439 raise InvalidCalendar(f"{prop} must be >= {min_value}, got {value}")
440 fdel(self)
441 if value is not None:
442 self.add(prop, value)
443
444 def fdel(self: Component):
445 """Delete the property."""
446 self.pop(prop, None)
447
448 return property(fget, fset, fdel, doc)
449
450
451def _decode_single_utc_value(name: str, value: object) -> date | datetime:
452 """Extract and check one datetime value of a singleton UTC property.
453
454 Raises:
455 ValueError: If a text or unknown value cannot be parsed.
456 InvalidCalendar: If the decoded value is not a date or datetime.
457 """
458 if isinstance(value, (vText, vUnknown)):
459 # we might be in an attribute that is not typed
460 value = vDDDTypes.from_ical(value)
461 else:
462 value = getattr(value, "dt", value)
463 if not isinstance(value, date):
464 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
465 return value
466
467
468def single_utc_property(name: str, docs: str) -> property:
469 """Create a property to access a value of datetime in UTC timezone.
470
471 Parameters:
472 name: name of the property
473 docs: documentation string
474 """
475
476 def fget(self: Component) -> datetime | None:
477 """Get the value."""
478 if name not in self:
479 return None
480 raw = self.get(name)
481 # Code continues with less indentation
482 # Broken calendars can repeat singleton properties. Keep all values
483 # for serialization; when reading, use the earliest valid value.
484 items = raw if isinstance(raw, list) else [raw]
485 values = []
486 invalid = raw
487 # duplicate lists are tiny, so the loop overhead is negligible
488 for item in items:
489 try:
490 values.append(_decode_single_utc_value(name, item))
491 except (InvalidCalendar, ValueError): # noqa: PERF203
492 # Report the decoded value as before, or the raw item
493 # when it cannot be decoded.
494 if isinstance(item, (vText, vUnknown)):
495 invalid = item
496 else:
497 invalid = getattr(item, "dt", item)
498 continue
499 if not values:
500 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {invalid}")
501 return min(tzp.localize_utc(value) for value in values)
502
503 def fset(self: Component, value: datetime | None):
504 """Set the value"""
505 if value is None:
506 fdel(self)
507 return
508 if not isinstance(value, date):
509 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
510 fdel(self)
511 self.add(name, tzp.localize_utc(value))
512
513 def fdel(self: Component):
514 """Delete the property."""
515 self.pop(name, None)
516
517 return property(fget, fset, fdel, doc=docs)
518
519
520def single_string_property(
521 name: str, docs: str, other_name: str | list[str] | None = None, default: str = ""
522) -> property:
523 """Create a property to access a single string value."""
524 other_names = (
525 []
526 if other_name is None
527 else [other_name]
528 if isinstance(other_name, str)
529 else list(other_name)
530 )
531
532 def fget(self: Component) -> str:
533 """Get the value."""
534 result = self.get(name, None)
535 if result is None:
536 for alias in other_names:
537 result = self.get(alias, None)
538 if result is not None:
539 break
540 if result is None or result == []:
541 return default
542 if isinstance(result, list):
543 return result[0]
544 return result
545
546 def fset(self: Component, value: str | None):
547 """Set the value.
548
549 Setting the value to None will delete it.
550 """
551 fdel(self)
552 if value is not None:
553 self.add(name, value)
554
555 def fdel(self: Component):
556 """Delete the property."""
557 self.pop(name, None)
558 for alias in other_names:
559 self.pop(alias, None)
560
561 return property(fget, fset, fdel, doc=docs)
562
563
564color_property = single_string_property(
565 "COLOR",
566 """This property specifies a color used for displaying the component.
567
568 This implements :rfc:`7986` ``COLOR`` property.
569
570 Property Parameters:
571 IANA and non-standard property parameters can
572 be specified on this property.
573
574 Conformance:
575 This property can be specified once in an iCalendar
576 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
577
578 Description:
579 This property specifies a color that clients MAY use
580 when presenting the relevant data to a user. Typically, this
581 would appear as the "background" color of events or tasks. The
582 value is a case-insensitive color name taken from the CSS3 set of
583 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
584
585 Example:
586 ``"turquoise"``, ``"#ffffff"``
587
588 .. code-block:: pycon
589
590 >>> from icalendar import Todo
591 >>> todo = Todo()
592 >>> todo.color = "green"
593 >>> print(todo.to_ical())
594 BEGIN:VTODO
595 COLOR:green
596 END:VTODO
597 """,
598)
599
600sequence_property = single_int_property(
601 "SEQUENCE",
602 0,
603 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
604
605Value Type:
606 INTEGER
607
608Property Parameters:
609 IANA and non-standard property parameters can be specified on this property.
610
611Conformance:
612 The property can be specified in "VEVENT", "VTODO", or
613 "VJOURNAL" calendar component.
614
615Description:
616 When a calendar component is created, its sequence
617 number is 0. It is monotonically incremented by the "Organizer's"
618 CUA each time the "Organizer" makes a significant revision to the
619 calendar component.
620
621 The "Organizer" includes this property in an iCalendar object that
622 it sends to an "Attendee" to specify the current version of the
623 calendar component.
624
625 The "Attendee" includes this property in an iCalendar object that
626 it sends to the "Organizer" to specify the version of the calendar
627 component to which the "Attendee" is referring.
628
629 A change to the sequence number is not the mechanism that an
630 "Organizer" uses to request a response from the "Attendees". The
631 "RSVP" parameter on the "ATTENDEE" property is used by the
632 "Organizer" to indicate that a response from the "Attendees" is
633 requested.
634
635 Recurrence instances of a recurring component MAY have different
636 sequence numbers.
637
638Examples:
639 The following is an example of this property for a calendar
640 component that was just created by the "Organizer":
641
642 .. code-block:: pycon
643
644 >>> from icalendar import Event
645 >>> event = Event()
646 >>> event.sequence
647 0
648
649 The following is an example of this property for a calendar
650 component that has been revised 10 different times by the
651 "Organizer":
652
653 .. code-block:: pycon
654
655 >>> from icalendar import Calendar
656 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
657 >>> event = calendar.events[0]
658 >>> event.sequence
659 10
660
661 Raises:
662 TypeError: If the value is not an ``int``. Booleans are rejected, too,
663 even though ``bool`` subclasses ``int``.
664
665 ~icalendar.error.InvalidCalendar: If the value is negative.
666
667 .. versionchanged:: 7.3.0
668 Negative values are no longer accepted.
669 """, # noqa: E501
670 min_value=0,
671)
672
673
674def _get_categories(component: Component) -> list[str]:
675 """Get all the categories."""
676 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
677 if isinstance(categories, list):
678 _set_categories(
679 component,
680 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
681 )
682 return _get_categories(component)
683 if categories is None:
684 categories = vCategory([])
685 component.add("CATEGORIES", categories)
686 return categories.cats
687
688
689def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
690 """Set the categories."""
691 if not cats and cats != []:
692 _del_categories(component)
693 return
694 component["CATEGORIES"] = categories = vCategory(cats)
695 if isinstance(cats, list):
696 cats.clear()
697 cats.extend(categories.cats)
698 categories.cats = cats
699
700
701def _del_categories(component: Component) -> None:
702 """Delete the categories."""
703 component.pop("CATEGORIES", None)
704
705
706categories_property = property(
707 _get_categories,
708 _set_categories,
709 _del_categories,
710 """This property defines the categories for a component.
711
712The categories property is used to specify categories or subtypes of the
713calendar component. The categories are useful to search for a calendar
714component of a particular type and category.
715
716Within the calendar components, specify categories as a list of strings.
717You can get, set, and delete categories for a component.
718
719This property can be used in icalendar through its Python attributes of:
720
721- :attr:`Available.categories <icalendar.cal.available.Available.categories>`
722- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>`
723- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>`
724- :attr:`Event.categories <icalendar.cal.event.Event.categories>`
725- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>`
726- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>`
727
728The categories property for ``Available`` and ``Availability`` complies with
729:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with
730:rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`.
731
732Note:
733 At present, icalendar doesn't take the LANGUAGE parameter as defined
734 in :rfc:`5545#section-3.2.10` into account.
735
736Parameters:
737 categories(list[str]): A list of categories as strings.
738
739Example:
740 Create an event, add categories to it, print its ical representation,
741 append another category, and finally compare the result
742 against its expected value.
743
744 .. code-block:: pycon
745
746 >>> from icalendar import Event
747 >>> event = Event()
748 >>> event.categories = ["Work", "Meeting"]
749 >>> print(event.to_ical())
750 BEGIN:VEVENT
751 CATEGORIES:Work,Meeting
752 END:VEVENT
753 >>> event.categories.append("Lecture")
754 >>> event.categories == ["Work", "Meeting", "Lecture"]
755 True
756
757See also:
758 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>`
759""",
760)
761
762
763def _get_attendees(self: Component) -> list[vCalAddress]:
764 """Get attendees."""
765 value = self.get("ATTENDEE")
766 if value is None:
767 value = []
768 self["ATTENDEE"] = value
769 return value
770 if isinstance(value, vCalAddress):
771 return [value]
772 return value
773
774
775ATTENDEE_TYPE_SETTER: TypeAlias = Sequence[vCalAddress | str] | vCalAddress | str | None
776
777
778def _set_attendees(self: Component, value: ATTENDEE_TYPE_SETTER):
779 """Set attendees."""
780 _del_attendees(self)
781 if value is None:
782 return
783 if isinstance(value, (vCalAddress, str)):
784 value = [value]
785 elif not isinstance(value, list):
786 value = list(value) if isinstance(value, Sequence) else [value]
787 for index, attendee in enumerate(value):
788 # vCalAddress subclasses str, so exclude it before normalizing strings
789 if not isinstance(attendee, vCalAddress) and isinstance(attendee, str):
790 value[index] = vCalAddress.new(attendee)
791 self["ATTENDEE"] = value
792
793
794def _del_attendees(self: Component):
795 """Delete all attendees."""
796 self.pop("ATTENDEE", None)
797
798
799attendees_property = property(
800 _get_attendees,
801 _set_attendees,
802 _del_attendees,
803 """ATTENDEE defines one or more "Attendees" within a calendar component.
804
805Conformance:
806 This property MUST be specified in an iCalendar object
807 that specifies a group-scheduled calendar entity. This property
808 MUST NOT be specified in an iCalendar object when publishing the
809 calendar information (e.g., NOT in an iCalendar object that
810 specifies the publication of a calendar user's busy time, event,
811 to-do, or journal). This property is not specified in an
812 iCalendar object that specifies only a time zone definition or
813 that defines calendar components that are not group-scheduled
814 components, but are components only on a single user's calendar.
815
816Description:
817 This property MUST only be specified within calendar
818 components to specify participants, non-participants, and the
819 chair of a group-scheduled calendar entity. The property is
820 specified within an "EMAIL" category of the "VALARM" calendar
821 component to specify an email address that is to receive the email
822 type of iCalendar alarm.
823
824Examples:
825 Assign one or more attendee email addresses directly. Strings are
826 converted to :class:`~icalendar.prop.cal_address.vCalAddress` objects
827 and receive a ``mailto:`` prefix when needed.
828
829 .. code-block:: pycon
830
831 >>> from icalendar import Event
832 >>> event = Event()
833 >>> event.attendees = [
834 ... "me@my-domain.com",
835 ... "mailto:you@my-domain.com",
836 ... ]
837 >>> event.attendees[0]
838 vCalAddress('mailto:me@my-domain.com')
839 >>> event.attendees[1]
840 vCalAddress('mailto:you@my-domain.com')
841 >>> print(event.to_ical())
842 BEGIN:VEVENT
843 ATTENDEE:mailto:me@my-domain.com
844 ATTENDEE:mailto:you@my-domain.com
845 END:VEVENT
846
847 Use :meth:`vCalAddress.new
848 <icalendar.prop.cal_address.vCalAddress.new>` when an attendee needs
849 parameters such as ``CN``, ``ROLE``, or ``RSVP``.
850
851 .. code-block:: pycon
852
853 >>> from icalendar import vCalAddress
854 >>> event.attendees = [
855 ... vCalAddress.new(
856 ... "chair@example.com",
857 ... cn="Meeting Chair",
858 ... role="CHAIR",
859 ... rsvp=True,
860 ... )
861 ... ]
862""",
863)
864
865uid_property = single_string_property(
866 "UID",
867 """UID specifies the persistent, globally unique identifier for a component.
868
869We recommend using :func:`uuid.uuid4` to generate new values.
870
871Returns:
872 The value of the UID property as a string or ``""`` if no value is set.
873
874Description:
875 The "UID" itself MUST be a globally unique identifier.
876 The generator of the identifier MUST guarantee that the identifier
877 is unique.
878
879 This is the method for correlating scheduling messages with the
880 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
881 The full range of calendar components specified by a recurrence
882 set is referenced by referring to just the "UID" property value
883 corresponding to the calendar component. The "RECURRENCE-ID"
884 property allows the reference to an individual instance within the
885 recurrence set.
886
887 This property is an important method for group-scheduling
888 applications to match requests with later replies, modifications,
889 or deletion requests. Calendaring and scheduling applications
890 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
891 calendar components to assure interoperability with other group-
892 scheduling applications. This identifier is created by the
893 calendar system that generates an iCalendar object.
894
895 Implementations MUST be able to receive and persist values of at
896 least 255 octets for this property, but they MUST NOT truncate
897 values in the middle of a UTF-8 multi-octet sequence.
898
899 :rfc:`7986` states that UID can be used, for
900 example, to identify duplicate calendar streams that a client may
901 have been given access to. It can be used in conjunction with the
902 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
903 to identify the most recent version of a calendar.
904
905Conformance:
906 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
907 and "VJOURNAL" calendar components.
908 :rfc:`7986` modifies the definition of the "UID" property to
909 allow it to be defined in an iCalendar object.
910 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
911 identifier to be specified. The value of this property can then be used
912 to refer uniquely to the "VALARM" component.
913
914 This property can be specified once only.
915
916Security:
917 :rfc:`7986` states that UID values MUST NOT include any data that
918 might identify a user, host, domain, or any other security- or
919 privacy-sensitive information. It is RECOMMENDED that calendar user
920 agents now generate "UID" values that are hex-encoded random
921 Universally Unique Identifier (UUID) values as defined in
922 Sections 4.4 and 4.5 of :rfc:`4122`.
923 You can use the :mod:`uuid` module to generate new UUIDs.
924
925Compatibility:
926 For Alarms, ``X-ALARMUID`` is also considered.
927
928Examples:
929 The following is an example of such a property value:
930 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
931
932 Set the UID of a calendar:
933
934 .. code-block:: pycon
935
936 >>> from icalendar import Calendar
937 >>> from uuid import uuid4
938 >>> calendar = Calendar()
939 >>> calendar.uid = uuid4()
940 >>> print(calendar.to_ical())
941 BEGIN:VCALENDAR
942 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
943 END:VCALENDAR
944
945""",
946)
947
948summary_property = multi_language_text_property(
949 "SUMMARY",
950 None,
951 """SUMMARY defines a short summary or subject for the calendar component.
952
953Property Parameters:
954 IANA, non-standard, alternate text
955 representation, and language property parameters can be specified
956 on this property.
957
958Conformance:
959 The property can be specified in "VEVENT", "VTODO",
960 "VJOURNAL", or "VALARM" calendar components.
961
962Description:
963 This property is used in the "VEVENT", "VTODO", and
964 "VJOURNAL" calendar components to capture a short, one-line
965 summary about the activity or journal entry.
966
967 This property is used in the "VALARM" calendar component to
968 capture the subject of an EMAIL category of alarm.
969
970Examples:
971 The following is an example of this property:
972
973 .. code-block:: pycon
974
975 SUMMARY:Department Party
976""",
977)
978
979description_property = multi_language_text_property(
980 "DESCRIPTION",
981 None,
982 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
983
984Property Parameters:
985 IANA, non-standard, alternate text
986 representation, and language property parameters can be specified
987 on this property.
988
989Conformance:
990 The property can be specified in the "VEVENT", "VTODO",
991 "VJOURNAL", or "VALARM" calendar components. The property can be
992 specified multiple times only within a "VJOURNAL" calendar
993 component.
994
995Description:
996 This property is used in the "VEVENT" and "VTODO" to
997 capture lengthy textual descriptions associated with the activity.
998
999 This property is used in the "VALARM" calendar component to
1000 capture the display text for a DISPLAY category of alarm, and to
1001 capture the body text for an EMAIL category of alarm.
1002
1003Examples:
1004 The following is an example of this property with formatted
1005 line breaks in the property value:
1006
1007 .. code-block:: pycon
1008
1009 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1010 design.\\nHappy Face Conference Room. Phoenix design team
1011 MUST attend this meeting.\\nRSVP to team leader.
1012
1013 """, # noqa: E501
1014)
1015
1016
1017def create_single_property(
1018 prop: str,
1019 value_attr: str | None,
1020 value_type: tuple[type],
1021 type_def: type,
1022 doc: str,
1023 vProp: type = vDDDTypes, # noqa: N803
1024 convert: Callable[[object], object] | None = None,
1025):
1026 """Create a single property getter and setter.
1027
1028 Parameters:
1029 prop: The name of the property.
1030 value_attr: The name of the attribute to get the value from.
1031 value_type: The type of the value.
1032 type_def: The type of the property.
1033 doc: The docstring of the property.
1034 vProp: The type of the property from :mod:`icalendar.prop`.
1035 """
1036
1037 def p_get(self: Component):
1038 default = object()
1039 result = self.get(prop, default)
1040 if result is default:
1041 return None
1042 if isinstance(result, list):
1043 raise InvalidCalendar(f"Multiple {prop} defined.")
1044 value = result if value_attr is None else getattr(result, value_attr, result)
1045 value = value if convert is None else convert(value)
1046 if not isinstance(value, value_type):
1047 raise InvalidCalendar(
1048 f"{prop} must be either a "
1049 f"{' or '.join(t.__name__ for t in value_type)},"
1050 f" not {value}."
1051 )
1052 return value
1053
1054 def p_set(self: Component, value) -> None:
1055 if value is None:
1056 p_del(self)
1057 return
1058 value = convert(value) if convert is not None else value
1059 if not isinstance(value, value_type):
1060 raise TypeError(
1061 f"Use {' or '.join(t.__name__ for t in value_type)}, "
1062 f"not {type(value).__name__}."
1063 )
1064 self[prop] = vProp(value)
1065 if prop in self.exclusive:
1066 for other_prop in self.exclusive:
1067 if other_prop != prop:
1068 self.pop(other_prop, None)
1069
1070 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
1071
1072 def p_del(self: Component):
1073 self.pop(prop)
1074
1075 p_doc = f"""The {prop} property.
1076
1077 {doc}
1078
1079 To delete the value, either use ``del`` or set it to ``None``.
1080
1081 Raises:
1082 InvalidCalendar: if the attribute has invalid values.
1083 """
1084 return property(p_get, p_set, p_del, p_doc)
1085
1086
1087X_MOZ_SNOOZE_TIME_property = single_utc_property(
1088 "X-MOZ-SNOOZE-TIME",
1089 """The ``X-MOZ-SNOOZE-TIME`` property as a :class:`~datetime.datetime` in UTC.
1090
1091 Thunderbird: Alarms before this time are snoozed.
1092""",
1093)
1094X_MOZ_LASTACK_property = single_utc_property(
1095 "X-MOZ-LASTACK",
1096 """The ``X-MOZ-LASTACK`` property as a :class:`~datetime.datetime` in UTC.
1097
1098 Thunderbird: Alarms before this time are acknowledged.
1099""",
1100)
1101
1102
1103def property_get_duration(self: Component) -> timedelta | None:
1104 """Getter for property DURATION."""
1105 default = object()
1106 duration = self.get("duration", default)
1107 if duration is default:
1108 return None
1109 result = getattr(duration, "td", None)
1110 if result is None:
1111 raise InvalidCalendar(
1112 f"DURATION must be a timedelta, not {type(duration).__name__}."
1113 )
1114 return result
1115
1116
1117def property_set_duration(self: Component, value: timedelta | None):
1118 """Setter for property DURATION."""
1119 if value is None:
1120 self.pop("duration", None)
1121 return
1122 if not isinstance(value, timedelta):
1123 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
1124 self["duration"] = vDuration(value)
1125 self.pop("DTEND")
1126 self.pop("DUE")
1127
1128
1129def property_del_duration(self: Component):
1130 """Delete property DURATION."""
1131 self.pop("DURATION")
1132
1133
1134property_doc_duration_template = """The DURATION property.
1135
1136The "DTSTART" property for a "{component}" specifies the inclusive
1137start of the {component}.
1138The "DURATION" property in conjunction with the DTSTART property
1139for a "{component}" calendar component specifies the non-inclusive end
1140of the event.
1141
1142If you would like to calculate the duration of a {component}, do not use this.
1143Instead use the duration property (lower case).
1144"""
1145
1146
1147def duration_property(component: str) -> property:
1148 """Return the duration property."""
1149 return property(
1150 property_get_duration,
1151 property_set_duration,
1152 property_del_duration,
1153 property_doc_duration_template.format(component=component),
1154 )
1155
1156
1157def multi_text_property(name: str, docs: str) -> property:
1158 """Get a property that can occur several times and is text.
1159
1160 Examples: Journal.descriptions, Event.comments
1161 """
1162
1163 def fget(self: Component) -> list[str]:
1164 """Get the values."""
1165 descriptions = self.get(name)
1166 if descriptions is None:
1167 return []
1168 if not isinstance(descriptions, SEQUENCE_TYPES):
1169 return [descriptions]
1170 return descriptions
1171
1172 def fset(self: Component, values: str | Sequence[str] | None):
1173 """Set the values."""
1174 fdel(self)
1175 if values is None:
1176 return
1177 if isinstance(values, str):
1178 self.add(name, values)
1179 else:
1180 for description in values:
1181 self.add(name, description)
1182
1183 def fdel(self: Component):
1184 """Delete the values."""
1185 self.pop(name)
1186
1187 return property(fget, fset, fdel, docs)
1188
1189
1190descriptions_property = multi_text_property(
1191 "DESCRIPTION",
1192 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1193
1194Property Parameters:
1195 IANA, non-standard, alternate text
1196 representation, and language property parameters can be specified
1197 on this property.
1198
1199Conformance:
1200 The property can be
1201 specified multiple times only within a "VJOURNAL" calendar component.
1202
1203Description:
1204 This property is used in the "VJOURNAL" calendar component to
1205 capture one or more textual journal entries.
1206
1207Examples:
1208 The following is an example of this property with formatted
1209 line breaks in the property value:
1210
1211 .. code-block:: pycon
1212
1213 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1214 design.\\nHappy Face Conference Room. Phoenix design team
1215 MUST attend this meeting.\\nRSVP to team leader.
1216
1217""", # noqa: E501
1218)
1219
1220comments_property = multi_text_property(
1221 "COMMENT",
1222 """Specifies a comment to the calendar user.
1223
1224This property holds free-text notes attached to a component; calendar clients
1225display it but never act on it. It is defined in :rfc:`5545#section-3.8.1.4`
1226and may appear multiple times on ``VEVENT``, ``VTODO``, ``VJOURNAL``, and
1227``VFREEBUSY`` components as well as on their ``STANDARD`` and ``DAYLIGHT``
1228sub-components. For availability components, it is defined in :rfc:`7953`, and
1229may appear multiple times on ``VAVAILABILITY`` and ``VAVAILABLE``.
1230
1231You can get, set, append, and delete comments on a component. The value is a
1232list of strings; each string is one comment.
1233
1234Example:
1235 Add two comments to an event and then read them back.
1236
1237 .. code-block:: pycon
1238
1239 >>> from icalendar import Event
1240 >>> event = Event()
1241 >>> event.add("COMMENT", "Moved from the planning board.")
1242 >>> event.add("COMMENT", "Confirmed by phone.")
1243 >>> [str(c) for c in event.comments]
1244 ['Moved from the planning board.', 'Confirmed by phone.']
1245 >>> str(event.comments[0])
1246 'Moved from the planning board.'
1247""",
1248)
1249
1250RECURRENCE_ID = create_single_property(
1251 "RECURRENCE-ID",
1252 "dt",
1253 (date, datetime),
1254 date | datetime,
1255 """
1256Identify a specific occurrence of a recurring calendar object.
1257
1258This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1259particular instance in a recurrence set. The value is the original start
1260date or datetime of that instance, not the rescheduled time.
1261
1262The value is usually a DATE-TIME and must use the same value type as the
1263``DTSTART`` property in the same component. A DATE value may be used for
1264all-day items instead.
1265
1266This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1267may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1268""",
1269 vDDDTypes,
1270)
1271
1272
1273def _get_organizer(self: Component) -> vCalAddress | None:
1274 """ORGANIZER defines the organizer for a calendar component.
1275
1276 Property Parameters:
1277 IANA, non-standard, language, common name,
1278 directory entry reference, and sent-by property parameters can be
1279 specified on this property.
1280
1281 Conformance:
1282 This property MUST be specified in an iCalendar object
1283 that specifies a group-scheduled calendar entity. This property
1284 MUST be specified in an iCalendar object that specifies the
1285 publication of a calendar user's busy time. This property MUST
1286 NOT be specified in an iCalendar object that specifies only a time
1287 zone definition or that defines calendar components that are not
1288 group-scheduled components, but are components only on a single
1289 user's calendar.
1290
1291 Description:
1292 This property is specified within the "VEVENT",
1293 "VTODO", and "VJOURNAL" calendar components to specify the
1294 organizer of a group-scheduled calendar entity. The property is
1295 specified within the "VFREEBUSY" calendar component to specify the
1296 calendar user requesting the free or busy time. When publishing a
1297 "VFREEBUSY" calendar component, the property is used to specify
1298 the calendar that the published busy time came from.
1299
1300 The property has the property parameters "CN", for specifying the
1301 common or display name associated with the "Organizer", "DIR", for
1302 specifying a pointer to the directory information associated with
1303 the "Organizer", "SENT-BY", for specifying another calendar user
1304 that is acting on behalf of the "Organizer". The non-standard
1305 parameters may also be specified on this property. If the
1306 "LANGUAGE" property parameter is specified, the identified
1307 language applies to the "CN" parameter value.
1308 """
1309 return self.get("ORGANIZER")
1310
1311
1312def _set_organizer(self: Component, value: vCalAddress | str | None):
1313 """Set the value."""
1314 _del_organizer(self)
1315 if value is not None:
1316 self.add("ORGANIZER", value)
1317
1318
1319def _del_organizer(self: Component):
1320 """Delete the value."""
1321 self.pop("ORGANIZER")
1322
1323
1324organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1325
1326
1327def single_string_enum_property(
1328 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1329) -> property:
1330 """Create a property to access a single string value and convert it to an enum."""
1331 prop = single_string_property(name, docs, default=default)
1332
1333 def fget(self: Component) -> StrEnum:
1334 """Get the value."""
1335 value = prop.fget(self)
1336 if value == default:
1337 return default
1338 return enum(str(value))
1339
1340 def fset(self: Component, value: str | StrEnum | None) -> None:
1341 """Set the value."""
1342 if value == "":
1343 value = None
1344 prop.fset(self, value)
1345
1346 return property(fget, fset, prop.fdel, doc=docs)
1347
1348
1349busy_type_property = single_string_enum_property(
1350 "BUSYTYPE",
1351 BUSYTYPE,
1352 BUSYTYPE.BUSY_UNAVAILABLE,
1353 """BUSYTYPE specifies the default busy time type.
1354
1355Returns:
1356 :class:`icalendar.enums.BUSYTYPE`
1357
1358Description:
1359 This property is used to specify the default busy time
1360 type. The values correspond to those used by the "FBTYPE"
1361 parameter used on a "FREEBUSY" property, with the exception that
1362 the "FREE" value is not used in this property. If not specified
1363 on a component that allows this property, the default is "BUSY-
1364 UNAVAILABLE".
1365""",
1366)
1367
1368
1369def _make_repeat_property() -> property:
1370 from icalendar.config import _clamp_repeat
1371
1372 _base = single_int_property(
1373 "REPEAT",
1374 0,
1375 """The number of additional times the alarm is triggered after the
1376initial trigger.
1377
1378Defaults to ``0``, meaning the alarm fires once. Must be paired with
1379:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`.
1380The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read.
1381
1382Raises:
1383 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1384 even though ``bool`` subclasses ``int``.
1385
1386 ~icalendar.error.InvalidCalendar: If the value is negative.
1387
1388.. versionchanged:: 7.3.0
1389 Negative values are no longer accepted.
1390""",
1391 min_value=0,
1392 )
1393
1394 def fget(self):
1395 return _clamp_repeat(_base.fget(self))
1396
1397 return property(fget, _base.fset, _base.fdel, _base.__doc__)
1398
1399
1400repeat_property = _make_repeat_property()
1401
1402priority_property = single_int_property(
1403 "PRIORITY",
1404 0,
1405 """
1406
1407Conformance:
1408 This property can be specified in "VEVENT" and "VTODO" calendar components
1409 according to :rfc:`5545`.
1410 :rfc:`7953` adds this property to "VAVAILABILITY".
1411
1412Description:
1413 This priority is specified as an integer in the range 0
1414 to 9. A value of 0 specifies an undefined priority. A value of 1
1415 is the highest priority. A value of 2 is the second highest
1416 priority. Subsequent numbers specify a decreasing ordinal
1417 priority. A value of 9 is the lowest priority.
1418
1419 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1420 "LOW" is mapped into this property such that a property value in
1421 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1422 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1423 is "LOW" priority.
1424
1425 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1426 "C3" is mapped into this property such that a property value of 1
1427 specifies "A1", a property value of 2 specifies "A2", a property
1428 value of 3 specifies "A3", and so forth up to a property value of
1429 9 specifies "C3".
1430
1431 Other integer values are reserved for future use.
1432
1433 Within a "VEVENT" calendar component, this property specifies a
1434 priority for the event. This property may be useful when more
1435 than one event is scheduled for a given time period.
1436
1437 Within a "VTODO" calendar component, this property specified a
1438 priority for the to-do. This property is useful in prioritizing
1439 multiple action items for a given time period.
1440
1441 Raises:
1442 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1443 even though ``bool`` subclasses ``int``.
1444
1445 ~icalendar.error.InvalidCalendar: If the value is negative.
1446
1447 .. versionchanged:: 7.3.0
1448 Negative values are no longer accepted.
1449""",
1450 min_value=0,
1451)
1452
1453class_property = single_string_enum_property(
1454 "CLASS",
1455 CLASS,
1456 CLASS.PUBLIC,
1457 """CLASS specifies the class of the calendar component.
1458
1459Returns:
1460 :class:`icalendar.enums.CLASS`
1461
1462Description:
1463 An access classification is only one component of the
1464 general security system within a calendar application. It
1465 provides a method of capturing the scope of the access the
1466 calendar owner intends for information within an individual
1467 calendar entry. The access classification of an individual
1468 iCalendar component is useful when measured along with the other
1469 security components of a calendar system (e.g., calendar user
1470 authentication, authorization, access rights, access role, etc.).
1471 Hence, the semantics of the individual access classifications
1472 cannot be completely defined by this memo alone. Additionally,
1473 due to the "blind" nature of most exchange processes using this
1474 memo, these access classifications cannot serve as an enforcement
1475 statement for a system receiving an iCalendar object. Rather,
1476 they provide a method for capturing the intention of the calendar
1477 owner for the access to the calendar component. If not specified
1478 in a component that allows this property, the default value is
1479 PUBLIC. Applications MUST treat x-name and iana-token values they
1480 don't recognize the same way as they would the PRIVATE value.
1481""",
1482)
1483
1484transparency_property = single_string_enum_property(
1485 "TRANSP",
1486 TRANSP,
1487 TRANSP.OPAQUE,
1488 """TRANSP defines whether or not an event is transparent to busy time searches.
1489
1490Returns:
1491 :class:`icalendar.enums.TRANSP`
1492
1493Description:
1494 Time Transparency is the characteristic of an event
1495 that determines whether it appears to consume time on a calendar.
1496 Events that consume actual time for the individual or resource
1497 associated with the calendar SHOULD be recorded as OPAQUE,
1498 allowing them to be detected by free/busy time searches. Other
1499 events, which do not take up the individual's (or resource's) time
1500 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1501 busy time searches.
1502""",
1503)
1504status_property = single_string_enum_property(
1505 "STATUS",
1506 STATUS,
1507 "",
1508 """STATUS defines the overall status or confirmation for the calendar component.
1509
1510Returns:
1511 :class:`icalendar.enums.STATUS`
1512
1513The default value is ``""``.
1514
1515Description:
1516 In a group-scheduled calendar component, the property
1517 is used by the "Organizer" to provide a confirmation of the event
1518 to the "Attendees". For example in a "VEVENT" calendar component,
1519 the "Organizer" can indicate that a meeting is tentative,
1520 confirmed, or cancelled. In a "VTODO" calendar component, the
1521 "Organizer" can indicate that an action item needs action, is
1522 completed, is in process or being worked on, or has been
1523 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1524 can indicate that a journal entry is draft, final, or has been
1525 cancelled or removed.
1526""",
1527)
1528
1529url_property = single_string_property(
1530 "URL",
1531 """A Uniform Resource Locator (URL) associated with a calendar component.
1532
1533This property specifies a URI where a more dynamic rendition of the calendar
1534information can be found. It is commonly used to reference related resources
1535or provide additional information about the component.
1536
1537According to :rfc:`5545#section-3.8.4.6`, this property can be specified
1538once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1539Since :rfc:`7986#section-5.5`, this property can also be defined on a
1540"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components.
1541
1542This property may be used in a calendar component to convey a location
1543where a more dynamic rendition of the calendar information can be found.
1544If both the URL property and Content-Location MIME header are specified,
1545they MUST point to the same resource.
1546
1547This differs from the SOURCE property, which identifies where calendar
1548data can be refreshed from, whereas URL provides an alternative
1549representation of the current calendar data.
1550
1551Examples:
1552
1553 Set a URL for an event that references additional information:
1554
1555 .. code-block:: pycon
1556
1557 >>> from icalendar import Event
1558 >>> event = Event()
1559 >>> event.add('url', 'http://example.com/events/meeting-2025')
1560 >>> print(event.to_ical().decode('utf-8'))
1561 BEGIN:VEVENT
1562 URL:http://example.com/events/meeting-2025
1563 END:VEVENT
1564
1565 Set a URL for a calendar:
1566
1567 .. code-block:: pycon
1568
1569 >>> from icalendar import Calendar
1570 >>> calendar = Calendar()
1571 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics')
1572 >>> print(calendar.to_ical().decode('utf-8'))
1573 BEGIN:VCALENDAR
1574 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1575 END:VCALENDAR
1576
1577See also:
1578 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where
1579 calendar data can be refreshed.
1580
1581 icalendar implementations:
1582
1583 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>`
1584 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>`
1585 - :attr:`Event.url <icalendar.cal.event.Event.url>`
1586 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>`
1587 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>`
1588 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>`
1589
1590
1591""",
1592)
1593
1594source_property = single_string_property(
1595 "SOURCE",
1596 """A URI from where calendar data can be refreshed.
1597
1598Description:
1599 This property identifies a location where a client can
1600 retrieve updated data for the calendar. Clients SHOULD honor any
1601 specified "REFRESH-INTERVAL" value when periodically retrieving
1602 data. Note that this property differs from the "URL" property in
1603 that "URL" is meant to provide an alternative representation of
1604 the calendar data rather than the original location of the data.
1605
1606Conformance:
1607 This property can be specified once in an iCalendar object.
1608
1609Example:
1610 The following is an example of this property:
1611
1612 .. code-block:: ics
1613
1614 SOURCE;VALUE=URI:https://example.com/holidays.ics
1615
1616""",
1617)
1618
1619location_property = multi_language_text_property(
1620 "LOCATION",
1621 None,
1622 """The intended venue for the activity defined by a calendar component.
1623
1624Property Parameters:
1625 IANA, non-standard, alternate text
1626 representation, and language property parameters can be specified
1627 on this property.
1628
1629Conformance:
1630 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1631 calendar component.
1632 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1633
1634Description:
1635 Specific venues such as conference or meeting rooms may
1636 be explicitly specified using this property. An alternate
1637 representation may be specified that is a URI that points to
1638 directory information with more structured specification of the
1639 location. For example, the alternate representation may specify
1640 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1641 CID URL :rfc:`2392` pointing to a MIME body part containing a
1642 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1643
1644""",
1645)
1646
1647contacts_property = multi_text_property(
1648 "CONTACT",
1649 """Contact information associated with a calendar component.
1650
1651The contact property holds free-text information for reaching the person or
1652organization responsible in a component, such as a name, phone number, or a
1653reference to more detailed contact data. Calendar clients surface it so
1654attendees know who to contact about the event or task.
1655
1656This property is defined in :rfc:`5545#section-3.8.4.2` and may appear on a
1657``VEVENT``, ``VTODO``, ``VJOURNAL``, or ``VFREEBUSY`` component. For
1658availability components it is defined in :rfc:`7953` and may appear on a
1659``VAVAILABILITY`` or ``VAVAILABLE`` component. An alternate representation may
1660point to a URI, for example, a vCard per :rfc:`2426`, via the ``ALTREP`` parameter.
1661
1662You can get, set, append, and delete contacts on a component. The value is a
1663list of strings; each string is one contact entry.
1664
1665Example:
1666 Add a contact to an event and then read it back.
1667
1668 .. code-block:: pycon
1669
1670 >>> from icalendar import Event
1671 >>> event = Event()
1672 >>> event.add("CONTACT", "Jim Dolittle, ABC Industries, +1-919-555-1234")
1673 >>> [str(c) for c in event.contacts]
1674 ['Jim Dolittle, ABC Industries, +1-919-555-1234']
1675 >>> str(event.contacts[0])
1676 'Jim Dolittle, ABC Industries, +1-919-555-1234'
1677""",
1678)
1679
1680
1681def _timezone_datetime_property(name: str, docs: str):
1682 """Create a property to access the values with a proper timezone."""
1683
1684 return single_utc_property(name, docs)
1685
1686
1687rfc_7953_dtstart_property = _timezone_datetime_property(
1688 "DTSTART",
1689 """Start of the component as a :class:`~datetime.datetime` in UTC.
1690
1691 This is almost the same as
1692 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception:
1693 The values MUST have a timezone and DATE is not allowed.
1694
1695 Description:
1696 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1697 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1698 "DATE-TIME" values specified as either the date with UTC time or
1699 the date with local time and a time zone reference.
1700
1701 """,
1702)
1703
1704rfc_7953_dtend_property = _timezone_datetime_property(
1705 "DTEND",
1706 """End of the component as a :class:`~datetime.datetime` in UTC.
1707
1708 This is almost the same as
1709 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception:
1710 The values MUST have a timezone and DATE is not allowed.
1711
1712 Description:
1713 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1714 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1715 "DATE-TIME" values specified as either the date with UTC time or
1716 the date with local time and a time zone reference.
1717 """,
1718)
1719
1720
1721@property
1722def rfc_7953_duration_property(self) -> timedelta | None:
1723 """Compute the duration of this component.
1724
1725 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1726 Otherwise, the duration is calculated from :attr:`DTSTART` and
1727 :attr:`DTEND`/:attr:`DURATION`.
1728
1729 This is in accordance with :rfc:`7953`:
1730 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1731 """
1732 duration = self.DURATION
1733 if duration:
1734 return duration
1735 end = self.DTEND
1736 if end is None:
1737 return None
1738 start = self.DTSTART
1739 if start is None:
1740 raise IncompleteComponent("Cannot compute duration without start.")
1741 return end - start
1742
1743
1744@property
1745def rfc_7953_end_property(self) -> timedelta | None:
1746 """Compute the duration of this component.
1747
1748 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1749 Otherwise, the duration is calculated from :attr:`DTSTART` and
1750 :attr:`DTEND`/:attr:`DURATION`.
1751
1752 This is in accordance with :rfc:`7953`:
1753 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1754 """
1755 duration = self.DURATION
1756 if duration:
1757 start = self.DTSTART
1758 if start is None:
1759 raise IncompleteComponent("Cannot compute end without start.")
1760 return start + duration
1761 end = self.DTEND
1762 if end is None:
1763 return None
1764 return end
1765
1766
1767@rfc_7953_end_property.setter
1768def rfc_7953_end_property(self, value: datetime):
1769 self.DTEND = value
1770
1771
1772@rfc_7953_end_property.deleter
1773def rfc_7953_end_property(self):
1774 del self.DTEND
1775
1776
1777def get_start_end_duration_with_validation(
1778 component: Event | Todo,
1779 start_property: Literal["DTSTART"],
1780 end_property: Literal["DTEND", "DUE"],
1781 component_name: Literal["VEVENT", "VTODO"],
1782) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1783 """
1784 Validate the component and return start, end, and duration.
1785
1786 This tests validity according to :rfc:`5545` rules
1787 for ``Event`` and ``Todo`` components.
1788
1789 Parameters:
1790 component: The component to validate, either ``Event`` or ``Todo``.
1791 start_property: The start property name, ``DTSTART``.
1792 end_property: The end property name, either ``DTEND`` for ``Event`` or
1793 ``DUE`` for ``Todo``.
1794 component_name: The component name for error messages,
1795 either ``VEVENT`` or ``VTODO``.
1796
1797 Returns:
1798 tuple: (start, end, duration) values from the component.
1799
1800 Raises:
1801 ~icalendar.error.InvalidCalendar: If the component violates
1802 RFC 5545 constraints.
1803
1804 """
1805 start = getattr(component, start_property, None)
1806 end = getattr(component, end_property, None)
1807 duration = component.DURATION
1808
1809 # RFC 5545: Only one of end property and DURATION may be present
1810 if duration is not None and end is not None:
1811 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1812 msg = (
1813 f"Only one of {end_name} and DURATION "
1814 f"may be in a {component_name}, not both."
1815 )
1816 raise InvalidCalendar(msg)
1817
1818 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1819 if (
1820 start is not None
1821 and is_date(start)
1822 and duration is not None
1823 and duration.seconds != 0
1824 ):
1825 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1826 raise InvalidCalendar(msg)
1827
1828 # RFC 5545: DTSTART and end property must be of the same type
1829 if start is not None and end is not None and is_date(start) != is_date(end):
1830 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1831 msg = (
1832 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1833 )
1834 raise InvalidCalendar(msg)
1835
1836 return start, end, duration
1837
1838
1839def get_start_property(component: Event | Todo) -> date | datetime:
1840 """
1841 Get the start property with validation.
1842
1843 Parameters:
1844 component: The component from which to get its start property.
1845
1846 Returns:
1847 The ``DTSTART`` value.
1848
1849 Raises:
1850 ~icalendar.error.IncompleteComponent: If no ``DTSTART`` is present.
1851
1852 """
1853 # Trigger validation by calling _get_start_end_duration
1854 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1855 if start is None:
1856 msg = "No DTSTART given."
1857 raise IncompleteComponent(msg)
1858 return start
1859
1860
1861def get_end_property(
1862 component: Event | Todo, end_property: Literal["DTEND", "DUE"]
1863) -> date | datetime:
1864 """
1865 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1866
1867 Parameters:
1868 component: The component to get end from
1869 end_property: The end property name, either ``DTEND`` for ``Event`` or
1870 ``DUE`` for ``Todo``.
1871
1872 Returns:
1873 The computed end value.
1874
1875 Raises:
1876 ~icalendar.error.IncompleteComponent: If the provided information is incomplete
1877 to compute the end property.
1878
1879 """
1880 # Trigger validation by calling _get_start_end_duration
1881 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1882
1883 if end is None and duration is None:
1884 if start is None:
1885 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1886 msg = f"No {end_name} or DURATION+DTSTART given."
1887 raise IncompleteComponent(msg)
1888
1889 # Default behavior differs for Event vs Todo:
1890 # Event: date gets +1 day, datetime gets same time
1891 # Todo: both date and datetime get same time (issue #898)
1892 if end_property == "DTEND" and is_date(start):
1893 return start + timedelta(days=1)
1894 return start
1895
1896 if duration is not None:
1897 if start is not None:
1898 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1899 return start
1900 return start + duration
1901 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1902 msg = f"No {end_name} or DURATION+DTSTART given."
1903 raise IncompleteComponent(msg)
1904
1905 return end
1906
1907
1908def get_duration_property(component: Event | Todo) -> timedelta:
1909 """
1910 Get the duration property with fallback calculation from start and end.
1911
1912 Parameters:
1913 component: The component from which to get its duration property.
1914
1915 Returns:
1916 The duration as a timedelta.
1917
1918 """
1919 # First check if DURATION property is explicitly set
1920 if "DURATION" in component:
1921 return component["DURATION"].dt
1922
1923 # Fall back to calculated duration from start and end
1924 return component.end - component.start
1925
1926
1927def set_duration_with_locking(
1928 component: Event | Todo,
1929 duration: timedelta | None,
1930 locked: Literal["start", "end"],
1931 end_property: Literal["DTEND", "DUE"],
1932) -> None:
1933 """
1934 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1935
1936 Parameters:
1937 component: The component to modify, either ``Event`` or ``Todo``.
1938 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1939 locked: Which property to keep unchanged, either ``start`` or ``end``.
1940 end_property: The end property name, either ``DTEND`` for ``Event`` or
1941 ``DUE`` for ``Todo``.
1942
1943 """
1944 # Convert to DURATION property if duration is None
1945 if duration is None:
1946 if "DURATION" in component:
1947 return # Already has DURATION property
1948 current_duration = component.duration
1949 component.DURATION = current_duration
1950 return
1951
1952 if not isinstance(duration, timedelta):
1953 msg = f"Use timedelta, not {type(duration).__name__}."
1954 raise TypeError(msg)
1955
1956 # Validate date/duration compatibility
1957 start = component.DTSTART
1958 if start is not None and is_date(start) and duration.seconds != 0:
1959 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1960 raise InvalidCalendar(msg)
1961
1962 if locked == "start":
1963 # Keep start locked, adjust end
1964 if start is None:
1965 msg = "Cannot set duration without DTSTART. Set start time first."
1966 raise IncompleteComponent(msg)
1967 component.pop(end_property, None) # Remove end property
1968 component.DURATION = duration
1969 elif locked == "end":
1970 # Keep end locked, adjust start
1971 current_end = component.end
1972 component.DTSTART = current_end - duration
1973 component.pop(end_property, None) # Remove end property
1974 component.DURATION = duration
1975 else:
1976 msg = f"locked must be 'start' or 'end', not {locked!r}"
1977 raise ValueError(msg)
1978
1979
1980def set_start_with_locking(
1981 component: Event | Todo,
1982 start: date | datetime,
1983 locked: Literal["duration", "end"] | None,
1984 end_property: Literal["DTEND", "DUE"],
1985) -> None:
1986 """
1987 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1988
1989 Parameters:
1990 component: The component to modify, either ``Event`` or ``Todo``.
1991 start: The start time to set.
1992 locked: Which property to keep unchanged, either ``duration``, ``end``,
1993 or ``None`` for auto-detect.
1994 end_property: The end property name, either ``DTEND`` for ``Event`` or
1995 ``DUE`` for ``Todo``.
1996
1997 """
1998 if locked is None:
1999 # Auto-detect based on existing properties
2000 if "DURATION" in component:
2001 locked = "duration"
2002 elif end_property in component:
2003 locked = "end"
2004 else:
2005 # Default to duration if no existing properties
2006 locked = "duration"
2007
2008 if locked == "duration":
2009 # Keep duration locked, adjust end
2010 current_duration = (
2011 component.duration
2012 if "DURATION" in component or end_property in component
2013 else None
2014 )
2015 component.DTSTART = start
2016 if current_duration is not None:
2017 component.pop(end_property, None) # Remove end property
2018 component.DURATION = current_duration
2019 elif locked == "end":
2020 # Keep end locked, adjust duration
2021 current_end = component.end
2022 component.DTSTART = start
2023 component.pop("DURATION", None) # Remove duration property
2024 setattr(component, end_property, current_end)
2025 else:
2026 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
2027 raise ValueError(msg)
2028
2029
2030def set_end_with_locking(
2031 component: Event | Todo,
2032 end: date | datetime,
2033 locked: Literal["start", "duration"],
2034 end_property: Literal["DTEND", "DUE"],
2035) -> None:
2036 """
2037 Set the end with explicit locking behavior for Event and Todo components.
2038
2039 Parameters:
2040 component: The component to modify, either ``Event`` or ``Todo``.
2041 end: The end time to set.
2042 locked: Which property to keep unchanged, either ``start`` or ``duration``.
2043 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
2044 for ``Todo``.
2045
2046 """
2047 if locked == "start":
2048 # Keep start locked, adjust duration
2049 component.pop("DURATION", None) # Remove duration property
2050 setattr(component, end_property, end)
2051 elif locked == "duration":
2052 # Keep duration locked, adjust start
2053 current_duration = component.duration
2054 component.DTSTART = end - current_duration
2055 component.pop(end_property, None) # Remove end property
2056 component.DURATION = current_duration
2057 else:
2058 msg = f"locked must be 'start' or 'duration', not {locked!r}"
2059 raise ValueError(msg)
2060
2061
2062def _get_images(self: Component) -> list[Image]:
2063 """IMAGE specifies an image associated with the calendar or a calendar component.
2064
2065 Description:
2066 This property specifies an image for an iCalendar
2067 object or a calendar component via a URI or directly with inline
2068 data that can be used by calendar user agents when presenting the
2069 calendar data to a user. Multiple properties MAY be used to
2070 specify alternative sets of images with, for example, varying
2071 media subtypes, resolutions, or sizes. When multiple properties
2072 are present, calendar user agents SHOULD display only one of them,
2073 picking one that provides the most appropriate image quality, or
2074 display none. The "DISPLAY" parameter is used to indicate the
2075 intended display mode for the image. The "ALTREP" parameter,
2076 defined in :rfc:`5545`, can be used to provide a "clickable" image
2077 where the URI in the parameter value can be "launched" by a click
2078 on the image in the calendar user agent.
2079
2080 Conformance:
2081 This property can be specified multiple times in an
2082 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
2083 components.
2084
2085 .. note::
2086
2087 At the present moment, this property is read-only. If you require a setter,
2088 please open an issue or a pull request.
2089 """
2090 images = self.get("IMAGE", [])
2091 if not isinstance(images, SEQUENCE_TYPES):
2092 images = [images]
2093 return [Image.from_property_value(img) for img in images]
2094
2095
2096images_property = property(_get_images)
2097
2098
2099def _get_conferences(self: Component) -> list[Conference]:
2100 """Return the CONFERENCE properties as a list.
2101
2102 Purpose:
2103 This property specifies information for accessing a conferencing system.
2104
2105 Conformance:
2106 This property can be specified multiple times in a
2107 "VEVENT" or "VTODO" calendar component.
2108
2109 Description:
2110 This property specifies information for accessing a
2111 conferencing system for attendees of a meeting or task. This
2112 might be for a telephone-based conference number dial-in with
2113 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
2114 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
2115 or https: URI :rfc:`7230`), or for an instant messaging group chat
2116 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
2117 conferencing system is not available, a data: URI :rfc:`2397`
2118 containing a text description can be used.
2119
2120 A conference system can be a bidirectional communication channel
2121 or a uni-directional "broadcast feed".
2122
2123 The "FEATURE" property parameter is used to describe the key
2124 capabilities of the conference system to allow a client to choose
2125 the ones that give the required level of interaction from a set of
2126 multiple properties.
2127
2128 The "LABEL" property parameter is used to convey additional
2129 details on the use of the URI. For example, the URIs or access
2130 codes for the moderator and attendee of a teleconference system
2131 could be different, and the "LABEL" property parameter could be
2132 used to "tag" each "CONFERENCE" property to indicate which is
2133 which.
2134
2135 The "LANGUAGE" property parameter can be used to specify the
2136 language used for text values used with this property (as per
2137 Section 3.2.10 of :rfc:`5545`).
2138
2139 Example:
2140 The following are examples of this property:
2141
2142 .. code-block:: ics
2143
2144 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
2145 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
2146 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2147 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
2148 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2149 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
2150 CONFERENCE;VALUE=URI;FEATURE=CHAT;
2151 LABEL=Chat room:xmpp:chat-123@conference.example.com
2152 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
2153 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
2154
2155 Get all conferences:
2156
2157 .. code-block:: pycon
2158
2159 >>> from icalendar import Event
2160 >>> event = Event()
2161 >>> event.conferences
2162 []
2163
2164 Set a conference:
2165
2166 .. code-block:: pycon
2167
2168 >>> from icalendar import Event, Conference
2169 >>> event = Event()
2170 >>> event.conferences = [
2171 ... Conference(
2172 ... "tel:+1-412-555-0123,,,654321",
2173 ... feature="PHONE,MODERATOR",
2174 ... label="Moderator dial-in",
2175 ... language="EN",
2176 ... )
2177 ... ]
2178 >>> print(event.to_ical())
2179 BEGIN:VEVENT
2180 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
2181 ALUE=URI:tel:+1-412-555-0123,,,654321
2182 END:VEVENT
2183
2184 """
2185 conferences = self.get("CONFERENCE", [])
2186 if not isinstance(conferences, SEQUENCE_TYPES):
2187 conferences = [conferences]
2188 return [Conference.from_uri(conference) for conference in conferences]
2189
2190
2191def _set_conferences(self: Component, conferences: list[Conference] | None):
2192 """Set the conferences."""
2193 _del_conferences(self)
2194 for conference in conferences or []:
2195 self.add("CONFERENCE", conference.to_uri())
2196
2197
2198def _del_conferences(self: Component):
2199 """Delete all conferences."""
2200 self.pop("CONFERENCE")
2201
2202
2203conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2204
2205
2206def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2207 """LINK properties as a list.
2208
2209 Purpose:
2210 LINK provides a reference to external information related to a component.
2211
2212 Property Parameters:
2213 The VALUE parameter is required.
2214 Non-standard, link relation type, format type, label, and language parameters
2215 can also be specified on this property.
2216 The LABEL parameter is defined in :rfc:`7986`.
2217
2218 Conformance:
2219 This property can be specified zero or more times in any iCalendar component.
2220 LINK is specified in :rfc:`9253`.
2221 The LINKREL parameter is required.
2222
2223 Description:
2224 When used in a component, the value of this property points to
2225 additional information related to the component.
2226 For example, it may reference the originating web server.
2227
2228 This property is a serialization of the model in :rfc:`8288`,
2229 where the link target is carried in the property value,
2230 the link context is the containing calendar entity,
2231 and the link relation type and any target attributes
2232 are carried in iCalendar property parameters.
2233
2234 The LINK property parameters map to :rfc:`8288` attributes as follows:
2235
2236 LABEL
2237 This parameter maps to the "title"
2238 attribute defined in Section 3.4.1 of :rfc:`8288`.
2239 LABEL is used to label the destination
2240 of a link such that it can be used as a human-readable identifier
2241 (e.g., a menu entry) in the language indicated by the LANGUAGE
2242 (if present).
2243 LANGUAGE
2244 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2245 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2246 LINKREL
2247 This parameter maps to the link relation type defined in Section 2.1 of
2248 :rfc:`8288`. See `Registered Link Relation Types
2249 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2250 FMTTYPE
2251 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2252 :rfc:`8288`.
2253
2254 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2255
2256 Examples:
2257 The following is an example of this property,
2258 which provides a reference to the source for the calendar object.
2259
2260 .. code-block:: ics
2261
2262 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2263 https://example.com/events
2264
2265 The following is an example of this property,
2266 which provides a reference to an entity from which this one was derived.
2267 The link relation is a vendor-defined value.
2268
2269 .. code-block:: ics
2270
2271 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2272 VALUE=URI:
2273 https://example.com/tasks/01234567-abcd1234.ics
2274
2275 The following is an example of this property,
2276 which provides a reference to a fragment of an XML document.
2277 The link relation is a vendor-defined value.
2278
2279 .. code-block:: ics
2280
2281 LINK;LINKREL="https://example.com/linkrel/costStructure";
2282 VALUE=XML-REFERENCE:
2283 https://example.com/xmlDocs/bidFramework.xml
2284 #xpointer(descendant::CostStruc/range-to(
2285 following::CostStrucEND[1]))
2286
2287 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2288
2289 .. code-block:: pycon
2290
2291 >>> from icalendar import Event, vUri
2292 >>> from datetime import datetime
2293 >>> link = vUri(
2294 ... "http://example.com/event-page",
2295 ... params={"LINKREL":"SOURCE"}
2296 ... )
2297 >>> event = Event.new(
2298 ... start=datetime(2025, 9, 17, 12, 0),
2299 ... summary="An Example Event with a page"
2300 ... )
2301 >>> event.links = [link]
2302 >>> print(event.to_ical())
2303 BEGIN:VEVENT
2304 SUMMARY:An Example Event with a page
2305 DTSTART:20250917T120000
2306 DTSTAMP:20250517T080612Z
2307 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2308 LINK;LINKREL="SOURCE":http://example.com/event-page
2309 END:VEVENT
2310
2311 """
2312 links = self.get("LINK", [])
2313 if not isinstance(links, list):
2314 links = [links]
2315 return links
2316
2317
2318LINKS_TYPE_SETTER: TypeAlias = (
2319 str | vUri | vUid | vXmlReference | list[str | vUri | vUid | vXmlReference] | None
2320)
2321
2322
2323def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2324 """Set the LINKs."""
2325 _del_links(self)
2326 if links is None:
2327 return
2328 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2329 links = [links]
2330 for link in links:
2331 if type(link) is str:
2332 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2333 self.add("LINK", link)
2334
2335
2336def _del_links(self: Component) -> None:
2337 """Delete all links."""
2338 self.pop("LINK")
2339
2340
2341links_property = property(_get_links, _set_links, _del_links)
2342
2343RELATED_TO_TYPE_SETTER: TypeAlias = (
2344 str | vText | vUri | vUid | list[str | vText | vUri | vUid] | None
2345)
2346
2347
2348def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2349 """RELATED-TO properties as a list.
2350
2351 Purpose:
2352 This property is used to represent a relationship or reference
2353 between one calendar component and another.
2354 :rfc:`9523` allows URI or UID values and a GAP parameter.
2355
2356 Value Type:
2357 :rfc:`5545`: TEXT
2358 :rfc:`9253`: URI, UID
2359
2360 Conformance:
2361 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2362 "VTODO", and "VJOURNAL" calendar components.
2363 Since :rfc:`9523`, this property MAY be specified in any
2364 iCalendar component.
2365
2366 Description (:rfc:`5545`):
2367 The property value consists of the persistent, globally
2368 unique identifier of another calendar component. This value would
2369 be represented in a calendar component by the "UID" property.
2370
2371 By default, the property value points to another calendar
2372 component that has a PARENT relationship to the referencing
2373 object. The "RELTYPE" property parameter is used to either
2374 explicitly state the default PARENT relationship type to the
2375 referenced calendar component or to override the default PARENT
2376 relationship type and specify either a CHILD or SIBLING
2377 relationship. The PARENT relationship indicates that the calendar
2378 component is a subordinate of the referenced calendar component.
2379 The CHILD relationship indicates that the calendar component is a
2380 superior of the referenced calendar component. The SIBLING
2381 relationship indicates that the calendar component is a peer of
2382 the referenced calendar component.
2383
2384 Changes to a calendar component referenced by this property can
2385 have an implicit impact on the related calendar component. For
2386 example, if a group event changes its start or end date or time,
2387 then the related, dependent events will need to have their start
2388 and end dates changed in a corresponding way. Similarly, if a
2389 PARENT calendar component is cancelled or deleted, then there is
2390 an implied impact to the related CHILD calendar components. This
2391 property is intended only to provide information on the
2392 relationship of calendar components. It is up to the target
2393 calendar system to maintain any property implications of this
2394 relationship.
2395
2396 Description (:rfc:`9253`):
2397 By default or when VALUE=UID is specified, the property value
2398 consists of the persistent, globally unique identifier of another
2399 calendar component. This value would be represented in a calendar
2400 component by the UID property.
2401
2402 By default, the property value
2403 points to another calendar component that has a PARENT relationship
2404 to the referencing object. The RELTYPE property parameter is used
2405 to either explicitly state the default PARENT relationship type to
2406 the referenced calendar component or to override the default
2407 PARENT relationship type and specify either a CHILD or SIBLING
2408 relationship or a temporal relationship.
2409
2410 The PARENT relationship
2411 indicates that the calendar component is a subordinate of the
2412 referenced calendar component. The CHILD relationship indicates
2413 that the calendar component is a superior of the referenced calendar
2414 component. The SIBLING relationship indicates that the calendar
2415 component is a peer of the referenced calendar component.
2416
2417 To preserve backwards compatibility, the value type MUST
2418 be UID when the PARENT, SIBLING, or CHILD relationships
2419 are specified.
2420
2421 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2422 or STARTTOSTART relationships define temporal relationships, as
2423 specified in the RELTYPE parameter definition.
2424
2425 The FIRST and NEXT
2426 define ordering relationships between calendar components.
2427
2428 The DEPENDS-ON relationship indicates that the current calendar
2429 component depends on the referenced calendar component in some manner.
2430 For example, a task may be blocked waiting on the other,
2431 referenced, task.
2432
2433 The REFID and CONCEPT relationships establish
2434 a reference from the current component to the referenced component.
2435 Changes to a calendar component referenced by this property
2436 can have an implicit impact on the related calendar component.
2437 For example, if a group event changes its start or end date or
2438 time, then the related, dependent events will need to have their
2439 start and end dates and times changed in a corresponding way.
2440 Similarly, if a PARENT calendar component is canceled or deleted,
2441 then there is an implied impact to the related CHILD calendar
2442 components. This property is intended only to provide information
2443 on the relationship of calendar components.
2444
2445 Deletion of the target component, for example, the target of a
2446 FIRST, NEXT, or temporal relationship, can result in broken links.
2447
2448 It is up to the target calendar system to maintain any property
2449 implications of these relationships.
2450
2451 Examples:
2452 :rfc:`5545` examples of this property:
2453
2454 .. code-block:: ics
2455
2456 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2457
2458 .. code-block:: ics
2459
2460 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2461
2462 :rfc:`9253` examples of this property:
2463
2464 .. code-block:: ics
2465
2466 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2467 https://example.com/caldav/user/jb/cal/
2468 19960401-080045-4000F192713.ics
2469
2470 See also :class:`icalendar.enums.RELTYPE`.
2471
2472 """
2473 return PropertyListView(self, "RELATED-TO")
2474
2475
2476def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2477 """Set the RELATED-TO properties."""
2478 PropertyListView(self, "RELATED-TO").replace_with(values)
2479
2480
2481def _del_related_to(self: Component):
2482 """Delete the RELATED-TO properties."""
2483 PropertyListView(self, "RELATED-TO").clear()
2484
2485
2486related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2487
2488
2489def _get_concepts(self: Component) -> list[vUri]:
2490 """CONCEPT
2491
2492 Purpose:
2493 CONCEPT defines the formal categories for a calendar component.
2494
2495 Conformance:
2496 Since :rfc:`9253`,
2497 this property can be specified zero or more times in any iCalendar component.
2498
2499 Description:
2500 This property is used to specify formal categories or classifications of
2501 the calendar component. The values are useful in searching for a calendar
2502 component of a particular type and category.
2503
2504 This categorization is distinct from the more informal "tagging" of components
2505 provided by the existing CATEGORIES property. It is expected that the value of
2506 the CONCEPT property will reference an external resource that provides
2507 information about the categorization.
2508
2509 In addition, a structured URI value allows for hierarchical categorization of
2510 events.
2511
2512 Possible category resources are the various proprietary systems, for example,
2513 the Library of Congress, or an open source of categorization data.
2514
2515 Examples:
2516 The following is an example of this property.
2517 It points to a server acting as the source for the calendar object.
2518
2519 .. code-block:: ics
2520
2521 CONCEPT:https://example.com/event-types/arts/music
2522
2523 .. seealso::
2524
2525 :attr:`icalendar.prop.categories.vCategory`
2526 """
2527 return PropertyListView(self, "CONCEPT")
2528
2529
2530CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2531
2532
2533def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2534 """Set the concepts."""
2535 PropertyListView(self, "CONCEPT").replace_with(concepts)
2536
2537
2538def _del_concepts(self: Component):
2539 """Delete the concepts."""
2540 PropertyListView(self, "CONCEPT").clear()
2541
2542
2543concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2544
2545
2546def multi_string_property(name: str, doc: str):
2547 """A property for an iCalendar Property that can occur multiple times."""
2548
2549 def fget(self: Component) -> PropertyListView:
2550 """Get the values of a multi-string property."""
2551 return PropertyListView(self, name)
2552
2553 def fset(self: Component, value: list[str] | str | None) -> None:
2554 """Set the values of a multi-string property."""
2555 PropertyListView(self, name).replace_with(value)
2556
2557 def fdel(self: Component):
2558 """Delete the values of a multi-string property."""
2559 PropertyListView(self, name).clear()
2560
2561 return property(fget, fset, fdel, doc=doc)
2562
2563
2564refids_property = multi_string_property(
2565 "REFID",
2566 """REFID
2567
2568Purpose:
2569 REFID acts as a key for associated iCalendar entities.
2570
2571Conformance:
2572 Since :rfc:`9253`,
2573 this property can be specified zero or more times in any iCalendar component.
2574
2575Description:
2576 The value of this property is free-form text that creates an
2577 identifier for associated components.
2578 All components that use the same REFID value are associated through
2579 that value and can be located or retrieved as a group.
2580 For example, all of the events in a travel itinerary
2581 would have the same REFID value, so as to be grouped together.
2582
2583Examples:
2584 The following is an example of this property.
2585
2586 .. code-block:: ics
2587
2588 REFID:itinerary-2014-11-17
2589
2590 Use a REFID to associate several VTODOs:
2591
2592 .. code-block:: pycon
2593
2594 >>> from icalendar import Todo
2595 >>> todo_1 = Todo.new(
2596 ... summary="turn off stove",
2597 ... refids=["travel", "alps"]
2598 ... )
2599 >>> todo_2 = Todo.new(
2600 ... summary="pack backpack",
2601 ... refids=["travel", "alps"]
2602 ... )
2603 >>> todo_1.refids == todo_2.refids
2604 True
2605
2606.. note::
2607
2608 When you assign a list to this property, the returned list
2609 is the same object stored in the component. Modifying it (``append()``,
2610 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2611 the component stores.
2612
2613 However, if you assign a single string value to this property, the returned
2614 list is a temporary copy, and changes to this list don't affect the component.
2615""",
2616)
2617
2618REQUEST_STATUS_property = multi_string_property(
2619 "REQUEST-STATUS",
2620 """This property defines the status code returned for a scheduling request.
2621
2622You can assign a single string, a list of strings, or ``None`` to this
2623property. The property stores a :class:`str` as-is. Assigning ``None`` or
2624an empty list removes all REQUEST-STATUS values, as does deleting the
2625property.
2626
2627The value consists of a short return status code component, a longer
2628return status description component, and optionally a status-specific
2629data component, separated by semicolons (statcode;statdesc[;extdata]).
2630The return status components are defined in :rfc:`5545#section-3.8.8.3`.
2631
2632The REQUEST-STATUS property can be specified in the following
2633icalendar components as ``REQUEST_STATUS``.
2634
2635- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>`
2636- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>`
2637- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>`
2638- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>`
2639
2640Note:
2641 When you assign a list to this property, the returned list
2642 is the same object stored in the component. Modifying it (``append()``,
2643 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2644 the component stores.
2645
2646 However, if you assign a single string value to this property, the returned
2647 list is a temporary copy, and changes to this list don't affect the component.
2648
2649Parameters:
2650 request_status(str | list[str] | None):
2651 Either a single status string, a list of status strings, or
2652 ``None`` to set the component's status code returned for a
2653 scheduling request.
2654
2655Example:
2656 Add a request status to an event:
2657
2658 .. code-block:: pycon
2659
2660 >>> from icalendar import Event
2661 >>> event = Event.new(request_status="2.0;Success")
2662 >>> event.REQUEST_STATUS == ["2.0;Success"]
2663 True
2664
2665 :class:`~icalendar.prop.request_status.vRequestStatus` defines
2666 properties for ease of use:
2667
2668 .. code-block:: pycon
2669
2670 >>> request_status = event.REQUEST_STATUS[0]
2671 >>> request_status.code
2672 (2, 0)
2673 >>> request_status.description
2674 'Success'
2675
2676""",
2677)
2678
2679RESOURCES_property = multi_string_property(
2680 "RESOURCES",
2681 """This property defines resources for a calendar component.
2682
2683You can assign a single string, a list of strings, or ``None`` to this
2684property. The property stores a :class:`str` as-is. Assigning ``None`` or
2685an empty list removes all RESOURCES values, as does deleting the property.
2686
2687The value is a comma-separated list of resources, such as equipment,
2688facilities, or other things that the component needs. Each item of the
2689list becomes its own RESOURCES property value. See
2690:rfc:`5545#section-3.8.1.10` for the specification.
2691
2692The RESOURCES property can be specified in the following
2693icalendar components as ``resources`` using the component's
2694``new()`` constructor.
2695
2696- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>`
2697- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>`
2698
2699Parameters:
2700 resources(str | list[str] | None):
2701 Either a single resource string, a list of resource strings,
2702 or ``None`` to set the component's resources.
2703
2704Note:
2705 When you assign a list to this property, the returned list
2706 is the same object stored in the component. Modifying it (``append()``,
2707 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2708 the component stores.
2709
2710 However, if you assign a single string value to this property, the returned
2711 list is a temporary copy, and changes to this list don't affect the component.
2712
2713Example:
2714 Add resources to an event:
2715
2716 .. code-block:: pycon
2717
2718 >>> from icalendar import Event
2719 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"])
2720 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"]
2721 True
2722""",
2723)
2724
2725XML_property = multi_string_property(
2726 "XML",
2727 """This property embeds XML elements into a component.
2728
2729You can assign XML text, an :class:`~xml.etree.ElementTree.Element`,
2730an :class:`~icalendar.parser.xcal.adapter.ElementAdapter`,
2731a list of these, or ``None`` to this property.
2732The values become :class:`~icalendar.prop.xml.vXml` values.
2733A :class:`~icalendar.prop.text.vText` value is not converted,
2734so it is written as text in xCal.
2735XML with the value type BINARY is a :class:`~icalendar.prop.binary.vBinary`.
2736Assigning ``None`` or an empty list removes all XML values,
2737as does deleting the property.
2738
2739The XML property is defined in :rfc:`6321#section-4.2`.
2740It can be specified multiple times in any component.
2741Its value is an XML element in a namespace other than the iCalendar namespace.
2742In xCal, the element is part of the properties of the component,
2743see :rfc:`6321#section-4.1`.
2744
2745.. note::
2746
2747 To change the values, assign a new list or use ``add()``.
2748 Changing the returned list does not always change the component.
2749
2750Example:
2751 Add XML to an event and get the XML elements:
2752
2753 .. code-block:: pycon
2754
2755 >>> from icalendar import Event
2756 >>> event = Event()
2757 >>> event.XML = '<a xmlns="http://example.com/ns">text</a>'
2758 >>> print(event.to_ical().decode())
2759 BEGIN:VEVENT
2760 XML:<a xmlns="http://example.com/ns">text</a>
2761 END:VEVENT
2762 >>> [xml.element.tag for xml in event.XML]
2763 ['{http://example.com/ns}a']
2764""",
2765)
2766
2767
2768ATTACHMENTS_TYPE_SETTER: TypeAlias = (
2769 str | bytes | vUri | vBinary | list[str | bytes | vUri | vBinary] | None
2770)
2771
2772
2773def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary:
2774 """Convert one attachment value."""
2775 if isinstance(value, (vUri, vBinary)):
2776 return value
2777 if isinstance(value, str):
2778 return vUri(value)
2779 if isinstance(value, bytes):
2780 return vBinary(value)
2781 raise TypeError(
2782 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}."
2783 )
2784
2785
2786def _get_attachments(self: Component) -> list[vUri | vBinary]:
2787 """Get all the attachments"""
2788 attachments = self.get("ATTACH", [])
2789 if not isinstance(attachments, SEQUENCE_TYPES):
2790 return [attachments]
2791 return list(attachments)
2792
2793
2794def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None:
2795 """Set attachments properties"""
2796 if value is None:
2797 _del_attachments(self)
2798 return
2799 if not isinstance(value, list):
2800 value = [value]
2801 attachments = [_normalize_attachment(attachment) for attachment in value]
2802 _del_attachments(self)
2803 for attachment in attachments:
2804 self.add("ATTACH", attachment)
2805
2806
2807def _del_attachments(self: Component) -> None:
2808 """Delete all attachments"""
2809 self.pop("ATTACH", None)
2810
2811
2812attachments_property = property(
2813 _get_attachments,
2814 _set_attachments,
2815 _del_attachments,
2816 """This property defines the attachments for a component.
2817
2818Setting this property replaces all existing attachments. A :class:`str`
2819is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is
2820converted to :class:`~icalendar.prop.binary.vBinary`. Values that are
2821already :class:`~icalendar.prop.uri.vUri` or
2822:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their
2823parameters are preserved. Setting ``None`` or an empty list removes all
2824attachments, as does deleting the property.
2825
2826Parameters:
2827 attachments(str | bytes | vUri | vBinary | list | None):
2828 A single attachment, or a list of attachments to set. Accepts
2829 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`,
2830 and :class:`~icalendar.prop.binary.vBinary`, individually or
2831 mixed together in a list.
2832
2833Example:
2834 Attach a URI to an event, then replace it with a URI and inline
2835 binary data together:
2836
2837 .. code-block:: pycon
2838
2839 >>> from icalendar import Event, vUri, vBinary
2840 >>> event = Event()
2841 >>> event.attachments
2842 []
2843 >>> event.attachments = ["https://example.com/agenda.pdf"]
2844 >>> print(event.to_ical().decode())
2845 BEGIN:VEVENT
2846 ATTACH:https://example.com/agenda.pdf
2847 END:VEVENT
2848 >>> event.attachments = [
2849 ... vUri(
2850 ... "https://example.com/agenda.pdf",
2851 ... params={"FMTTYPE": "application/pdf"},
2852 ... ),
2853 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},),
2854 ... ]
2855 >>> len(event.attachments)
2856 2
2857
2858.. note::
2859
2860 An alarm as an audio action must not contain more than one attachment.
2861
2862 List modifications do not modify the component. Methods such as
2863 ``append()``, ``extend()``, and ``remove()``, as well as item
2864 assignment, act on a copy. Assign the list back to the property, or
2865 use :meth:`Component.add <icalendar.cal.component.Component.add>`
2866 with a typed value instead.
2867
2868.. seealso::
2869
2870 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH``
2871 property.
2872""",
2873)
2874
2875
2876__all__ = [
2877 "ATTACHMENTS_TYPE_SETTER",
2878 "ATTENDEE_TYPE_SETTER",
2879 "CONCEPTS_TYPE_SETTER",
2880 "LINKS_TYPE_SETTER",
2881 "RECURRENCE_ID",
2882 "RELATED_TO_TYPE_SETTER",
2883 "PropertyListView",
2884 "REQUEST_STATUS_property",
2885 "RESOURCES_property",
2886 "XML_property",
2887 "attachments_property",
2888 "attendees_property",
2889 "busy_type_property",
2890 "categories_property",
2891 "class_property",
2892 "color_property",
2893 "comments_property",
2894 "concepts_property",
2895 "conferences_property",
2896 "contacts_property",
2897 "create_single_property",
2898 "description_property",
2899 "descriptions_property",
2900 "duration_property",
2901 "exdates_property",
2902 "get_duration_property",
2903 "get_end_property",
2904 "get_start_end_duration_with_validation",
2905 "get_start_property",
2906 "images_property",
2907 "links_property",
2908 "location_property",
2909 "multi_language_text_property",
2910 "multi_string_property",
2911 "organizer_property",
2912 "priority_property",
2913 "property_del_duration",
2914 "property_doc_duration_template",
2915 "property_get_duration",
2916 "property_set_duration",
2917 "rdates_property",
2918 "refids_property",
2919 "related_to_property",
2920 "repeat_property",
2921 "rfc_7953_dtend_property",
2922 "rfc_7953_dtstart_property",
2923 "rfc_7953_duration_property",
2924 "rfc_7953_end_property",
2925 "rrules_property",
2926 "sequence_property",
2927 "set_duration_with_locking",
2928 "set_end_with_locking",
2929 "set_start_with_locking",
2930 "single_int_property",
2931 "single_utc_property",
2932 "source_property",
2933 "status_property",
2934 "summary_property",
2935 "transparency_property",
2936 "uid_property",
2937 "url_property",
2938]