1"""Parsing error value preservation."""
2
3from __future__ import annotations
4
5from typing import TYPE_CHECKING, Any, ClassVar
6
7from icalendar.error import BrokenCalendarProperty
8from icalendar.parser_tools import DEFAULT_ENCODING
9from icalendar.prop.text import vText
10
11if TYPE_CHECKING:
12 from icalendar.compatibility import Self
13 from icalendar.parser import Parameters
14
15
16class vBroken(vText):
17 """Property that failed to parse, preserving raw value as text.
18
19 Represents property values that failed to parse with their expected
20 type. The raw iCalendar string is preserved for round-trip serialization.
21 """
22
23 default_value: ClassVar[str] = "TEXT"
24 __slots__ = ("expected_type", "parse_error", "property_name")
25
26 def __new__(
27 cls,
28 value: str | bytes,
29 encoding: str = DEFAULT_ENCODING,
30 /,
31 params: dict[str, Any] | None = None,
32 expected_type: str | None = None,
33 property_name: str | None = None,
34 parse_error: BaseException | None = None,
35 ) -> Self:
36 self = super().__new__(cls, value, encoding, params=params)
37 self.expected_type = expected_type
38 self.property_name = property_name
39 self.parse_error = parse_error
40 return self
41
42 def __getattr__(self, name: str):
43 """Raise BrokenCalendarProperty for attribute access.
44
45 Attributes like ``.dt`` that the expected type would have
46 raise :class:`~icalendar.error.BrokenCalendarProperty`
47 with the original parse error chained. Private attributes
48 (starting with ``_``) raise normal ``AttributeError`` to
49 preserve ``getattr(..., default)`` patterns.
50 """
51 if name.startswith("_"):
52 raise AttributeError(name)
53 raise BrokenCalendarProperty(
54 f"Cannot access {name!r} on broken property "
55 f"{self.property_name!r} (expected {self.expected_type!r}): "
56 f"{self.parse_error}"
57 ) from self.parse_error
58
59 def __repr__(self) -> str:
60 return (
61 f"{self.__class__.__name__}({str(self)!r}, "
62 f"expected_type={self.expected_type!r}, "
63 f"property_name={self.property_name!r})"
64 )
65
66 @classmethod
67 def from_parse_error(
68 cls,
69 raw_value: str,
70 params: Parameters,
71 property_name: str,
72 expected_type: str,
73 error: Exception,
74 ) -> Self:
75 """Create vBroken from parse failure."""
76 return cls(
77 raw_value,
78 params=params,
79 expected_type=expected_type,
80 property_name=property_name,
81 parse_error=error,
82 )
83
84 @classmethod
85 def examples(cls) -> list[Self]:
86 """Examples of vBroken."""
87 return [
88 cls(
89 "INVALID-DATE",
90 expected_type="date-time",
91 property_name="DTSTART",
92 parse_error=ValueError("Invalid date format"),
93 )
94 ]
95
96
97__all__ = ["vBroken"]