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

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

104 statements  

1"""vRequestStatus is a subclass of vText with convenience methods.""" 

2 

3from __future__ import annotations 

4 

5import re 

6from typing import TYPE_CHECKING, Any 

7from xml.etree.ElementTree import Element, SubElement 

8 

9from icalendar.error import InvalidCalendar, JCalParsingError 

10from icalendar.parser.parameter import Parameters 

11from icalendar.parser.xcal.wrapper import from_xcal_wrapper 

12 

13from .text import vText 

14 

15if TYPE_CHECKING: 

16 from icalendar.compatibility import Self 

17 from icalendar.parser.xcal.base import XCalParser 

18 

19CODE_REGEX = r"^[0-9]\.[0-9](?:\.[0-9])?$" 

20CODE_REGEX_PATTERN = re.compile(CODE_REGEX) 

21 

22REQUEST_STATUS_GRAMMAR = re.compile( 

23 r"^(?P<code>[0-9]\.[0-9](?:\.[0-9])?)" 

24 r";(?P<description>(?:\\;|[^;])*)" 

25 r"(?:;(?P<data>.*))?$", 

26 re.MULTILINE, 

27) 

28UNESCAPE = re.compile(r"\\(.)") 

29ESCAPE = re.compile(r"([\\,;])") 

30 

31 

32class vRequestStatus(vText): 

33 """A text specifically for REQUEST-STATUS. 

34 

35 See :rfc:`5545#section-3.8.8.3`, 

36 :rfc:`6321#appendix-A` in subsection 3.8.8.3 Request Status and, 

37 :rfc:`7265#section-3.4.1.2`. 

38 

39 Examples: 

40 

41 The attributes of a valid request status return the parsed data. 

42 

43 .. code-block:: pycon 

44 

45 >>> from icalendar.prop import vRequestStatus 

46 >>> r = vRequestStatus("2.0;Success") 

47 >>> r.code 

48 (2, 0) 

49 >>> r.description 

50 'Success' 

51 >>> r.data is None 

52 True 

53 

54 An invalid :attr:`code` is indicated by an empty tuple and 

55 an empty description. 

56 

57 .. code-block:: pycon 

58 

59 >>> r = vRequestStatus("2.10;invalid") 

60 >>> r.code 

61 () 

62 >>> r.description 

63 '' 

64 >>> r.data is None 

65 True 

66 

67 """ 

68 

69 __match: re.Match | None = None 

70 

71 def _get_match(self) -> re.Match | None: 

72 """Return a match for parsing.""" 

73 if self.__match is None: 

74 self.__match = REQUEST_STATUS_GRAMMAR.match(self) 

75 return self.__match 

76 

77 @property 

78 def code(self) -> tuple[int, int] | tuple[int, int, int] | tuple[()]: 

79 """Return the status code as a tuple. 

80 

81 Returns: 

82 A tuple of integers or ``()`` if the status code could not be parsed. 

83 """ 

84 s = self.code_string.split(".") 

85 if len(s) == 2: 

86 return int(s[0]), int(s[1]) 

87 if len(s) == 3: 

88 return int(s[0]), int(s[1]), int(s[2]) 

89 return () 

90 

91 @property 

92 def code_string(self) -> str: 

93 """Return the status code as a string. 

94 

95 Returns: 

96 The status string or ``""`` if the request status could not be parsed. 

97 """ 

98 match = self._get_match() 

99 if not match: 

100 return "" 

101 return match.group("code") 

102 

103 def _unescape(self, string: str): 

104 """Unescape a string.""" 

105 return UNESCAPE.sub(r"\1", string) 

106 

107 @property 

108 def description(self) -> str: 

109 """Return the description of the request status. 

110 

111 Returns: 

112 The description of the request status or ``""`` 

113 if the request status could not be parsed. 

114 """ 

115 match = self._get_match() 

116 if not match: 

117 return "" 

118 description: str = match.group("description") or "" 

119 return self._unescape(description) 

120 

121 @property 

122 def data(self) -> str | None: 

123 """Return the data of the request status. 

124 

125 Returns: 

126 The data of the request status or ``None``. 

127 """ 

128 match = self._get_match() 

129 if not match: 

130 return None 

131 data: str = match.group("data") 

132 if data is None: 

133 return None 

134 return self._unescape(data) 

135 

136 @classmethod 

137 def new( 

138 cls, 

139 code: tuple[int, int] | tuple[int, int, int] | str, 

140 description: str = "", 

141 data: str | None = None, 

142 *, 

143 params: dict[str, Any] | None = None, 

144 validate: bool = True, 

145 ) -> Self: 

146 """Create a new request status object. 

147 

148 Parameters: 

149 code: The status code. 

150 description: The description of the request status. 

151 data: The data of the request status. 

152 validate: Turn validation for the ``code`` string on and off. 

153 ``code`` tuples are always validated. 

154 

155 Raises: 

156 InvalidCalendar: If the code is not valid. 

157 TypeError: If the code is neither a tuple of integers nor string. 

158 

159 Returns: 

160 A new request status object. 

161 """ 

162 if not isinstance(code, (tuple, str)): 

163 raise TypeError("code must be tuple or string") 

164 if isinstance(code, str): 

165 code_string = code 

166 if validate and CODE_REGEX_PATTERN.match(code_string) is None: 

167 raise InvalidCalendar( 

168 f"code must be either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code_string!r}." 

169 ) 

170 else: 

171 if not all(isinstance(x, int) for x in code): 

172 raise TypeError( 

173 f"code must be a tuple of either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code!r}." 

174 ) 

175 if len(code) not in (2, 3) or not all(0 <= x <= 9 for x in code): 

176 raise InvalidCalendar( 

177 f"code must be either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code!r}." 

178 ) 

179 code_string = f"{code[0]}.{code[1]}" 

180 if len(code) == 3: 

181 code_string += f".{code[2]}" 

182 description = ESCAPE.sub(r"\\\1", description) 

183 request_status = f"{code_string};{description}" 

184 if data is not None: 

185 data = ESCAPE.sub(r"\\\1", data) 

186 request_status += f";{data}" 

187 return cls(request_status, params=params) 

188 

189 @classmethod 

190 def examples(cls) -> list[Self]: # pyright: ignore[reportIncompatibleMethodOverride] 

191 """Examples of vRequestStatus.""" 

192 return [ 

193 cls("2.0;Success"), 

194 cls("3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.org"), 

195 ] 

196 

197 def to_jcal(self, name: str) -> list: 

198 """The jCal representation of this property according to :rfc:`7265`.""" 

199 status = [self.code_string, self.description, self.data] 

200 if status[-1] is None: 

201 status = status[:-1] 

202 return [name, self.params.to_jcal(), self.VALUE.lower(), status] 

203 

204 @classmethod 

205 def from_jcal(cls, jcal_property: list) -> Self: 

206 """Parse jCal from :rfc:`7265`. 

207 

208 Parameters: 

209 jcal_property: The jCal property to parse. 

210 

211 Raises: 

212 ~error.JCalParsingError: If the provided jCal is invalid. 

213 """ 

214 JCalParsingError.validate_property(jcal_property, cls) 

215 status_list = jcal_property[3] 

216 JCalParsingError.validate_list_type(status_list, str, cls, 3) 

217 return cls.new( 

218 code=status_list[0] if len(status_list) > 0 else "", 

219 description=status_list[1] if len(status_list) > 1 else "", 

220 data=";".join(status_list[2:]) if len(status_list) > 2 else None, 

221 params=Parameters.from_jcal_property(jcal_property), 

222 validate=False, 

223 ) 

224 

225 def to_xcal(self, element: Element) -> None: 

226 """Add the xCal representation of this property according to :rfc:`6321`.""" 

227 self.params.to_xcal(element) 

228 SubElement(element, "code").text = self.code_string 

229 SubElement(element, "description").text = self.description 

230 if self.data is not None: 

231 SubElement(element, "data").text = self.data 

232 

233 @classmethod 

234 @from_xcal_wrapper 

235 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: 

236 """Parse xCal from :rfc:`6321`. 

237 

238 Parameters: 

239 xml: The XML to parse or a parser. 

240 

241 Raises: 

242 ~error.XCalParsingError: If the provided xCal is invalid. 

243 """ 

244 code = parser.parse_tag("code") 

245 description = parser.parse_tag("description") 

246 data = parser.parse_optional_tag("data") 

247 return cls.new( 

248 code.get_xsd_token(), 

249 description.get_xsd_string(), 

250 None if data is None else data.get_xsd_string(), 

251 params=params, 

252 validate=False, 

253 ) 

254 

255 

256__all__ = ["vRequestStatus"]