Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/factory.py: 82%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

92 statements  

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.text import vText 

31from icalendar.prop.uid import vUid 

32from icalendar.prop.unknown import vUnknown 

33from icalendar.prop.uri import vUri 

34from icalendar.prop.xml_reference import vXmlReference 

35 

36from .integer import vInt 

37 

38if TYPE_CHECKING: 

39 from icalendar.prop import VPROPERTY 

40 

41 

42class TypesFactory(CaselessDict): 

43 """Factory for all value types defined in :rfc:`5545` and subsequent. 

44 

45 The value and parameter names don't overlap. So one factory is enough for 

46 both kinds. 

47 """ 

48 

49 _instance: ClassVar[TypesFactory | None] = None 

50 

51 @staticmethod 

52 def instance() -> TypesFactory: 

53 """Return a singleton instance of this class.""" 

54 if TypesFactory._instance is None: 

55 TypesFactory._instance = TypesFactory() 

56 return TypesFactory._instance 

57 

58 def __init__(self, *args: Any, **kwargs: Any) -> None: 

59 """Set keys to upper for initial dict""" 

60 super().__init__(*args, **kwargs) 

61 self.all_types = ( 

62 vBinary, 

63 vBoolean, 

64 vCalAddress, 

65 vDDDLists, 

66 vDDDTypes, 

67 vDate, 

68 vDatetime, 

69 vDuration, 

70 vFloat, 

71 vFrequency, 

72 vGeo, 

73 vInline, 

74 vInt, 

75 vPeriod, 

76 vRecur, 

77 vText, 

78 vTime, 

79 vUTCOffset, 

80 vUri, 

81 vWeekday, 

82 vCategory, 

83 vAdr, 

84 vN, 

85 vOrg, 

86 vUid, 

87 vXmlReference, 

88 vUnknown, 

89 vBroken, 

90 ) 

91 self["binary"] = vBinary 

92 self["boolean"] = vBoolean 

93 self["cal-address"] = vCalAddress 

94 self["date"] = vDDDTypes 

95 self["date-time"] = vDDDTypes 

96 self["duration"] = vDDDTypes 

97 self["float"] = vFloat 

98 self["integer"] = vInt 

99 self["period"] = vPeriod 

100 self["recur"] = vRecur 

101 self["text"] = vText 

102 self["time"] = vTime 

103 self["uri"] = vUri 

104 self["utc-offset"] = vUTCOffset 

105 self["geo"] = vGeo 

106 self["inline"] = vInline 

107 self["date-time-list"] = vDDDLists 

108 self["categories"] = vCategory 

109 self["adr"] = vAdr # RFC 6350 vCard 

110 self["n"] = vN # RFC 6350 vCard 

111 self["org"] = vOrg # RFC 6350 vCard 

112 self["unknown"] = vUnknown # RFC 7265 

113 self["uid"] = vUid # RFC 9253 

114 self["xml-reference"] = vXmlReference # RFC 9253 

115 self["x-broken"] = vBroken 

116 

117 ################################################# 

118 # Property types 

119 

120 # These are the default types 

121 types_map = CaselessDict( 

122 { 

123 #################################### 

124 # Property value types 

125 # Calendar Properties 

126 "calscale": "text", 

127 "method": "text", 

128 "prodid": "text", 

129 "version": "text", 

130 # Descriptive Component Properties 

131 "attach": "uri", 

132 "categories": "categories", 

133 "class": "text", 

134 # vCard Properties (RFC 6350) 

135 "adr": "adr", 

136 "n": "n", 

137 "org": "org", 

138 "comment": "text", 

139 "description": "text", 

140 "geo": "geo", 

141 "location": "text", 

142 "percent-complete": "integer", 

143 "priority": "integer", 

144 "resources": "text", 

145 "status": "text", 

146 "summary": "text", 

147 # RFC 9253 

148 # link should be uri, xml-reference or uid 

149 # uri is likely most helpful if people forget to set VALUE 

150 "link": "uri", 

151 "concept": "uri", 

152 "refid": "text", 

153 # Date and Time Component Properties 

154 "completed": "date-time", 

155 "dtend": "date-time", 

156 "due": "date-time", 

157 "dtstart": "date-time", 

158 "duration": "duration", 

159 "freebusy": "period", 

160 "transp": "text", 

161 "refresh-interval": "duration", # RFC 7986 

162 # Time Zone Component Properties 

163 "tzid": "text", 

164 "tzname": "text", 

165 "tzoffsetfrom": "utc-offset", 

166 "tzoffsetto": "utc-offset", 

167 "tzurl": "uri", 

168 # Relationship Component Properties 

169 "attendee": "cal-address", 

170 "contact": "text", 

171 "organizer": "cal-address", 

172 "recurrence-id": "date-time", 

173 "related-to": "text", 

174 "url": "uri", 

175 "conference": "uri", # RFC 7986 

176 "image": "unknown", # RFC 7986 has no default value type 

177 "source": "uri", 

178 "uid": "text", 

179 # Recurrence Component Properties 

180 "exdate": "date-time-list", 

181 "exrule": "recur", 

182 "rdate": "date-time-list", 

183 "rrule": "recur", 

184 # Alarm Component Properties 

185 "action": "text", 

186 "repeat": "integer", 

187 "trigger": "duration", 

188 "acknowledged": "date-time", 

189 "proximity": "text", # RFC 9074 

190 # Change Management Component Properties 

191 "created": "date-time", 

192 "dtstamp": "date-time", 

193 "last-modified": "date-time", 

194 "sequence": "integer", 

195 # Miscellaneous Component Properties 

196 "request-status": "text", 

197 #################################### 

198 # parameter types (luckily there is no name overlap) 

199 "altrep": "uri", 

200 "cn": "text", 

201 "cutype": "text", 

202 "delegated-from": "cal-address", 

203 "delegated-to": "cal-address", 

204 "dir": "uri", 

205 "encoding": "text", 

206 "fmttype": "text", 

207 "fbtype": "text", 

208 "language": "text", 

209 "member": "cal-address", 

210 "partstat": "text", 

211 "range": "text", 

212 "related": "text", 

213 "reltype": "text", 

214 "role": "text", 

215 "rsvp": "boolean", 

216 "sent-by": "cal-address", 

217 "value": "text", 

218 # rfc 9253 parameters 

219 "label": "text", 

220 "linkrel": "text", 

221 "gap": "duration", 

222 } 

223 ) 

224 

225 def for_property(self, name, value_param: str | None = None) -> type[VPROPERTY]: 

226 """Returns the type class for a property or parameter. 

227 

228 Parameters: 

229 name: Property or parameter name 

230 value_param: Optional ``VALUE`` parameter, for example, 

231 "DATE", "DATE-TIME", or other string. 

232 

233 Returns: 

234 The appropriate value type class. 

235 """ 

236 name = name.upper() 

237 value_param = value_param.upper() if value_param else None 

238 # RFC 7265's UNKNOWN type is always represented verbatim, even for 

239 # properties such as RDATE/EXDATE that normally use list parsing. 

240 if value_param and value_param == "UNKNOWN": 

241 return self["unknown"] 

242 

243 # Special case: RDATE and EXDATE always use vDDDLists to support list values 

244 # regardless of the VALUE parameter 

245 if name in ("RDATE", "EXDATE") and ( 

246 value_param is None or value_param in vDDDTypes.VALUE_MAP 

247 ): 

248 return self["date-time-list"] 

249 # Only use VALUE parameter for known properties that support multiple value 

250 # types (like DTSTART, DTEND, etc. which can be DATE or DATE-TIME) 

251 # For unknown/custom properties, always use the default type from types_map 

252 if value_param and name in self.types_map and value_param in self: 

253 # vCategory also has TEXT as VALUE. 

254 # It should be preferred over TEXT 

255 # This checks the case that the value type actually matches. 

256 value_type: VPROPERTY = self[self.types_map[name]] 

257 if value_type.default_value == value_param: 

258 return value_type 

259 return self[value_param] 

260 if value_param and (value_param in self) and value_param != "IMAGE": 

261 return self[value_param] 

262 

263 return self[self.types_map.get(name, "unknown")] 

264 

265 #: jCal value types defined by :rfc:`7265` that are used verbatim as the 

266 #: type field. Internal type keys outside of this set (for example 

267 #: ``"categories"`` or ``"date-time-list"``) are mapped to their concrete 

268 #: jCal type via the value class. 

269 _jcal_value_types = frozenset( 

270 { 

271 "binary", 

272 "boolean", 

273 "cal-address", 

274 "date", 

275 "date-time", 

276 "duration", 

277 "float", 

278 "integer", 

279 "period", 

280 "recur", 

281 "text", 

282 "time", 

283 "uri", 

284 "utc-offset", 

285 "unknown", 

286 } 

287 ) 

288 

289 def default_value_type(self, name: str) -> str: 

290 """Return the default jCal value type for a property. 

291 

292 The result is a lowercase :rfc:`7265` value type, for example 

293 ``"date-time"`` for ``DTSTART`` or ``"duration"`` for ``TRIGGER``. 

294 This is the value type a property uses when no explicit ``VALUE`` 

295 parameter is given, so it tells :func:`Component.from_jcal` whether a 

296 ``VALUE`` parameter has to be restored from the jCal type field. 

297 

298 Parameters: 

299 name: Property name. 

300 

301 Returns: 

302 The default jCal value type as a lowercase string. 

303 """ 

304 internal = self.types_map.get(name.lower(), "unknown") 

305 if internal in self._jcal_value_types: 

306 return internal 

307 # Internal keys such as ``categories`` or ``date-time-list`` are not 

308 # jCal value types themselves; ask the value class for the type it 

309 # actually serialises to. 

310 try: 

311 return self[internal].examples()[0].VALUE.lower() 

312 except (KeyError, IndexError, AttributeError): 

313 return "unknown" 

314 

315 def to_ical(self, name, value): 

316 """Encodes a named value from a primitive python type to an icalendar 

317 encoded string. 

318 """ 

319 type_class = self.for_property(name) 

320 return type_class(value).to_ical() 

321 

322 def from_ical(self, name, value): 

323 """Decodes a named property or parameter value from an icalendar 

324 encoded string to a primitive python type. 

325 """ 

326 type_class = self.for_property(name) 

327 return type_class.from_ical(value) 

328 

329 

330__all__ = ["TypesFactory"]