1"""A factory to create components."""
2
3from __future__ import annotations
4
5import re
6from typing import TYPE_CHECKING
7
8from icalendar.caselessdict import CaselessDict
9
10if TYPE_CHECKING:
11 from icalendar import Component
12
13
14class ComponentFactory(CaselessDict):
15 """Registered components from :rfc:`7953` and :rfc:`5545`.
16
17 To get a component, use this class as shown below.
18
19 .. code-block:: pycon
20
21 >>> from icalendar import ComponentFactory
22 >>> factory = ComponentFactory()
23 >>> event_class = factory.get_component_class('VEVENT')
24 >>> event_class()
25 VEVENT({})
26
27 Automatically creates custom component classes for unknown names (X-components,
28 IANA-components). Custom components are never dropped per :rfc:`5545`.
29
30 .. code-block:: pycon
31
32 >>> factory = ComponentFactory()
33 >>> custom_class = factory.get_component_class('X-VENDOR')
34 >>> custom_class()
35 X-VENDOR({})
36
37 If a component class is not yet supported, it can be either created using :meth:`get_component_class` or added manually as a subclass of :class:`~icalendar.cal.component.Component`.
38 See :doc:`/how-to/custom-components` for details.
39 """
40
41 def __init__(self, *args, **kwargs):
42 """Set keys to upper for initial dict."""
43 super().__init__(*args, **kwargs)
44 from icalendar.cal.alarm import Alarm
45 from icalendar.cal.availability import Availability
46 from icalendar.cal.available import Available
47 from icalendar.cal.calendar import Calendar
48 from icalendar.cal.event import Event
49 from icalendar.cal.free_busy import FreeBusy
50 from icalendar.cal.journal import Journal
51 from icalendar.cal.timezone import Timezone, TimezoneDaylight, TimezoneStandard
52 from icalendar.cal.todo import Todo
53
54 self.add_component_class(Calendar)
55 self.add_component_class(Event)
56 self.add_component_class(Todo)
57 self.add_component_class(Journal)
58 self.add_component_class(FreeBusy)
59 self.add_component_class(Timezone)
60 self.add_component_class(TimezoneStandard)
61 self.add_component_class(TimezoneDaylight)
62 self.add_component_class(Alarm)
63 self.add_component_class(Available)
64 self.add_component_class(Availability)
65
66 def add_component_class(self, cls: type[Component]) -> None:
67 """Add a component class to the factory.
68
69 Args:
70 cls: The component class to add.
71 """
72 self[cls.name] = cls
73
74 def get_component_class(self, name: str) -> type[Component]:
75 """Get the component class from the factory.
76
77 This also creates and adds the component class if it does not exist.
78
79 Args:
80 name: The name of the component, for example, ``"VCALENDAR"``.
81
82 Returns:
83 The registered component class.
84 """
85 component_class = self.get(name)
86 if component_class is None:
87 from icalendar.cal.component import Component
88
89 component_class = type(
90 re.sub(r"[^\w]+", "", name), (Component,), {"name": name.upper()}
91 )
92 self.add_component_class(component_class)
93 return component_class
94
95
96__all__ = ["ComponentFactory"]