fix: performance regression with ServiceInfo IPv6Addresses (#1330) · python-zeroconf/python-zeroconf@e2f9f81
@@ -23,8 +23,7 @@
2323import asyncio
2424import random
2525import sys
26-from functools import lru_cache
27-from ipaddress import IPv4Address, IPv6Address, _BaseAddress, ip_address
26+from ipaddress import IPv4Address, IPv6Address, _BaseAddress
2827from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union, cast
29283029from .._dns import (
@@ -47,6 +46,12 @@
4746run_coro_with_timeout,
4847wait_for_future_set_or_timeout,
4948)
49+from .._utils.ipaddress import (
50+cached_ip_addresses,
51+get_ip_address_object_from_record,
52+ip_bytes_and_scope_to_address,
53+str_without_scope_id,
54+)
5055from .._utils.name import service_type_name
5156from .._utils.net import IPVersion, _encode_address
5257from .._utils.time import current_time_millis
@@ -67,6 +72,8 @@
6772_TYPE_TXT,
6873)
697475+IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0)
76+7077_IPVersion_All_value = IPVersion.All.value
7178_IPVersion_V4Only_value = IPVersion.V4Only.value
7279# https://datatracker.ietf.org/doc/html/rfc6762#section-5.2
@@ -86,7 +93,6 @@
8693DNS_QUESTION_TYPE_QU = DNSQuestionType.QU
8794DNS_QUESTION_TYPE_QM = DNSQuestionType.QM
889589-IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0)
90969197if TYPE_CHECKING:
9298from .._core import Zeroconf
@@ -102,41 +108,6 @@ def instance_name_from_service_info(info: "ServiceInfo", strict: bool = True) ->
102108return info.name[: -len(service_name) - 1]
103109104110105-@lru_cache(maxsize=512)
106-def _cached_ip_addresses(address: Union[str, bytes, int]) -> Optional[Union[IPv4Address, IPv6Address]]:
107-"""Cache IP addresses."""
108-try:
109-return ip_address(address)
110-except ValueError:
111-return None
112-113-114-_cached_ip_addresses_wrapper = _cached_ip_addresses
115-116-117-def _get_ip_address_object_from_record(record: DNSAddress) -> Optional[Union[IPv4Address, IPv6Address]]:
118-"""Get the IP address object from the record."""
119-if IPADDRESS_SUPPORTS_SCOPE_ID and record.type == _TYPE_AAAA and record.scope_id is not None:
120-return _ip_bytes_and_scope_to_address(record.address, record.scope_id)
121-return _cached_ip_addresses_wrapper(record.address)
122-123-124-def _ip_bytes_and_scope_to_address(address: bytes_, scope: int_) -> Optional[Union[IPv4Address, IPv6Address]]:
125-"""Convert the bytes and scope to an IP address object."""
126-base_address = _cached_ip_addresses_wrapper(address)
127-if base_address is not None and base_address.is_link_local:
128-return _cached_ip_addresses_wrapper(f"{base_address}%{scope}")
129-return base_address
130-131-132-def _str_without_scope_id(addr: Union[IPv4Address, IPv6Address]) -> str:
133-"""Return the string representation of the address without the scope id."""
134-if IPADDRESS_SUPPORTS_SCOPE_ID and addr.version == 6:
135-address_str = str(addr)
136-return address_str.partition('%')[0]
137-return str(addr)
138-139-140111class ServiceInfo(RecordUpdateListener):
141112"""Service information.
142113@@ -271,9 +242,9 @@ def addresses(self, value: List[bytes]) -> None:
271242272243for address in value:
273244if IPADDRESS_SUPPORTS_SCOPE_ID and len(address) == 16 and self.interface_index is not None:
274-addr = _ip_bytes_and_scope_to_address(address, self.interface_index)
245+addr = ip_bytes_and_scope_to_address(address, self.interface_index)
275246else:
276-addr = _cached_ip_addresses_wrapper(address)
247+addr = cached_ip_addresses(address)
277248if addr is None:
278249raise TypeError(
279250"Addresses must either be IPv4 or IPv6 strings, bytes, or integers;"
@@ -369,7 +340,7 @@ def parsed_addresses(self, version: IPVersion = IPVersion.All) -> List[str]:
369340 This means the first address will always be the most recently added
370341 address of the given IP version.
371342 """
372-return [_str_without_scope_id(addr) for addr in self._ip_addresses_by_version_value(version.value)]
343+return [str_without_scope_id(addr) for addr in self._ip_addresses_by_version_value(version.value)]
373344374345def parsed_scoped_addresses(self, version: IPVersion = IPVersion.All) -> List[str]:
375346"""Equivalent to parsed_addresses, with the exception that IPv6 Link-Local
@@ -446,7 +417,7 @@ def _get_ip_addresses_from_cache_lifo(
446417for record in self._get_address_records_from_cache_by_type(zc, type):
447418if record.is_expired(now):
448419continue
449-ip_addr = _get_ip_address_object_from_record(record)
420+ip_addr = get_ip_address_object_from_record(record)
450421if ip_addr is not None and ip_addr not in address_list:
451422address_list.append(ip_addr)
452423address_list.reverse() # Reverse to get LIFO order
@@ -496,7 +467,7 @@ def _process_record_threadsafe(self, zc: 'Zeroconf', record: DNSRecord, now: flo
496467dns_address_record = record
497468if TYPE_CHECKING:
498469assert isinstance(dns_address_record, DNSAddress)
499-ip_addr = _get_ip_address_object_from_record(dns_address_record)
470+ip_addr = get_ip_address_object_from_record(dns_address_record)
500471if ip_addr is None:
501472log.warning(
502473"Encountered invalid address while processing %s: %s",