Skip to content

Commit d29553a

Browse files
authored
feat: ensure ServiceInfo.properties always returns bytes (#1333)
1 parent a1c84dc commit d29553a

3 files changed

Lines changed: 17 additions & 11 deletions

File tree

src/zeroconf/_services/info.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ cdef class ServiceInfo(RecordUpdateListener):
7272
@cython.locals(length="unsigned char", index="unsigned int", key_value=bytes, key_sep_value=tuple)
7373
cdef void _unpack_text_into_properties(self)
7474

75+
@cython.locals(properties_contain_str=bint)
7576
cpdef _set_properties(self, cython.dict properties)
7677

7778
cdef _set_text(self, cython.bytes text)

src/zeroconf/_services/info.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def __init__(
191191
self.priority = priority
192192
self.server = server if server else None
193193
self.server_key = server.lower() if server else None
194-
self._properties: Optional[Dict[Union[str, bytes], Optional[Union[str, bytes]]]] = None
194+
self._properties: Optional[Dict[bytes, Optional[bytes]]] = None
195195
if isinstance(properties, bytes):
196196
self._set_text(properties)
197197
else:
@@ -260,14 +260,8 @@ def addresses(self, value: List[bytes]) -> None:
260260
self._ipv6_addresses.append(addr)
261261

262262
@property
263-
def properties(self) -> Dict[Union[str, bytes], Optional[Union[str, bytes]]]:
264-
"""If properties were set in the constructor this property returns the original dictionary
265-
of type `Dict[Union[bytes, str], Any]`.
266-
267-
If properties are coming from the network, after decoding a TXT record, the keys are always
268-
bytes and the values are either bytes, if there was a value, even empty, or `None`, if there
269-
was none. No further decoding is attempted. The type returned is `Dict[bytes, Optional[bytes]]`.
270-
"""
263+
def properties(self) -> Dict[bytes, Optional[bytes]]:
264+
"""Return properties as bytes."""
271265
if self._properties is None:
272266
self._unpack_text_into_properties()
273267
if TYPE_CHECKING:
@@ -356,21 +350,31 @@ def parsed_scoped_addresses(self, version: IPVersion = IPVersion.All) -> List[st
356350

357351
def _set_properties(self, properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]]) -> None:
358352
"""Sets properties and text of this info from a dictionary"""
359-
self._properties = properties
360353
list_: List[bytes] = []
354+
properties_contain_str = False
361355
result = b''
362356
for key, value in properties.items():
363357
if isinstance(key, str):
364358
key = key.encode('utf-8')
359+
properties_contain_str = True
365360

366361
record = key
367362
if value is not None:
368363
if not isinstance(value, bytes):
369364
value = str(value).encode('utf-8')
365+
properties_contain_str = True
370366
record += b'=' + value
371367
list_.append(record)
372368
for item in list_:
373369
result = b''.join((result, bytes((len(item),)), item))
370+
if not properties_contain_str:
371+
# If there are no str keys or values, we can use the properties
372+
# as-is, without decoding them, otherwise calling
373+
# self.properties will lazy decode them, which is expensive.
374+
if TYPE_CHECKING:
375+
self._properties = cast("Dict[bytes, Optional[bytes]]", properties)
376+
else:
377+
self._properties = properties
374378
self.text = result
375379

376380
def _set_text(self, text: bytes) -> None:
@@ -392,7 +396,7 @@ def _unpack_text_into_properties(self) -> None:
392396
return
393397

394398
index = 0
395-
properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {}
399+
properties: Dict[bytes, Optional[bytes]] = {}
396400
while index < end:
397401
length = text[index]
398402
index += 1

tests/test_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def update_service(self, zeroconf, type, name):
133133
assert info.properties[b'prop_blank'] == properties['prop_blank']
134134
assert info.properties[b'prop_true'] == b'1'
135135
assert info.properties[b'prop_false'] == b'0'
136+
136137
assert info.addresses == addresses[:1] # no V6 by default
137138
assert set(info.addresses_by_version(r.IPVersion.All)) == set(addresses)
138139

0 commit comments

Comments
 (0)