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

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

70 statements  

1"""ADR property of :rfc:`6350`.""" 

2from typing import Any, ClassVar, NamedTuple 

3 

4from icalendar.compatibility import Self 

5from icalendar.error import JCalParsingError 

6from icalendar.parser import Parameters 

7from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode 

8 

9 

10class AdrFields(NamedTuple): 

11 """Named fields for vCard ADR (Address) property per :rfc:`6350#section-6.3.1`. 

12 

13 Provides named access to the seven address components. 

14 """ 

15 

16 po_box: str 

17 """Post office box.""" 

18 extended: str 

19 """Extended address (e.g., apartment or suite number).""" 

20 street: str 

21 """Street address.""" 

22 locality: str 

23 """Locality (e.g., city).""" 

24 region: str 

25 """Region (e.g., state or province).""" 

26 postal_code: str 

27 """Postal code.""" 

28 country: str 

29 """Country name (full name).""" 

30 

31 

32class vAdr: 

33 """vCard ADR (Address) structured property per :rfc:`6350#section-6.3.1`. 

34 

35 The ADR property represents a delivery address as a single text value. 

36 The structured type value consists of a sequence of seven address components. 

37 The component values must be specified in their corresponding position. 

38 

39 - post office box 

40 - extended address (e.g., apartment or suite number) 

41 - street address 

42 - locality (e.g., city) 

43 - region (e.g., state or province) 

44 - postal code 

45 - country name (full name) 

46 

47 When a component value is missing, the associated component separator MUST still be specified. 

48 

49 Semicolons are field separators and are NOT escaped. 

50 Commas and backslashes within field values ARE escaped per :rfc:`6350`. 

51 

52 Examples: 

53 .. code-block:: pycon 

54 

55 >>> from icalendar.prop import vAdr 

56 >>> adr = vAdr(("", "", "123 Main St", "Springfield", "IL", "62701", "USA")) 

57 >>> adr.to_ical() 

58 b';;123 Main St;Springfield;IL;62701;USA' 

59 >>> vAdr.from_ical(";;123 Main St;Springfield;IL;62701;USA") 

60 AdrFields(po_box='', extended='', street='123 Main St', locality='Springfield', region='IL', postal_code='62701', country='USA') 

61 """ 

62 

63 default_value: ClassVar[str] = "TEXT" 

64 params: Parameters 

65 fields: AdrFields 

66 

67 def __init__( 

68 self, 

69 fields: tuple[str, ...] | list[str] | str | AdrFields, 

70 /, 

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

72 ): 

73 """Initialize ADR with seven fields or parse from vCard format string. 

74 

75 Parameters: 

76 fields: Either an AdrFields, tuple, or list of seven strings, one per field, 

77 or a vCard format string with semicolon-separated fields 

78 params: Optional property parameters 

79 """ 

80 if isinstance(fields, str): 

81 fields = self.from_ical(fields) 

82 if isinstance(fields, AdrFields): 

83 self.fields = fields 

84 else: 

85 if len(fields) != 7: 

86 raise ValueError(f"ADR must have exactly 7 fields, got {len(fields)}") 

87 self.fields = AdrFields(*(str(f) for f in fields)) 

88 self.params = Parameters(params) 

89 

90 def to_ical(self) -> bytes: 

91 """Generate vCard format with semicolon-separated fields.""" 

92 # Each field is vText (handles comma/backslash escaping) 

93 # but we join with unescaped semicolons (field separators) 

94 from icalendar.prop.text import vText 

95 parts = [vText(f).to_ical().decode(DEFAULT_ENCODING) for f in self.fields] 

96 return ";".join(parts).encode(DEFAULT_ENCODING) 

97 

98 @staticmethod 

99 def from_ical(ical: str | bytes) -> AdrFields: 

100 """Parse vCard ADR format into an AdrFields named tuple. 

101 

102 Parameters: 

103 ical: vCard format string with semicolon-separated fields 

104 

105 Returns: 

106 AdrFields named tuple with seven field values. 

107 """ 

108 from icalendar.parser import split_on_unescaped_semicolon 

109 

110 ical = to_unicode(ical) 

111 fields = split_on_unescaped_semicolon(ical) 

112 if len(fields) != 7: 

113 raise ValueError( 

114 f"ADR must have exactly 7 fields, got {len(fields)}: {ical}" 

115 ) 

116 return AdrFields(*fields) 

117 

118 def __eq__(self, other: object) -> bool: 

119 """self == other""" 

120 return isinstance(other, vAdr) and self.fields == other.fields 

121 

122 def __repr__(self) -> str: 

123 """String representation.""" 

124 return f"{self.__class__.__name__}({self.fields}, params={self.params})" 

125 

126 @property 

127 def ical_value(self) -> AdrFields: 

128 """The address fields as a named tuple.""" 

129 return self.fields 

130 

131 from icalendar.param import VALUE 

132 

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

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

135 result = [name, self.params.to_jcal(), self.VALUE.lower()] 

136 result.extend(self.fields) 

137 return result 

138 

139 @classmethod 

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

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

142 

143 Parameters: 

144 jcal_property: The jCal property to parse. 

145 

146 Raises: 

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

148 """ 

149 JCalParsingError.validate_property(jcal_property, cls) 

150 if len(jcal_property) != 10: # name, params, value_type, 7 fields 

151 raise JCalParsingError( 

152 f"ADR must have 10 elements (name, params, value_type, 7 fields), " 

153 f"got {len(jcal_property)}" 

154 ) 

155 for i, field in enumerate(jcal_property[3:], start=3): 

156 JCalParsingError.validate_value_type(field, str, cls, i) 

157 return cls( 

158 tuple(jcal_property[3:]), 

159 Parameters.from_jcal_property(jcal_property), 

160 ) 

161 

162 @classmethod 

163 def examples(cls) -> list[Self]: 

164 """Examples of vAdr.""" 

165 return [cls(("", "", "123 Main St", "Springfield", "IL", "62701", "USA"))] 

166 

167 

168 

169__all__ = ["AdrFields", "vAdr"]