Source code for icalendar.cal.component

"""The base for :rfc:`5545` components."""

from __future__ import annotations

import json
from copy import deepcopy
from datetime import date, datetime, time, timedelta, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal, overload

from icalendar.attr import (
    CONCEPTS_TYPE_SETTER,
    LINKS_TYPE_SETTER,
    RELATED_TO_TYPE_SETTER,
    comments_property,
    concepts_property,
    links_property,
    refids_property,
    related_to_property,
    single_utc_property,
    uid_property,
)
from icalendar.cal.component_factory import ComponentFactory
from icalendar.caselessdict import CaselessDict
from icalendar.error import InvalidCalendar, JCalParsingError
from icalendar.parser import (
    Contentline,
    Contentlines,
    Parameters,
    q_join,
    q_split,
)
from icalendar.parser.ical.component import ComponentIcalParser
from icalendar.parser_tools import DEFAULT_ENCODING
from icalendar.prop import VPROPERTY, TypesFactory, vDDDLists, vText
from icalendar.timezone import tzp
from icalendar.tools import is_date

if TYPE_CHECKING:
    from collections.abc import Iterable

    from icalendar.compatibility import Self

_marker = []


[docs] class Component(CaselessDict): """Base class for calendar components. Component is the base object for calendar, Event and the other components defined in :rfc:`5545`. Normally you will not use this class directly, but rather one of the subclasses. """ name: ClassVar[str | None] = None """The name of the component. This is defined in each component class. Example: .. code-block:: pycon >>> from icalendar import Calendar >>> cal = Calendar.new() >>> cal.name 'VCALENDAR' """ required: ClassVar[tuple[()]] = () """These properties are required.""" singletons: ClassVar[tuple[()]] = () """These properties must appear only once.""" multiple: ClassVar[tuple[()]] = () """These properties may occur more than once.""" exclusive: ClassVar[tuple[()]] = () """These properties are mutually exclusive.""" inclusive: ClassVar[(tuple[str] | tuple[tuple[str, str]])] = () """These properties are inclusive. In other words, if the first property in the tuple occurs, then the second one must also occur. Example: .. code-block:: python ('duration', 'repeat') """ ignore_exceptions: ClassVar[bool] = False """Whether or not to ignore exceptions when parsing. If ``True``, and this component can't be parsed, then it will silently ignore it, rather than let the exception propagate upwards. """ types_factory: ClassVar[TypesFactory] = TypesFactory.instance() _components_factory: ClassVar[ComponentFactory | None] = None subcomponents: list[Component] """All subcomponents of this component.""" @classmethod def _get_component_factory(cls) -> ComponentFactory: """Get the component factory.""" if cls._components_factory is None: cls._components_factory = ComponentFactory() return cls._components_factory
[docs] @classmethod def get_component_class(cls, name: str) -> type[Component]: """Return a component with this name. Parameters: name: Name of the component, i.e. ``VCALENDAR`` """ return cls._get_component_factory().get_component_class(name)
[docs] @classmethod def register(cls, component_class: type[Component]) -> None: """Register a custom component class. Parameters: component_class: Component subclass to register. Must have a ``name`` attribute. Raises: ValueError: If ``component_class`` has no ``name`` attribute. ValueError: If a component with this name is already registered. Examples: Create a custom icalendar component with the name ``X-EXAMPLE``: .. code-block:: pycon >>> from icalendar import Component >>> class XExample(Component): ... name = "X-EXAMPLE" ... def custom_method(self): ... return "custom" >>> Component.register(XExample) """ if not hasattr(component_class, "name") or component_class.name is None: raise ValueError(f"{component_class} must have a 'name' attribute") # Check if already registered component_factory = cls._get_component_factory() existing = component_factory.get(component_class.name) if existing is not None and existing is not component_class: raise ValueError( f"Component '{component_class.name}' is already registered" f" as {existing}" ) component_factory.add_component_class(component_class)
@staticmethod def _infer_value_type( value: date | datetime | timedelta | time | tuple | list, ) -> str | None: """Infer the ``VALUE`` parameter from a Python type. Parameters: value: Python native type, one of :class:`datetime.date`, :class:`datetime.datetime`, :class:`datetime.timedelta`, :class:`datetime.time`, :class:`tuple`, or :class:`list`. Returns: str or None: The ``VALUE`` parameter string, for example, "DATE", "TIME", or other string, or ``None`` if no specific ``VALUE`` is needed. """ if isinstance(value, list): if not value: return None # Check if ALL items are date (but not datetime) if all(is_date(item) for item in value): return "DATE" # Check if ALL items are time if all(isinstance(item, time) for item in value): return "TIME" # Mixed types or other types - don't infer return None if is_date(value): return "DATE" if isinstance(value, time): return "TIME" # Don't infer PERIOD - it's too risky and vPeriod already handles it return None def __init__(self, *args, **kwargs): """Set keys to upper for initial dict.""" super().__init__(*args, **kwargs) # set parameters here for properties that use non-default values self.subcomponents: list[Component] = [] # Components can be nested. self.errors = [] # If we ignored exception(s) while # parsing a property, contains error strings def __bool__(self): """Returns True, CaselessDict would return False if it had no items.""" return True def __getitem__(self, key): """Get property value from the component dictionary.""" return super().__getitem__(key)
[docs] def get(self, key, default=None): """Get property value with default.""" try: return self[key] except KeyError: return default
[docs] def is_empty(self): """Returns True if Component has no items or subcomponents, else False.""" return bool(not list(self.values()) + self.subcomponents)
############################# # handling of property values @classmethod def _encode(cls, name, value, parameters=None, encode=1): """Encode values to icalendar property values. :param name: Name of the property. :type name: string :param value: Value of the property. Either of a basic Python type of any of the icalendar's own property types. :type value: Python native type or icalendar property type. :param parameters: Property parameter dictionary for the value. Only available, if encode is set to True. :type parameters: Dictionary :param encode: True, if the value should be encoded to one of icalendar's own property types (Fallback is "vText") or False, if not. :type encode: Boolean :returns: icalendar property value """ if not encode: return value if isinstance(value, cls.types_factory.all_types): # Don't encode already encoded values. obj = value else: # Extract VALUE parameter if present, or infer it from the Python type value_param = None if parameters and "VALUE" in parameters: value_param = parameters["VALUE"] elif not isinstance(value, cls.types_factory.all_types): inferred = cls._infer_value_type(value) if inferred: value_param = inferred # Auto-set the VALUE parameter if parameters is None: parameters = {} if "VALUE" not in parameters: parameters["VALUE"] = inferred klass = cls.types_factory.for_property(name, value_param) obj = klass(value) if parameters: if not hasattr(obj, "params"): obj.params = Parameters() for key, item in parameters.items(): if item is None: if key in obj.params: del obj.params[key] else: obj.params[key] = item return obj
[docs] def add( self, name: str, value, parameters: dict[str, str] | Parameters = None, encode: bool = True, ): """Add a property. :param name: Name of the property. :type name: string :param value: Value of the property. Either of a basic Python type of any of the icalendar's own property types. :type value: Python native type or icalendar property type. :param parameters: Property parameter dictionary for the value. Only available, if encode is set to True. :type parameters: Dictionary :param encode: True, if the value should be encoded to one of icalendar's own property types (Fallback is "vText") or False, if not. :type encode: Boolean :returns: None """ if isinstance(value, datetime) and name.lower() in ( "dtstamp", "created", "last-modified", ): # RFC expects UTC for those... force value conversion. value = tzp.localize_utc(value) # encode value if ( encode and isinstance(value, list) and name.lower() not in ["rdate", "exdate", "categories"] ): # Individually convert each value to an ical type except rdate and # exdate, where lists of dates might be passed to vDDDLists. value = [self._encode(name, v, parameters, encode) for v in value] else: value = self._encode(name, value, parameters, encode) # set value if name in self: # If property already exists, append it. oldval = self[name] if isinstance(oldval, list): if isinstance(value, list): value = oldval + value else: oldval.append(value) value = oldval else: value = [oldval, value] self[name] = value
def _decode(self, name: str, value: VPROPERTY): """Internal for decoding property values.""" # TODO: Currently the decoded method calls the icalendar.prop instances # from_ical. We probably want to decode properties into Python native # types here. But when parsing from an ical string with from_ical, we # want to encode the string into a real icalendar.prop property. if hasattr(value, "ical_value"): return value.ical_value if isinstance(value, vDDDLists): # TODO: Workaround unfinished decoding return value decoded = self.types_factory.from_ical(name, value) # TODO: remove when proper decoded is implemented in every prop.* class # Workaround to decode vText properly if isinstance(decoded, vText): decoded = decoded.encode(DEFAULT_ENCODING) return decoded
[docs] def decoded(self, name: str, default: Any = _marker) -> Any: """Returns decoded value of property. A component maps keys to icalendar property value types. This function returns values compatible to native Python types. """ if name in self: value = self[name] if isinstance(value, list): return [self._decode(name, v) for v in value] return self._decode(name, value) if default is _marker: raise KeyError(name) return default
######################################################################## # Inline values. A few properties have multiple values inlined in in one # property line. These methods are used for splitting and joining these.
[docs] def get_inline(self, name, decode=1): """Returns a list of values (split on comma).""" vals = [v.strip('" ') for v in q_split(self[name])] if decode: return [self._decode(name, val) for val in vals] return vals
[docs] def set_inline(self, name, values, encode=1): """Converts a list of values into comma separated string and sets value to that. """ if encode: values = [self._encode(name, value, encode=1) for value in values] self[name] = self.types_factory["inline"](q_join(values))
######################### # Handling of components
[docs] def add_component(self, component: Component) -> None: """Add a subcomponent to this component.""" self.subcomponents.append(component)
def _walk( self, name: str | None, select: callable[[Component], bool] ) -> list[Component]: """Walk to given component.""" result = [] stack = [self] while stack: component = stack.pop() if (name is None or component.name == name) and select(component): result.append(component) stack.extend(reversed(component.subcomponents)) return result
[docs] def walk( self, name: str | None = None, select: callable[[Component], bool] = lambda _: True, ) -> list[Component]: """Recursively traverses component and subcomponents. Returns sequence of same. If name is passed, only components with name will be returned. :param name: The name of the component or None such as ``VEVENT``. :param select: A function that takes the component as first argument and returns True/False. :returns: A list of components that match. :rtype: list[Component] """ if name is not None: name = name.upper() return self._walk(name, select)
[docs] def with_uid(self, uid: str) -> list[Component]: """Return a list of components with the given UID. Parameters: uid: The UID of the component. Returns: list[Component]: List of components with the given UID. """ return self.walk(select=lambda c: c.uid == uid)
##################### # Generation
[docs] def property_items( self, recursive: bool = True, sorted: bool = True, ) -> list[tuple[str, object]]: """Returns properties in this component and subcomponents as: [(name, value), ...] """ # Iterative implementation to avoid RecursionError result = [] v_text = self.types_factory["text"] # Stack stores (component, state) # state: True means we are processing the END of the component # state: False means we are processing the BEGIN and properties of the component stack = [(self, False)] while stack: comp, is_end = stack.pop() if is_end: result.append(("END", v_text(comp.name).to_ical())) else: result.append(("BEGIN", v_text(comp.name).to_ical())) property_names = comp.sorted_keys() if sorted else comp.keys() for name in property_names: values = comp[name] if isinstance(values, list): # normally one property is one line for value in values: result.append((name, value)) else: result.append((name, values)) # Push the END marker for this component stack.append((comp, True)) # Push subcomponents if recursion is enabled if recursive: # Push in reverse order to maintain original order in result for subcomponent in reversed(comp.subcomponents): stack.append((subcomponent, False)) return result
@overload @classmethod def from_ical( cls, st: str | bytes, multiple: Literal[False] = False ) -> Component: ... @overload @classmethod def from_ical(cls, st: str | bytes, multiple: Literal[True]) -> list[Component]: ... @classmethod def _get_ical_parser(cls, st: str | bytes) -> ComponentIcalParser: """Get the iCal parser for the given input string.""" return ComponentIcalParser(st, cls._get_component_factory(), cls.types_factory)
[docs] @classmethod def from_ical( cls, st: str | bytes | Path, multiple: bool = False ) -> Component | list[Component]: """Parse iCalendar data into component instances. Handles standard and custom components (``X-*``, IANA-registered). Parameters: st: iCalendar data as bytes or string, or a path to an iCalendar file as :class:`pathlib.Path` or string. multiple: If ``True``, returns list. If ``False``, returns single component. Returns: Component or list of components See Also: :doc:`/how-to/custom-components` for examples of parsing custom components """ if isinstance(st, Path): st = st.read_bytes() elif isinstance(st, str) and "\n" not in st and "\r" not in st: path = Path(st) try: is_file = path.is_file() except OSError: is_file = False if is_file: st = path.read_bytes() parser = cls._get_ical_parser(st) components = parser.parse() if multiple: return components if len(components) > 1: raise ValueError( cls._format_error( "Found multiple components where only one is allowed", st ) ) if len(components) < 1: raise ValueError( cls._format_error( "Found no components where exactly one is required", st ) ) return components[0]
@staticmethod def _format_error(error_description, bad_input, elipsis="[...]"): # there's three character more in the error, ie. ' ' x2 and a ':' max_error_length = 100 - 3 if len(error_description) + len(bad_input) + len(elipsis) > max_error_length: truncate_to = max_error_length - len(error_description) - len(elipsis) return f"{error_description}: {bad_input[:truncate_to]} {elipsis}" return f"{error_description}: {bad_input}"
[docs] def content_line(self, name, value, sorted: bool = True): """Returns property as content line.""" params = getattr(value, "params", Parameters()) return Contentline.from_parts(name, params, value, sorted=sorted)
[docs] def content_lines(self, sorted: bool = True): """Converts the Component and subcomponents into content lines.""" contentlines = Contentlines() for name, value in self.property_items(sorted=sorted): cl = self.content_line(name, value, sorted=sorted) contentlines.append(cl) contentlines.append("") # remember the empty string in the end return contentlines
[docs] def to_ical(self, sorted: bool = True): """ :param sorted: Whether parameters and properties should be lexicographically sorted. """ content_lines = self.content_lines(sorted=sorted) return content_lines.to_ical()
def __repr__(self): """String representation of class with all of it's subcomponents.""" subs = ", ".join(str(it) for it in self.subcomponents) return ( f"{self.name or type(self).__name__}" f"({dict(self)}{', ' + subs if subs else ''})" ) def __eq__(self, other): if len(self.subcomponents) != len(other.subcomponents): return False properties_equal = super().__eq__(other) if not properties_equal: return False # The subcomponents might not be in the same order, # neither there's a natural key we can sort the subcomponents by nor # are the subcomponent types hashable, so we cant put them in a set to # check for set equivalence. We have to iterate over the subcomponents # and look for each of them in the list. for subcomponent in self.subcomponents: if subcomponent not in other.subcomponents: return False # We now know the other component's subcomponents are not a strict subset # of this component's. However, we still need to check the other way around. for subcomponent in other.subcomponents: if subcomponent not in self.subcomponents: return False return True DTSTAMP = stamp = single_utc_property( "DTSTAMP", """The UTC datetime stamp recording when this component instance was created or last revised. Defined in :rfc:`5545#section-3.8.7.2` and required in ``VEVENT``, ``VTODO``, ``VJOURNAL``, and ``VFREEBUSY`` components. When the calendar object carries a ``METHOD`` property (e.g., for scheduling), this value is the creation time of *this particular revision*. Without a ``METHOD`` property it is equivalent to :attr:`LAST_MODIFIED`. The value is always in UTC. Also accessible as :attr:`stamp`. Example: .. code-block:: pycon >>> from datetime import timezone, datetime >>> from icalendar import Event >>> event = Event() >>> event.DTSTAMP = datetime(2024, 6, 1, 12, 0, 0, tzinfo=timezone.utc) >>> event.DTSTAMP # doctest: +ELLIPSIS datetime.datetime(2024, 6, 1, 12, 0, tzinfo=...) See also: :attr:`LAST_MODIFIED`, :attr:`CREATED`, :attr:`stamp` """, ) LAST_MODIFIED = single_utc_property( "LAST-MODIFIED", """The UTC datetime when this component's information was last revised, per :rfc:`5545#section-3.8.7.3`. Analogous to a file's modification timestamp. This property is optional; when absent, :attr:`last_modified` falls back to :attr:`DTSTAMP`. Applicable to ``VEVENT``, ``VTODO``, ``VJOURNAL``, and ``VTIMEZONE`` components. The value is always in UTC. Example: .. code-block:: pycon >>> from datetime import timezone, datetime >>> from icalendar import Event >>> event = Event() >>> event.LAST_MODIFIED = datetime(2024, 6, 1, 9, 0, 0, tzinfo=timezone.utc) >>> event.LAST_MODIFIED # doctest: +ELLIPSIS datetime.datetime(2024, 6, 1, 9, 0, tzinfo=...) See also: :attr:`last_modified`, :attr:`DTSTAMP`, :attr:`CREATED` """, ) @property def last_modified(self) -> datetime: """Datetime when the information associated with the component was last revised. Since :attr:`LAST_MODIFIED` is an optional property, this returns :attr:`DTSTAMP` if :attr:`LAST_MODIFIED` is not set. """ return self.LAST_MODIFIED or self.DTSTAMP @last_modified.setter def last_modified(self, value): self.LAST_MODIFIED = value @last_modified.deleter def last_modified(self): del self.LAST_MODIFIED @property def created(self) -> datetime: """Datetime when the information associated with the component was created. Since :attr:`CREATED` is an optional property, this returns :attr:`DTSTAMP` if :attr:`CREATED` is not set. """ return self.CREATED or self.DTSTAMP @created.setter def created(self, value): self.CREATED = value @created.deleter def created(self): del self.CREATED
[docs] def is_thunderbird(self) -> bool: """Whether this component has attributes that indicate that Mozilla Thunderbird created it.""" return any(attr.startswith("X-MOZ-") for attr in self.keys())
@staticmethod def _utc_now(): """Return now as UTC value.""" return datetime.now(timezone.utc) uid = uid_property comments = comments_property links = links_property related_to = related_to_property concepts = concepts_property refids = refids_property CREATED = single_utc_property( "CREATED", """The UTC datetime when this calendar component was first created, per :rfc:`5545#section-3.8.7.1`. Records when the calendar user agent originally stored the component. This property is optional; when absent, :attr:`created` falls back to :attr:`DTSTAMP`. Applicable to ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components. The value is always in UTC. Example: .. code-block:: pycon >>> from datetime import timezone, datetime >>> from icalendar import Event >>> event = Event() >>> event.CREATED = datetime(2024, 1, 1, 8, 0, 0, tzinfo=timezone.utc) >>> event.CREATED # doctest: +ELLIPSIS datetime.datetime(2024, 1, 1, 8, 0, tzinfo=...) See also: :attr:`created`, :attr:`DTSTAMP`, :attr:`LAST_MODIFIED` """, ) _validate_new = True @staticmethod def _validate_start_and_end(start, end): """This validates start and end. Raises: ~error.InvalidCalendar: If the information is not valid """ if start is None or end is None: return if start > end: raise InvalidCalendar("end must be after start")
[docs] @classmethod def new( cls, created: date | None = None, comments: list[str] | str | None = None, concepts: CONCEPTS_TYPE_SETTER = None, last_modified: date | None = None, links: LINKS_TYPE_SETTER = None, refids: list[str] | str | None = None, related_to: RELATED_TO_TYPE_SETTER = None, stamp: date | None = None, subcomponents: Iterable[Component] | None = None, ) -> Component: """Create a new component. Parameters: comments: The :attr:`comments` of the component. concepts: The :attr:`concepts` of the component. created: The :attr:`created` of the component. last_modified: The :attr:`last_modified` of the component. links: The :attr:`links` of the component. related_to: The :attr:`related_to` of the component. stamp: The :attr:`DTSTAMP` of the component. subcomponents: The subcomponents of the component. Raises: ~error.InvalidCalendar: If the content is not valid according to :rfc:`5545`. .. warning:: As time progresses, we will be stricter with the validation. """ component = cls() component.DTSTAMP = stamp component.created = created component.last_modified = last_modified component.comments = comments component.links = links component.related_to = related_to component.concepts = concepts component.refids = refids if subcomponents is not None: component.subcomponents = ( subcomponents if isinstance(subcomponents, list) else list(subcomponents) ) return component
[docs] def to_jcal(self) -> list: """Convert this component to a jCal object. Returns: jCal object See also :attr:`to_json`. In this example, we create a simple VEVENT component and convert it to jCal: .. code-block:: pycon >>> from icalendar import Event >>> from datetime import date >>> from pprint import pprint >>> event = Event.new(summary="My Event", start=date(2025, 11, 22)) >>> pprint(event.to_jcal()) ['vevent', [['dtstamp', {}, 'date-time', '2025-05-17T08:06:12Z'], ['summary', {}, 'text', 'My Event'], ['uid', {}, 'text', 'd755cef5-2311-46ed-a0e1-6733c9e15c63'], ['dtstart', {}, 'date', '2025-11-22']], []] """ properties = [] for key, value in self.items(): for item in value if isinstance(value, list) else [value]: properties.append(item.to_jcal(key.lower())) return [ self.name.lower(), properties, [subcomponent.to_jcal() for subcomponent in self.subcomponents], ]
[docs] def to_json(self) -> str: """Return this component as a jCal JSON string. Returns: JSON string See also :attr:`to_jcal`. """ return json.dumps(self.to_jcal())
[docs] @classmethod def from_jcal(cls, jcal: str | list) -> Component: """Create a component from a jCal list. Parameters: jcal: jCal list or JSON string according to :rfc:`7265`. Raises: ~error.JCalParsingError: If the jCal provided is invalid. ~json.JSONDecodeError: If the provided string is not valid JSON. This reverses :func:`to_json` and :func:`to_jcal`. The following code parses an example from :rfc:`7265`: .. code-block:: pycon >>> from icalendar import Component >>> jcal = ["vcalendar", ... [ ... ["calscale", {}, "text", "GREGORIAN"], ... ["prodid", {}, "text", "-//Example Inc.//Example Calendar//EN"], ... ["version", {}, "text", "2.0"] ... ], ... [ ... ["vevent", ... [ ... ["dtstamp", {}, "date-time", "2008-02-05T19:12:24Z"], ... ["dtstart", {}, "date", "2008-10-06"], ... ["summary", {}, "text", "Planning meeting"], ... ["uid", {}, "text", "4088E990AD89CB3DBB484909"] ... ], ... [] ... ] ... ] ... ] >>> calendar = Component.from_jcal(jcal) >>> print(calendar.name) VCALENDAR >>> print(calendar.prodid) -//Example Inc.//Example Calendar//EN >>> event = calendar.events[0] >>> print(event.summary) Planning meeting """ if isinstance(jcal, str): jcal = json.loads(jcal) if not isinstance(jcal, list) or len(jcal) != 3: raise JCalParsingError( "A component must be a list with 3 items.", cls, value=jcal ) name, properties, subcomponents = jcal if not isinstance(name, str): raise JCalParsingError( "The name must be a string.", cls, path=[0], value=name ) if name.upper() != cls.name: # delegate to correct component class component_cls = cls.get_component_class(name.upper()) return component_cls.from_jcal(jcal) component = cls() if not isinstance(properties, list): raise JCalParsingError( "The properties must be a list.", cls, path=1, value=properties ) for i, prop in enumerate(properties): JCalParsingError.validate_property(prop, cls, path=[1, i]) prop_name = prop[0] prop_value = prop[2] prop_cls: type[VPROPERTY] = cls.types_factory.for_property( prop_name, prop_value ) with JCalParsingError.reraise_with_path_added(1, i): v_prop = prop_cls.from_jcal(prop) # if we use the default value for that property, we can delete the # VALUE parameter if prop_cls == cls.types_factory.for_property(prop_name): del v_prop.VALUE component.add(prop_name, v_prop) if not isinstance(subcomponents, list): raise JCalParsingError( "The subcomponents must be a list.", cls, 2, value=subcomponents ) for i, subcomponent in enumerate(subcomponents): with JCalParsingError.reraise_with_path_added(2, i): component.subcomponents.append(cls.from_jcal(subcomponent)) return component
[docs] def copy(self, recursive: bool = False) -> Self: """Copy the component. Parameters: recursive: If ``True``, this creates copies of the component, its subcomponents, and all its properties. If ``False``, this only creates a shallow copy of the component. Returns: A copy of the component. Examples: Create a shallow copy of a component: .. code-block:: pycon >>> from icalendar import Event >>> event = Event.new(description="Event to be copied") >>> event_copy = event.copy() >>> str(event_copy.description) 'Event to be copied' Shallow copies lose their subcomponents: .. code-block:: pycon >>> from icalendar import Calendar >>> calendar = Calendar.example() >>> len(calendar.subcomponents) 3 >>> calendar_copy = calendar.copy() >>> len(calendar_copy.subcomponents) 0 A recursive copy also copies all the subcomponents: .. code-block:: pycon >>> full_calendar_copy = calendar.copy(recursive=True) >>> len(full_calendar_copy.subcomponents) 3 >>> full_calendar_copy.events[0] == calendar.events[0] True >>> full_calendar_copy.events[0] is calendar.events[0] False """ if recursive: return deepcopy(self) return super().copy()
[docs] def is_lazy(self) -> bool: """This component is fully parsed.""" return False
[docs] def parse(self) -> Self: """Return the fully parsed component. For non-lazy components, this returns self. For lazy components, this parses the component and returns the result. """ return self
__all__ = ["Component"]