"""Factory class for all the property types."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, ClassVar
from icalendar.caselessdict import CaselessDict
from icalendar.prop.adr import vAdr
from icalendar.prop.binary import vBinary
from icalendar.prop.boolean import vBoolean
from icalendar.prop.broken import vBroken
from icalendar.prop.cal_address import vCalAddress
from icalendar.prop.categories import vCategory
from icalendar.prop.dt import (
vDate,
vDatetime,
vDDDLists,
vDDDTypes,
vDuration,
vPeriod,
vTime,
vUTCOffset,
)
from icalendar.prop.float import vFloat
from icalendar.prop.geo import vGeo
from icalendar.prop.inline import vInline
from icalendar.prop.n import vN
from icalendar.prop.org import vOrg
from icalendar.prop.recur import vFrequency, vRecur, vWeekday
from icalendar.prop.request_status import vRequestStatus
from icalendar.prop.text import vText
from icalendar.prop.uid import vUid
from icalendar.prop.unknown import vUnknown
from icalendar.prop.uri import vUri
from icalendar.prop.xml import vXml
from icalendar.prop.xml_reference import vXmlReference
from .integer import vInt
if TYPE_CHECKING:
from icalendar.prop import VPROPERTY
[docs]
class TypesFactory(CaselessDict):
"""Factory for all value types defined in :rfc:`5545` and subsequent.
The value and parameter names don't overlap. So one factory is enough for
both kinds.
"""
_instance: ClassVar[TypesFactory | None] = None
[docs]
@staticmethod
def instance() -> TypesFactory:
"""Return a singleton instance of this class."""
if TypesFactory._instance is None:
TypesFactory._instance = TypesFactory()
return TypesFactory._instance
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Set keys to upper for initial dict"""
super().__init__(*args, **kwargs)
self.all_types = (
vBinary,
vBoolean,
vCalAddress,
vDDDLists,
vDDDTypes,
vDate,
vDatetime,
vDuration,
vFloat,
vFrequency,
vGeo,
vInline,
vInt,
vPeriod,
vRecur,
vText,
vTime,
vUTCOffset,
vUri,
vWeekday,
vCategory,
vAdr,
vN,
vOrg,
vUid,
vXmlReference,
vXml,
vUnknown,
vRequestStatus,
vBroken,
)
self["binary"] = vBinary
self["boolean"] = vBoolean
self["cal-address"] = vCalAddress
self["date"] = vDDDTypes
self["date-time"] = vDDDTypes
self["duration"] = vDDDTypes
self["float"] = vFloat
self["integer"] = vInt
self["period"] = vPeriod
self["recur"] = vRecur
self["text"] = vText
self["time"] = vTime
self["uri"] = vUri
self["utc-offset"] = vUTCOffset
self["geo"] = vGeo
self["inline"] = vInline
self["date-time-list"] = vDDDLists
self["categories"] = vCategory
self["request-status-text"] = vRequestStatus # RFC 5545 and 6321
self["adr"] = vAdr # RFC 6350 vCard
self["n"] = vN # RFC 6350 vCard
self["org"] = vOrg # RFC 6350 vCard
self["unknown"] = vUnknown # RFC 7265
self["uid"] = vUid # RFC 9253
self["xml-reference"] = vXmlReference # RFC 9253
self["xml-text"] = vXml # RFC 6321
self["x-broken"] = vBroken
#################################################
# Property types
# These are the default types
types_map = CaselessDict(
{
####################################
# Property value types
# Calendar Properties
"calscale": "text",
"method": "text",
"prodid": "text",
"version": "text",
# Descriptive Component Properties
"attach": "uri",
"categories": "categories",
"class": "text",
# vCard Properties (RFC 6350)
"adr": "adr",
"n": "n",
"org": "org",
"comment": "text",
"description": "text",
"geo": "geo",
"location": "text",
"percent-complete": "integer",
"priority": "integer",
"resources": "text",
"status": "text",
"summary": "text",
# RFC 9253
# link should be uri, xml-reference or uid
# uri is likely most helpful if people forget to set VALUE
"link": "uri",
"concept": "uri",
"refid": "text",
# Date and Time Component Properties
"completed": "date-time",
"dtend": "date-time",
"due": "date-time",
"dtstart": "date-time",
"duration": "duration",
"freebusy": "period",
"transp": "text",
"refresh-interval": "duration", # RFC 7986
# Time Zone Component Properties
"tzid": "text",
"tzname": "text",
"tzoffsetfrom": "utc-offset",
"tzoffsetto": "utc-offset",
"tzurl": "uri",
# Relationship Component Properties
"attendee": "cal-address",
"contact": "text",
"organizer": "cal-address",
"recurrence-id": "date-time",
"related-to": "text",
"url": "uri",
"conference": "uri", # RFC 7986
"image": "unknown", # RFC 7986 has no default value type
"source": "uri",
"uid": "text",
# Recurrence Component Properties
"exdate": "date-time-list",
"exrule": "recur",
"rdate": "date-time-list",
"rrule": "recur",
# Alarm Component Properties
"action": "text",
"repeat": "integer",
"trigger": "duration",
"acknowledged": "date-time",
"proximity": "text", # RFC 9074
# Change Management Component Properties
"created": "date-time",
"dtstamp": "date-time",
"last-modified": "date-time",
"sequence": "integer",
# XML property (RFC 6321)
"xml": "xml-text",
# Miscellaneous Component Properties
"request-status": "request-status-text",
####################################
# parameter types (luckily there is no name overlap)
"altrep": "uri",
"cn": "text",
"cutype": "text",
"delegated-from": "cal-address",
"delegated-to": "cal-address",
"dir": "uri",
"encoding": "text",
"fmttype": "text",
"fbtype": "text",
"language": "text",
"member": "cal-address",
"partstat": "text",
"range": "text",
"related": "text",
"reltype": "text",
"role": "text",
"rsvp": "boolean",
"sent-by": "cal-address",
"value": "text",
# rfc 9253 parameters
"label": "text",
"linkrel": "text",
"gap": "duration",
}
)
[docs]
def for_property(self, name, value_param: str | None = None) -> type[VPROPERTY]:
"""Returns the type class for a property or parameter.
Parameters:
name: Property or parameter name
value_param: Optional ``VALUE`` parameter, for example,
"DATE", "DATE-TIME", or other string.
Returns:
The appropriate value type class.
"""
name = name.upper()
value_param = value_param.upper() if value_param else None
# RFC 7265's UNKNOWN type is always represented verbatim, even for
# properties such as RDATE/EXDATE that normally use list parsing.
if value_param and value_param == "UNKNOWN":
return self["unknown"]
# Special case: RDATE and EXDATE always use vDDDLists to support list values
# regardless of the VALUE parameter
if name in ("RDATE", "EXDATE") and (
value_param is None or value_param in vDDDTypes.VALUE_MAP
):
return self["date-time-list"]
# Only use VALUE parameter for known properties that support multiple value
# types (like DTSTART, DTEND, etc. which can be DATE or DATE-TIME)
# For unknown/custom properties, always use the default type from types_map
if value_param and name in self.types_map and value_param in self:
# vCategory also has TEXT as VALUE.
# It should be preferred over TEXT
# This checks the case that the value type actually matches.
value_type: VPROPERTY = self[self.types_map[name]]
if value_type.default_value == value_param:
return value_type
return self[value_param]
if value_param and (value_param in self) and value_param != "IMAGE":
return self[value_param]
return self[self.types_map.get(name, "unknown")]
#: jCal value types defined by :rfc:`7265` that are used verbatim as the
#: type field. Internal type keys outside of this set (for example
#: ``"categories"`` or ``"date-time-list"``) are mapped to their concrete
#: jCal type via the value class.
_jcal_value_types = frozenset(
{
"binary",
"boolean",
"cal-address",
"date",
"date-time",
"duration",
"float",
"integer",
"period",
"recur",
"text",
"time",
"uri",
"utc-offset",
"unknown",
}
)
[docs]
def default_value_type(self, name: str) -> str:
"""Return the default jCal value type for a property.
The result is a lowercase :rfc:`7265` value type, for example
``"date-time"`` for ``DTSTART`` or ``"duration"`` for ``TRIGGER``.
This is the value type a property uses when no explicit ``VALUE``
parameter is given, so it tells :meth:`Component.from_jcal <icalendar.cal.component.Component.from_jcal>` whether a
``VALUE`` parameter has to be restored from the jCal type field.
Parameters:
name: Property name.
Returns:
The default jCal value type as a lowercase string.
"""
internal = self.types_map.get(name, "unknown").lower()
if internal in self._jcal_value_types:
return internal
# Internal keys such as ``categories`` or ``date-time-list`` are not
# jCal value types themselves; ask the value class for the type it
# actually serialises to.
return self[internal].default_value.lower()
[docs]
def to_ical(self, name, value):
"""Encodes a named value from a primitive python type to an icalendar
encoded string.
"""
type_class = self.for_property(name)
return type_class(value).to_ical()
[docs]
def from_ical(self, name, value):
"""Decodes a named property or parameter value from an icalendar
encoded string to a primitive python type.
"""
type_class = self.for_property(name)
return type_class.from_ical(value)
__all__ = ["TypesFactory"]