feat: speed up unpacking text records in ServiceInfo (#1212) · python-zeroconf/python-zeroconf@99a6f98
@@ -172,7 +172,7 @@ def __init__(
172172self.priority = priority
173173self.server = server if server else None
174174self.server_key = server.lower() if server else None
175-self._properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {}
175+self._properties: Optional[Dict[Union[str, bytes], Optional[Union[str, bytes]]]] = None
176176if isinstance(properties, bytes):
177177self._set_text(properties)
178178else:
@@ -226,14 +226,18 @@ def addresses(self, value: List[bytes]) -> None:
226226self._ipv6_addresses.append(addr)
227227228228@property
229-def properties(self) -> Dict:
229+def properties(self) -> Dict[Union[str, bytes], Optional[Union[str, bytes]]]:
230230"""If properties were set in the constructor this property returns the original dictionary
231231 of type `Dict[Union[bytes, str], Any]`.
232232233233 If properties are coming from the network, after decoding a TXT record, the keys are always
234234 bytes and the values are either bytes, if there was a value, even empty, or `None`, if there
235235 was none. No further decoding is attempted. The type returned is `Dict[bytes, Optional[bytes]]`.
236236 """
237+if self._properties is None:
238+self._unpack_text_into_properties()
239+if TYPE_CHECKING:
240+assert self._properties is not None
237241return self._properties
238242239243async def async_wait(self, timeout: float) -> None:
@@ -317,10 +321,10 @@ def parsed_scoped_addresses(self, version: IPVersion = IPVersion.All) -> List[st
317321for addr in self._ip_addresses_by_version_value(version.value)
318322 ]
319323320-def _set_properties(self, properties: Dict) -> None:
324+def _set_properties(self, properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]]) -> None:
321325"""Sets properties and text of this info from a dictionary"""
322326self._properties = properties
323-list_ = []
327+list_: List[bytes] = []
324328result = b''
325329for key, value in properties.items():
326330if isinstance(key, str):
@@ -338,14 +342,25 @@ def _set_properties(self, properties: Dict) -> None:
338342339343def _set_text(self, text: bytes) -> None:
340344"""Sets properties and text given a text field"""
345+if text == self.text:
346+return
341347self.text = text
348+# Clear the properties cache
349+self._properties = None
350+351+def _unpack_text_into_properties(self) -> None:
352+"""Unpacks the text field into properties"""
353+text = self.text
342354end = len(text)
343355if end == 0:
356+# Properties should be set atomically
357+# in case another thread is reading them
344358self._properties = {}
345359return
360+346361result: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {}
347362index = 0
348-strs = []
363+strs: List[bytes] = []
349364while index < end:
350365length = text[index]
351366index += 1
@@ -355,17 +370,20 @@ def _set_text(self, text: bytes) -> None:
355370key: bytes
356371value: Optional[bytes]
357372for s in strs:
358-try:
359-key, value = s.split(b'=', 1)
360-except ValueError:
373+key_value = s.split(b'=', 1)
374+if len(key_value) == 2:
375+key, value = key_value
376+else:
361377# No equals sign at all
362378key = s
363379value = None
364380365381# Only update non-existent properties
366-if key and result.get(key) is None:
382+if key and key not in result:
367383result[key] = value
368384385+# Properties should be set atomically
386+# in case another thread is reading them
369387self._properties = result
370388371389def get_name(self) -> str: