feat: speed up unpacking text records in ServiceInfo (#1212) · python-zeroconf/python-zeroconf@99a6f98

@@ -172,7 +172,7 @@ def __init__(

172172

self.priority = priority

173173

self.server = server if server else None

174174

self.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

176176

if isinstance(properties, bytes):

177177

self._set_text(properties)

178178

else:

@@ -226,14 +226,18 @@ def addresses(self, value: List[bytes]) -> None:

226226

self._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

237241

return self._properties

238242239243

async def async_wait(self, timeout: float) -> None:

@@ -317,10 +321,10 @@ def parsed_scoped_addresses(self, version: IPVersion = IPVersion.All) -> List[st

317321

for 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"""

322326

self._properties = properties

323-

list_ = []

327+

list_: List[bytes] = []

324328

result = b''

325329

for key, value in properties.items():

326330

if isinstance(key, str):

@@ -338,14 +342,25 @@ def _set_properties(self, properties: Dict) -> None:

338342339343

def _set_text(self, text: bytes) -> None:

340344

"""Sets properties and text given a text field"""

345+

if text == self.text:

346+

return

341347

self.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

342354

end = len(text)

343355

if end == 0:

356+

# Properties should be set atomically

357+

# in case another thread is reading them

344358

self._properties = {}

345359

return

360+346361

result: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {}

347362

index = 0

348-

strs = []

363+

strs: List[bytes] = []

349364

while index < end:

350365

length = text[index]

351366

index += 1

@@ -355,17 +370,20 @@ def _set_text(self, text: bytes) -> None:

355370

key: bytes

356371

value: Optional[bytes]

357372

for 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

362378

key = s

363379

value = None

364380365381

# Only update non-existent properties

366-

if key and result.get(key) is None:

382+

if key and key not in result:

367383

result[key] = value

368384385+

# Properties should be set atomically

386+

# in case another thread is reading them

369387

self._properties = result

370388371389

def get_name(self) -> str: