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