1"""Factory class for all the property types."""
2
3from __future__ import annotations
4
5from typing import TYPE_CHECKING, Any, ClassVar
6
7from icalendar.caselessdict import CaselessDict
8from icalendar.prop.adr import vAdr
9from icalendar.prop.binary import vBinary
10from icalendar.prop.boolean import vBoolean
11from icalendar.prop.broken import vBroken
12from icalendar.prop.cal_address import vCalAddress
13from icalendar.prop.categories import vCategory
14from icalendar.prop.dt import (
15 vDate,
16 vDatetime,
17 vDDDLists,
18 vDDDTypes,
19 vDuration,
20 vPeriod,
21 vTime,
22 vUTCOffset,
23)
24from icalendar.prop.float import vFloat
25from icalendar.prop.geo import vGeo
26from icalendar.prop.inline import vInline
27from icalendar.prop.n import vN
28from icalendar.prop.org import vOrg
29from icalendar.prop.recur import vFrequency, vRecur, vWeekday
30from icalendar.prop.request_status import vRequestStatus
31from icalendar.prop.text import vText
32from icalendar.prop.uid import vUid
33from icalendar.prop.unknown import vUnknown
34from icalendar.prop.uri import vUri
35from icalendar.prop.xml import vXml
36from icalendar.prop.xml_reference import vXmlReference
37
38from .integer import vInt
39
40if TYPE_CHECKING:
41 from icalendar.prop import VPROPERTY
42
43
44class TypesFactory(CaselessDict):
45 """Factory for all value types defined in :rfc:`5545` and subsequent.
46
47 The value and parameter names don't overlap. So one factory is enough for
48 both kinds.
49 """
50
51 _instance: ClassVar[TypesFactory | None] = None
52
53 @staticmethod
54 def instance() -> TypesFactory:
55 """Return a singleton instance of this class."""
56 if TypesFactory._instance is None:
57 TypesFactory._instance = TypesFactory()
58 return TypesFactory._instance
59
60 def __init__(self, *args: Any, **kwargs: Any) -> None:
61 """Set keys to upper for initial dict"""
62 super().__init__(*args, **kwargs)
63 self.all_types = (
64 vBinary,
65 vBoolean,
66 vCalAddress,
67 vDDDLists,
68 vDDDTypes,
69 vDate,
70 vDatetime,
71 vDuration,
72 vFloat,
73 vFrequency,
74 vGeo,
75 vInline,
76 vInt,
77 vPeriod,
78 vRecur,
79 vText,
80 vTime,
81 vUTCOffset,
82 vUri,
83 vWeekday,
84 vCategory,
85 vAdr,
86 vN,
87 vOrg,
88 vUid,
89 vXmlReference,
90 vXml,
91 vUnknown,
92 vRequestStatus,
93 vBroken,
94 )
95 self["binary"] = vBinary
96 self["boolean"] = vBoolean
97 self["cal-address"] = vCalAddress
98 self["date"] = vDDDTypes
99 self["date-time"] = vDDDTypes
100 self["duration"] = vDDDTypes
101 self["float"] = vFloat
102 self["integer"] = vInt
103 self["period"] = vPeriod
104 self["recur"] = vRecur
105 self["text"] = vText
106 self["time"] = vTime
107 self["uri"] = vUri
108 self["utc-offset"] = vUTCOffset
109 self["geo"] = vGeo
110 self["inline"] = vInline
111 self["date-time-list"] = vDDDLists
112 self["categories"] = vCategory
113 self["request-status-text"] = vRequestStatus # RFC 5545 and 6321
114 self["adr"] = vAdr # RFC 6350 vCard
115 self["n"] = vN # RFC 6350 vCard
116 self["org"] = vOrg # RFC 6350 vCard
117 self["unknown"] = vUnknown # RFC 7265
118 self["uid"] = vUid # RFC 9253
119 self["xml-reference"] = vXmlReference # RFC 9253
120 self["xml-text"] = vXml # RFC 6321
121 self["x-broken"] = vBroken
122
123 #################################################
124 # Property types
125
126 # These are the default types
127 types_map = CaselessDict(
128 {
129 ####################################
130 # Property value types
131 # Calendar Properties
132 "calscale": "text",
133 "method": "text",
134 "prodid": "text",
135 "version": "text",
136 # Descriptive Component Properties
137 "attach": "uri",
138 "categories": "categories",
139 "class": "text",
140 # vCard Properties (RFC 6350)
141 "adr": "adr",
142 "n": "n",
143 "org": "org",
144 "comment": "text",
145 "description": "text",
146 "geo": "geo",
147 "location": "text",
148 "percent-complete": "integer",
149 "priority": "integer",
150 "resources": "text",
151 "status": "text",
152 "summary": "text",
153 # RFC 9253
154 # link should be uri, xml-reference or uid
155 # uri is likely most helpful if people forget to set VALUE
156 "link": "uri",
157 "concept": "uri",
158 "refid": "text",
159 # Date and Time Component Properties
160 "completed": "date-time",
161 "dtend": "date-time",
162 "due": "date-time",
163 "dtstart": "date-time",
164 "duration": "duration",
165 "freebusy": "period",
166 "transp": "text",
167 "refresh-interval": "duration", # RFC 7986
168 # Time Zone Component Properties
169 "tzid": "text",
170 "tzname": "text",
171 "tzoffsetfrom": "utc-offset",
172 "tzoffsetto": "utc-offset",
173 "tzurl": "uri",
174 # Relationship Component Properties
175 "attendee": "cal-address",
176 "contact": "text",
177 "organizer": "cal-address",
178 "recurrence-id": "date-time",
179 "related-to": "text",
180 "url": "uri",
181 "conference": "uri", # RFC 7986
182 "image": "unknown", # RFC 7986 has no default value type
183 "source": "uri",
184 "uid": "text",
185 # Recurrence Component Properties
186 "exdate": "date-time-list",
187 "exrule": "recur",
188 "rdate": "date-time-list",
189 "rrule": "recur",
190 # Alarm Component Properties
191 "action": "text",
192 "repeat": "integer",
193 "trigger": "duration",
194 "acknowledged": "date-time",
195 "proximity": "text", # RFC 9074
196 # Change Management Component Properties
197 "created": "date-time",
198 "dtstamp": "date-time",
199 "last-modified": "date-time",
200 "sequence": "integer",
201 # XML property (RFC 6321)
202 "xml": "xml-text",
203 # Miscellaneous Component Properties
204 "request-status": "request-status-text",
205 ####################################
206 # parameter types (luckily there is no name overlap)
207 "altrep": "uri",
208 "cn": "text",
209 "cutype": "text",
210 "delegated-from": "cal-address",
211 "delegated-to": "cal-address",
212 "dir": "uri",
213 "encoding": "text",
214 "fmttype": "text",
215 "fbtype": "text",
216 "language": "text",
217 "member": "cal-address",
218 "partstat": "text",
219 "range": "text",
220 "related": "text",
221 "reltype": "text",
222 "role": "text",
223 "rsvp": "boolean",
224 "sent-by": "cal-address",
225 "value": "text",
226 # rfc 9253 parameters
227 "label": "text",
228 "linkrel": "text",
229 "gap": "duration",
230 }
231 )
232
233 def for_property(self, name, value_param: str | None = None) -> type[VPROPERTY]:
234 """Returns the type class for a property or parameter.
235
236 Parameters:
237 name: Property or parameter name
238 value_param: Optional ``VALUE`` parameter, for example,
239 "DATE", "DATE-TIME", or other string.
240
241 Returns:
242 The appropriate value type class.
243 """
244 name = name.upper()
245 value_param = value_param.upper() if value_param else None
246 # RFC 7265's UNKNOWN type is always represented verbatim, even for
247 # properties such as RDATE/EXDATE that normally use list parsing.
248 if value_param and value_param == "UNKNOWN":
249 return self["unknown"]
250
251 # Special case: RDATE and EXDATE always use vDDDLists to support list values
252 # regardless of the VALUE parameter
253 if name in ("RDATE", "EXDATE") and (
254 value_param is None or value_param in vDDDTypes.VALUE_MAP
255 ):
256 return self["date-time-list"]
257 # Only use VALUE parameter for known properties that support multiple value
258 # types (like DTSTART, DTEND, etc. which can be DATE or DATE-TIME)
259 # For unknown/custom properties, always use the default type from types_map
260 if value_param and name in self.types_map and value_param in self:
261 # vCategory also has TEXT as VALUE.
262 # It should be preferred over TEXT
263 # This checks the case that the value type actually matches.
264 value_type: VPROPERTY = self[self.types_map[name]]
265 if value_type.default_value == value_param:
266 return value_type
267 return self[value_param]
268 if value_param and (value_param in self) and value_param != "IMAGE":
269 return self[value_param]
270
271 return self[self.types_map.get(name, "unknown")]
272
273 #: jCal value types defined by :rfc:`7265` that are used verbatim as the
274 #: type field. Internal type keys outside of this set (for example
275 #: ``"categories"`` or ``"date-time-list"``) are mapped to their concrete
276 #: jCal type via the value class.
277 _jcal_value_types = frozenset(
278 {
279 "binary",
280 "boolean",
281 "cal-address",
282 "date",
283 "date-time",
284 "duration",
285 "float",
286 "integer",
287 "period",
288 "recur",
289 "text",
290 "time",
291 "uri",
292 "utc-offset",
293 "unknown",
294 }
295 )
296
297 def default_value_type(self, name: str) -> str:
298 """Return the default jCal value type for a property.
299
300 The result is a lowercase :rfc:`7265` value type, for example
301 ``"date-time"`` for ``DTSTART`` or ``"duration"`` for ``TRIGGER``.
302 This is the value type a property uses when no explicit ``VALUE``
303 parameter is given, so it tells :meth:`Component.from_jcal <icalendar.cal.component.Component.from_jcal>` whether a
304 ``VALUE`` parameter has to be restored from the jCal type field.
305
306 Parameters:
307 name: Property name.
308
309 Returns:
310 The default jCal value type as a lowercase string.
311 """
312 internal = self.types_map.get(name, "unknown").lower()
313 if internal in self._jcal_value_types:
314 return internal
315 # Internal keys such as ``categories`` or ``date-time-list`` are not
316 # jCal value types themselves; ask the value class for the type it
317 # actually serialises to.
318 return self[internal].default_value.lower()
319
320 def to_ical(self, name, value):
321 """Encodes a named value from a primitive python type to an icalendar
322 encoded string.
323 """
324 type_class = self.for_property(name)
325 return type_class(value).to_ical()
326
327 def from_ical(self, name, value):
328 """Decodes a named property or parameter value from an icalendar
329 encoded string to a primitive python type.
330 """
331 type_class = self.for_property(name)
332 return type_class.from_ical(value)
333
334
335__all__ = ["TypesFactory"]