Source code for icalendar.prop.request_status

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

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any
from xml.etree.ElementTree import Element, SubElement

from icalendar.error import InvalidCalendar, JCalParsingError
from icalendar.parser.parameter import Parameters
from icalendar.parser.xcal.wrapper import from_xcal_wrapper

from .text import vText

if TYPE_CHECKING:
    from icalendar.compatibility import Self
    from icalendar.parser.xcal.base import XCalParser

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

REQUEST_STATUS_GRAMMAR = re.compile(
    r"^(?P<code>[0-9]\.[0-9](?:\.[0-9])?)"
    r";(?P<description>(?:\\;|[^;])*)"
    r"(?:;(?P<data>.*))?$",
    re.MULTILINE,
)
UNESCAPE = re.compile(r"\\(.)")
ESCAPE = re.compile(r"([\\,;])")


[docs] class vRequestStatus(vText): """A text specifically for REQUEST-STATUS. See :rfc:`5545#section-3.8.8.3`, :rfc:`6321#appendix-A` in subsection 3.8.8.3 Request Status and, :rfc:`7265#section-3.4.1.2`. Examples: The attributes of a valid request status return the parsed data. .. code-block:: pycon >>> from icalendar.prop import vRequestStatus >>> r = vRequestStatus("2.0;Success") >>> r.code (2, 0) >>> r.description 'Success' >>> r.data is None True An invalid :attr:`code` is indicated by an empty tuple and an empty description. .. code-block:: pycon >>> r = vRequestStatus("2.10;invalid") >>> r.code () >>> r.description '' >>> r.data is None True """ __match: re.Match | None = None def _get_match(self) -> re.Match | None: """Return a match for parsing.""" if self.__match is None: self.__match = REQUEST_STATUS_GRAMMAR.match(self) return self.__match @property def code(self) -> tuple[int, int] | tuple[int, int, int] | tuple[()]: """Return the status code as a tuple. Returns: A tuple of integers or ``()`` if the status code could not be parsed. """ s = self.code_string.split(".") if len(s) == 2: return int(s[0]), int(s[1]) if len(s) == 3: return int(s[0]), int(s[1]), int(s[2]) return () @property def code_string(self) -> str: """Return the status code as a string. Returns: The status string or ``""`` if the request status could not be parsed. """ match = self._get_match() if not match: return "" return match.group("code") def _unescape(self, string: str): """Unescape a string.""" return UNESCAPE.sub(r"\1", string) @property def description(self) -> str: """Return the description of the request status. Returns: The description of the request status or ``""`` if the request status could not be parsed. """ match = self._get_match() if not match: return "" description: str = match.group("description") or "" return self._unescape(description) @property def data(self) -> str | None: """Return the data of the request status. Returns: The data of the request status or ``None``. """ match = self._get_match() if not match: return None data: str = match.group("data") if data is None: return None return self._unescape(data)
[docs] @classmethod def new( cls, code: tuple[int, int] | tuple[int, int, int] | str, description: str = "", data: str | None = None, *, params: dict[str, Any] | None = None, validate: bool = True, ) -> Self: """Create a new request status object. Parameters: code: The status code. description: The description of the request status. data: The data of the request status. validate: Turn validation for the ``code`` string on and off. ``code`` tuples are always validated. Raises: InvalidCalendar: If the code is not valid. TypeError: If the code is neither a tuple of integers nor string. Returns: A new request status object. """ if not isinstance(code, (tuple, str)): raise TypeError("code must be tuple or string") if isinstance(code, str): code_string = code if validate and CODE_REGEX_PATTERN.match(code_string) is None: raise InvalidCalendar( f"code must be either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code_string!r}." ) else: if not all(isinstance(x, int) for x in code): raise TypeError( f"code must be a tuple of either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code!r}." ) if len(code) not in (2, 3) or not all(0 <= x <= 9 for x in code): raise InvalidCalendar( f"code must be either 2 or 3 numbers, each in the range 0-9, separated by `.`. Got {code!r}." ) code_string = f"{code[0]}.{code[1]}" if len(code) == 3: code_string += f".{code[2]}" description = ESCAPE.sub(r"\\\1", description) request_status = f"{code_string};{description}" if data is not None: data = ESCAPE.sub(r"\\\1", data) request_status += f";{data}" return cls(request_status, params=params)
[docs] @classmethod def examples(cls) -> list[Self]: # pyright: ignore[reportIncompatibleMethodOverride] """Examples of vRequestStatus.""" return [ cls("2.0;Success"), cls("3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.org"), ]
[docs] def to_jcal(self, name: str) -> list: """The jCal representation of this property according to :rfc:`7265`.""" status = [self.code_string, self.description, self.data] if status[-1] is None: status = status[:-1] return [name, self.params.to_jcal(), self.VALUE.lower(), status]
[docs] @classmethod def from_jcal(cls, jcal_property: list) -> Self: """Parse jCal from :rfc:`7265`. Parameters: jcal_property: The jCal property to parse. Raises: ~error.JCalParsingError: If the provided jCal is invalid. """ JCalParsingError.validate_property(jcal_property, cls) status_list = jcal_property[3] JCalParsingError.validate_list_type(status_list, str, cls, 3) return cls.new( code=status_list[0] if len(status_list) > 0 else "", description=status_list[1] if len(status_list) > 1 else "", data=";".join(status_list[2:]) if len(status_list) > 2 else None, params=Parameters.from_jcal_property(jcal_property), validate=False, )
[docs] def to_xcal(self, element: Element) -> None: """Add the xCal representation of this property according to :rfc:`6321`.""" self.params.to_xcal(element) SubElement(element, "code").text = self.code_string SubElement(element, "description").text = self.description if self.data is not None: SubElement(element, "data").text = self.data
[docs] @classmethod @from_xcal_wrapper def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: """Parse xCal from :rfc:`6321`. Parameters: xml: The XML to parse or a parser. Raises: ~error.XCalParsingError: If the provided xCal is invalid. """ code = parser.parse_tag("code") description = parser.parse_tag("description") data = parser.parse_optional_tag("data") return cls.new( code.get_xsd_token(), description.get_xsd_string(), None if data is None else data.get_xsd_string(), params=params, validate=False, )
__all__ = ["vRequestStatus"]