Skip to content

Commit df4f873

Browse files
committed
feat: normalize typing on ServiceInfo.properties
Clean up the typing on `ServiceInfo.properties` to always return bytes. The typing on properties was very confusing because it could have a mix of bytes and str types, but only bytes would ever come back from the backend and cache. The only way str could happen is if someone manually passed it in. We now ensure all passed in data is converted to bytes
1 parent a1c84dc commit df4f873

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)