Custom components#
This chapter describes how to use custom components in icalendar.
icalendar automatically handles X-components and IANA-components not in RFC 5545.
RFC 5545 defines two custom component types.
- X-Components
Vendor-specific components, for example,
X-MYAPP-SETTINGS.- IANA-Components
IANA-registered components, but not in RFC 5545.
icalendar preserves all custom components through dynamic component creation using ComponentFactory.
Parse custom components#
Parse custom components using either Component.from_ical() or Calendar.from_ical().
Component.from_ical()#
Parse any component type, including custom ones.
The following example shows how to parse a component, then display its name and SUMMARY.
>>> from icalendar import Component
>>> ics_data = b"""BEGIN:X-MYCOMPONENT
... SUMMARY:Custom component example
... X-CUSTOM-PROP:Some value
... END:X-MYCOMPONENT
... """
>>> component = Component.from_ical(ics_data)
>>> component.name
'X-MYCOMPONENT'
>>> str(component["SUMMARY"])
'Custom component example'
Calendar.from_ical()#
Parse a calendar containing custom components.
The following example shows how to parse a component, get its subcomponents, then display its name and UID.
>>> from icalendar import Calendar
>>> ics_data = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Example//EN
... BEGIN:X-CUSTOM
... UID:custom-1
... SUMMARY:A custom component
... END:X-CUSTOM
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(ics_data)
>>> custom = cal.subcomponents[0]
>>> custom.name
'X-CUSTOM'
>>> str(custom["UID"])
'custom-1'
Access custom components#
Custom components work exactly like standard components. The following example shows how to access a custom component to display its name and other attributes.
>>> from icalendar import Component
>>> # Create custom component using factory
>>> MyComp = Component.get_component_class("MYCOMP")
>>> comp = MyComp()
>>> comp.name
'MYCOMP'
>>>
>>> # Add properties
>>> comp.add("summary", "Test Summary")
>>> comp.add("x-custom-field", "Custom Value")
>>>
>>> # Access properties
>>> str(comp["SUMMARY"])
'Test Summary'
>>> str(comp.get("X-CUSTOM-FIELD"))
'Custom Value'
>>>
>>> # Add subcomponents
>>> from icalendar import Event
>>> event = Event()
>>> event.add("uid", "123")
>>> comp.add_component(event)
>>> len(comp.subcomponents)
1
Round-trip preservation#
Custom components are fully preserved during round-trip parsing. The following example demonstrates this feature.
>>> from icalendar import Calendar
>>> original = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Test//EN
... BEGIN:X-VENDOR-COMPONENT
... X-VENDOR-PROP:proprietary
... UID:vendor-123
... END:X-VENDOR-COMPONENT
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(original)
>>> regenerated = cal.to_ical()
>>> # Custom component and its properties are preserved
>>> b"X-VENDOR-COMPONENT" in regenerated
True
>>> b"X-VENDOR-PROP" in regenerated
True
Which from_ical()?#
Use Component.from_ical() for standalone components and fragments.
Use Calendar.from_ical() for complete iCalendar files.
It also handles timezones.
The following example shows the different usage of both methods.
>>> from icalendar import Component, Calendar
>>>
>>> # Standalone custom component - use Component.from_ical()
>>> standalone = b"""BEGIN:X-MYCOMP
... UID:123
... END:X-MYCOMP
... """
>>> comp = Component.from_ical(standalone)
>>> comp.name
'X-MYCOMP'
>>>
>>> # Complete calendar - use Calendar.from_ical()
>>> calendar_data = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Test//EN
... BEGIN:X-MYCOMP
... UID:123
... END:X-MYCOMP
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(calendar_data)
>>> len(cal.subcomponents)
1
Register custom component classes#
Register custom component classes with special behavior. The following example shows how to create and register a custom component with validation and helper methods.
from icalendar import Component
class XAcmeComponent(Component):
"""Custom ACME component with validation."""
name = "X-ACME"
def validate(self):
"""Validate required properties."""
required = ["UID", "X-ACME-ID"]
for prop in required:
if prop not in self:
raise ValueError(f"Missing {prop}")
def get_acme_id(self):
"""Get ACME ID."""
return self.get("X-ACME-ID")
# Register the component
Component.register(XAcmeComponent)
After registration, parsing BEGIN:X-ACME uses your custom class:
>>> from icalendar import Component
>>> class XAcmeComponent(Component):
... """Custom ACME component with validation."""
... name = "X-ACME"
... def validate(self):
... """Validate required properties."""
... required = ["UID", "X-ACME-ID"]
... for prop in required:
... if prop not in self:
... raise ValueError(f"Missing {prop}")
... def get_acme_id(self):
... """Get ACME ID."""
... return self.get("X-ACME-ID")
>>> Component.register(XAcmeComponent)
>>> ical_data = b"""BEGIN:X-ACME
... UID:123
... X-ACME-ID:acme-1
... END:X-ACME
... """
>>> comp = Component.from_ical(ical_data)
>>> comp.validate()
>>> str(comp.get_acme_id())
'acme-1'
RFC 5545 compliance#
The icalendar library is fully compliant with RFC 5545 requirements for custom components.
- Preserves unknown components
Custom components are never dropped.
- Maintains data integrity
All properties and subcomponents are preserved.
- Round-trip safe
Parse to serialize, and back to parse, produces equivalent results.
- No special handling
X-components and IANA-components are treated identically.
icalendar implements a permissive approach. Rather than rejecting unknown components, it preserves them while making them accessible through the same API as standard components.
Nested custom components#
Custom components can contain standard components, and vice versa.
>>> from icalendar import Component, Event
>>> # Custom component containing a standard event
>>> ics_data = b"""BEGIN:X-CONTAINER
... SUMMARY:Container
... BEGIN:VEVENT
... UID:event-1
... DTSTART:20240101T120000Z
... SUMMARY:Event inside custom component
... END:VEVENT
... END:X-CONTAINER
... """
>>> container = Component.from_ical(ics_data)
>>> container.name
'X-CONTAINER'
>>> event = container.subcomponents[0]
>>> event.name
'VEVENT'
>>> str(event["SUMMARY"])
'Event inside custom component'
Use cases#
Custom components extend the functionality beyond icalendar’s core. Use cases include the following:
vendor extensions and proprietary features
experimental or draft component types
legacy system support
data preservation during round-trips
The following example shows how to add a feature for a calendar, where an application supports dark mode in its theme.
>>> from icalendar import Calendar, Component
>>> cal = Calendar()
>>> cal.add("prodid", "-//My App//EN")
>>> cal.add("version", "2.0")
>>> # Create custom component using factory
>>> CustomSettings = Component.get_component_class("X-MYAPP-SETTINGS")
>>> custom = CustomSettings()
>>> custom.add("uid", "settings-1")
>>> custom.add("x-theme", "dark")
>>> cal.add_component(custom)
>>> b"BEGIN:X-MYAPP-SETTINGS" in cal.to_ical()
True