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
38 using :meth:`get_component_class` or added manually as a subclass of
39 :class:`~icalendar.cal.component.Component`.
40 See :doc:`/how-to/custom-components` for details.
41 """
42
43 def __init__(self, *args, **kwargs):
44 """Set keys to upper for initial dict."""
45 super().__init__(*args, **kwargs)
46 from icalendar.cal.alarm import Alarm
47 from icalendar.cal.availability import Availability
48 from icalendar.cal.available import Available
49 from icalendar.cal.calendar import Calendar
50 from icalendar.cal.event import Event
51 from icalendar.cal.free_busy import FreeBusy
52 from icalendar.cal.journal import Journal
53 from icalendar.cal.timezone import (
54 Timezone,
55 TimezoneDaylight,
56 TimezoneStandard,
57 )
58 from icalendar.cal.todo import Todo
59
60 self.add_component_class(Calendar)
61 self.add_component_class(Event)
62 self.add_component_class(Todo)
63 self.add_component_class(Journal)
64 self.add_component_class(FreeBusy)
65 self.add_component_class(Timezone)
66 self.add_component_class(TimezoneStandard)
67 self.add_component_class(TimezoneDaylight)
68 self.add_component_class(Alarm)
69 self.add_component_class(Available)
70 self.add_component_class(Availability)
71
72 def add_component_class(self, cls: type[Component]) -> None:
73 """Add a component class to the factory.
74
75 Parameters:
76 cls: The component class to add.
77 """
78 self[cls.name] = cls
79
80 def get_component_class(self, name: str) -> type[Component]:
81 """Get the component class from the factory.
82
83 This also creates and adds the component class if it does not exist.
84
85 Parameters:
86 name: The name of the component, for example, ``"VCALENDAR"``.
87
88 Returns:
89 The registered component class.
90 """
91 component_class = self.get(name)
92 if component_class is None:
93 from icalendar.cal.component import Component
94
95 component_class = type(
96 re.sub(r"[^\w]+", "", name), (Component,), {"name": name.upper()}
97 )
98 self.add_component_class(component_class)
99 return component_class
100
101
102__all__ = ["ComponentFactory"]