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 If a component class is not yet supported, it can be either created
28 using :meth:`get_component_class` or added manually as a subclass of
29 :class:`~icalendar.Component`.
30 """
31
32 def __init__(self, *args, **kwargs):
33 """Set keys to upper for initial dict."""
34 super().__init__(*args, **kwargs)
35 from icalendar.cal.alarm import Alarm
36 from icalendar.cal.availability import Availability
37 from icalendar.cal.available import Available
38 from icalendar.cal.calendar import Calendar
39 from icalendar.cal.event import Event
40 from icalendar.cal.free_busy import FreeBusy
41 from icalendar.cal.journal import Journal
42 from icalendar.cal.timezone import Timezone, TimezoneDaylight, TimezoneStandard
43 from icalendar.cal.todo import Todo
44
45 self.add_component_class(Calendar)
46 self.add_component_class(Event)
47 self.add_component_class(Todo)
48 self.add_component_class(Journal)
49 self.add_component_class(FreeBusy)
50 self.add_component_class(Timezone)
51 self.add_component_class(TimezoneStandard)
52 self.add_component_class(TimezoneDaylight)
53 self.add_component_class(Alarm)
54 self.add_component_class(Available)
55 self.add_component_class(Availability)
56
57 def add_component_class(self, cls: type[Component]) -> None:
58 """Add a component class to the factory.
59
60 Args:
61 cls: The component class to add.
62 """
63 self[cls.name] = cls
64
65 def get_component_class(self, name: str) -> type[Component]:
66 """Get the component class from the factory.
67
68 This also creates and adds the component class if it does not exist.
69
70 Args:
71 name: The name of the component, for example, ``"VCALENDAR"``.
72
73 Returns:
74 The registered component class.
75 """
76 component_class = self.get(name)
77 if component_class is None:
78 from icalendar.cal.component import Component
79
80 component_class = type(
81 re.sub(r"[^\w]+", "", name), (Component,), {"name": name.upper()}
82 )
83 self.add_component_class(component_class)
84 return component_class
85
86
87__all__ = ["ComponentFactory"]