zeroconf 0.149.17__tar.gz → 0.150.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zeroconf-0.149.17 → zeroconf-0.150.2}/CHANGELOG.md +32 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/PKG-INFO +1 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/pyproject.toml +4 -3
- {zeroconf-0.149.17 → zeroconf-0.150.2}/setup.py +1 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/__init__.py +1 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_cache.py +2 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_core.py +94 -0
- zeroconf-0.150.2/src/zeroconf/_engine.py +471 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/query_handler.pxd +0 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/query_handler.py +9 -7
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/record_manager.py +4 -2
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_listener.pxd +2 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_listener.py +18 -4
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_protocol/incoming.py +1 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/browser.pxd +2 -2
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/browser.py +8 -5
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/info.pxd +8 -1
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/info.py +8 -5
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/registry.py +3 -2
- zeroconf-0.150.2/src/zeroconf/_transport.py +129 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/net.py +67 -11
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/asyncio.py +16 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/services/test_info.py +58 -0
- zeroconf-0.150.2/tests/test_interface_update.py +974 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/utils/test_net.py +103 -0
- zeroconf-0.149.17/src/zeroconf/_engine.py +0 -195
- zeroconf-0.149.17/src/zeroconf/_transport.py +0 -68
- {zeroconf-0.149.17 → zeroconf-0.150.2}/COPYING +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/README.rst +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/build_ext.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/docs/Makefile +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/docs/_ext/zeroconfautodocfix.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/docs/api.rst +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/docs/conf.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/docs/index.rst +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_cache.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_dns.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_dns.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_exceptions.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/answers.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/answers.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/multicast_outgoing_queue.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/multicast_outgoing_queue.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_handlers/record_manager.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_history.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_history.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_logger.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_protocol/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_protocol/incoming.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_protocol/outgoing.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_protocol/outgoing.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_record_update.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_record_update.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/__init__.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/registry.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_services/types.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_updates.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_updates.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/asyncio.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/ipaddress.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/ipaddress.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/name.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/time.pxd +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/_utils/time.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/const.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/src/zeroconf/py.typed +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/helpers.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_cache.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_cache_bound.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_cache_mark_expire.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_incoming.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_ipaddress.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_listener_dedup.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_mark_seen.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_outgoing.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_send.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/benchmarks/test_txt_properties.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/conftest.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/services/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/services/test_browser.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/services/test_registry.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/services/test_types.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_asyncio.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_cache.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_circular_imports.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_core.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_dns.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_engine.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_exceptions.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_handlers.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_history.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_init.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_listener.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_logger.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_protocol.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_services.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/test_updates.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/utils/__init__.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/utils/test_asyncio.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/utils/test_ipaddress.py +0 -0
- {zeroconf-0.149.17 → zeroconf-0.150.2}/tests/utils/test_name.py +0 -0
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v0.150.2 (2026-08-28)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Keep `key=` distinct from a valueless `key` in TXT records
|
|
10
|
+
([#1813](https://github.com/python-zeroconf/python-zeroconf/pull/1813),
|
|
11
|
+
[`899eaaa`](https://github.com/python-zeroconf/python-zeroconf/commit/899eaaa8a11c3d63b8325f73c9968a270e1e72f2))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## v0.150.1 (2026-08-28)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- Build with cython 3.3.0 ([#1820](https://github.com/python-zeroconf/python-zeroconf/pull/1820),
|
|
19
|
+
[`df947b2`](https://github.com/python-zeroconf/python-zeroconf/commit/df947b2f4df94950e272fe09dc9d48ad18411743))
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- Add an example for re-announcing on interface changes
|
|
24
|
+
([#1803](https://github.com/python-zeroconf/python-zeroconf/pull/1803),
|
|
25
|
+
[`c690b78`](https://github.com/python-zeroconf/python-zeroconf/commit/c690b78cbd8d54df9847e8873cf5edce80e70b8d))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## v0.150.0 (2026-06-22)
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
- Add async_update_interfaces to rescan network interfaces at runtime
|
|
33
|
+
([#1797](https://github.com/python-zeroconf/python-zeroconf/pull/1797),
|
|
34
|
+
[`471feb4`](https://github.com/python-zeroconf/python-zeroconf/commit/471feb490dc315578ca124411c00745028a1a736))
|
|
35
|
+
|
|
36
|
+
|
|
5
37
|
## v0.149.17 (2026-06-21)
|
|
6
38
|
|
|
7
39
|
### Performance Improvements
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zeroconf"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.150.2"
|
|
8
8
|
license = "LGPL-2.1-or-later"
|
|
9
9
|
description = "A pure python implementation of multicast DNS service discovery"
|
|
10
10
|
readme = "README.rst"
|
|
@@ -84,10 +84,10 @@ python = "^3.10"
|
|
|
84
84
|
ifaddr = ">=0.1.7"
|
|
85
85
|
|
|
86
86
|
[tool.poetry.group.dev.dependencies]
|
|
87
|
-
pytest = ">=9.1.
|
|
87
|
+
pytest = ">=9.1.1,<10.0"
|
|
88
88
|
pytest-cov = ">=4,<8"
|
|
89
89
|
pytest-asyncio = ">=1.4.0,<1.5.0"
|
|
90
|
-
cython = "^3.2.
|
|
90
|
+
cython = "^3.2.9"
|
|
91
91
|
setuptools = ">=65.6.3,<83.0.0"
|
|
92
92
|
pytest-timeout = "^2.1.0"
|
|
93
93
|
pytest-codspeed = ">=5.0.3,<6.0"
|
|
@@ -108,6 +108,7 @@ ignore = [
|
|
|
108
108
|
"PLR0912", # too many to fix right now
|
|
109
109
|
"TID252", # skip
|
|
110
110
|
"PLR0913", # too late to make changes here
|
|
111
|
+
"PLR0917", # too late to make changes here
|
|
111
112
|
"PLR0911", # would be breaking change
|
|
112
113
|
"TRY003", # too many to fix
|
|
113
114
|
"SLF001", # design choice
|
|
@@ -19,7 +19,7 @@ install_requires = \
|
|
|
19
19
|
|
|
20
20
|
setup_kwargs = {
|
|
21
21
|
'name': 'zeroconf',
|
|
22
|
-
'version': '0.
|
|
22
|
+
'version': '0.150.2',
|
|
23
23
|
'description': 'A pure python implementation of multicast DNS service discovery',
|
|
24
24
|
'long_description': 'python-zeroconf\n===============\n\n.. image:: https://github.com/python-zeroconf/python-zeroconf/workflows/CI/badge.svg\n :target: https://github.com/python-zeroconf/python-zeroconf?query=workflow%3ACI+branch%3Amaster\n\n.. image:: https://img.shields.io/pypi/v/zeroconf.svg\n :target: https://pypi.python.org/pypi/zeroconf\n\n.. image:: https://codecov.io/gh/python-zeroconf/python-zeroconf/branch/master/graph/badge.svg\n :target: https://codecov.io/gh/python-zeroconf/python-zeroconf\n\n.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json\n :target: https://codspeed.io/python-zeroconf/python-zeroconf\n :alt: Codspeed.io status for python-zeroconf\n\n.. image:: https://readthedocs.org/projects/python-zeroconf/badge/?version=latest\n :target: https://python-zeroconf.readthedocs.io/en/latest/?badge=latest\n :alt: Documentation Status\n\n`Documentation <https://python-zeroconf.readthedocs.io/en/latest/>`_.\n\nThis is fork of pyzeroconf, Multicast DNS Service Discovery for Python,\noriginally by Paul Scott-Murphy (https://github.com/paulsm/pyzeroconf),\nmodified by William McBrine (https://github.com/wmcbrine/pyzeroconf).\n\nThe original William McBrine\'s fork note::\n\n This fork is used in all of my TiVo-related projects: HME for Python\n (and therefore HME/VLC), Network Remote, Remote Proxy, and pyTivo.\n Before this, I was tracking the changes for zeroconf.py in three\n separate repos. I figured I should have an authoritative source.\n\n Although I make changes based on my experience with TiVos, I expect that\n they\'re generally applicable. This version also includes patches found\n on the now-defunct (?) Launchpad repo of pyzeroconf, and elsewhere\n around the net -- not always well-documented, sorry.\n\nCompatible with:\n\n* Bonjour\n* Avahi\n\nCompared to some other Zeroconf/Bonjour/Avahi Python packages, python-zeroconf:\n\n* isn\'t tied to Bonjour or Avahi\n* doesn\'t use D-Bus\n* doesn\'t force you to use particular event loop or Twisted (asyncio is used under the hood but not required)\n* is pip-installable\n* has PyPI distribution\n* has an optional cython extension for performance (pure python is supported as well)\n\nPython compatibility\n--------------------\n\n* CPython 3.10+\n* PyPy 3.10+\n\nVersioning\n----------\n\nThis project uses semantic versioning.\n\nStatus\n------\n\nThis project is actively maintained.\n\nTraffic Reduction\n-----------------\n\nBefore version 0.32, most traffic reduction techniques described in https://datatracker.ietf.org/doc/html/rfc6762#section-7\nwhere not implemented which could lead to excessive network traffic. It is highly recommended that version 0.32 or later\nis used if this is a concern.\n\nIPv6 support\n------------\n\nIPv6 support is relatively new and currently limited, specifically:\n\n* `InterfaceChoice.All` is an alias for `InterfaceChoice.Default` on non-POSIX\n systems.\n* Dual-stack IPv6 sockets are used, which may not be supported everywhere (some\n BSD variants do not have them).\n* Listening on localhost (`::1`) does not work. Help with understanding why is\n appreciated.\n\nHow to get python-zeroconf?\n===========================\n\n* PyPI page https://pypi.org/project/zeroconf/\n* GitHub project https://github.com/python-zeroconf/python-zeroconf\n\nThe easiest way to install python-zeroconf is using pip::\n\n pip install zeroconf\n\n\n\nHow do I use it?\n================\n\nHere\'s an example of browsing for a service:\n\n.. code-block:: python\n\n from zeroconf import ServiceBrowser, ServiceListener, Zeroconf\n\n\n class MyListener(ServiceListener):\n\n def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"Service {name} updated")\n\n def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n print(f"Service {name} removed")\n\n def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:\n info = zc.get_service_info(type_, name)\n print(f"Service {name} added, service info: {info}")\n\n\n zeroconf = Zeroconf()\n listener = MyListener()\n browser = ServiceBrowser(zeroconf, "_http._tcp.local.", listener)\n try:\n input("Press enter to exit...\\n\\n")\n finally:\n zeroconf.close()\n\n.. note::\n\n Discovery and service registration use *all* available network interfaces by default.\n If you want to customize that you need to specify ``interfaces`` argument when\n constructing ``Zeroconf`` object (see the code for details).\n\nIf you don\'t know the name of the service you need to browse for, try:\n\n.. code-block:: python\n\n from zeroconf import ZeroconfServiceTypes\n print(\'\\n\'.join(ZeroconfServiceTypes.find()))\n\nSee examples directory for more.\n\nChangelog\n=========\n\n`Changelog <CHANGELOG.md>`_\n\nLicense\n=======\n\nGNU Lesser General Public License v2.1 or later (LGPL-2.1-or-later).\n\nThe full text of LGPL 2.1 is included in the `COPYING <COPYING>`_ file.\nYou may, at your option, use this library under the terms of any later\nversion of the LGPL published by the Free Software Foundation. The\ncanonical SPDX identifier for this project is ``LGPL-2.1-or-later``, as\ndeclared in ``pyproject.toml``.\n',
|
|
25
25
|
'author': 'Paul Scott-Murphy',
|
|
@@ -88,7 +88,7 @@ from ._utils.time import ( # noqa # import needed for backwards compat
|
|
|
88
88
|
|
|
89
89
|
__author__ = "Paul Scott-Murphy, William McBrine"
|
|
90
90
|
__maintainer__ = "Jakub Stasiak <jakub@stasiak.at>"
|
|
91
|
-
__version__ = "0.
|
|
91
|
+
__version__ = "0.150.2"
|
|
92
92
|
__license__ = "LGPL"
|
|
93
93
|
|
|
94
94
|
|
|
@@ -42,6 +42,7 @@ from .const import _MAX_CACHE_RECORDS, _ONE_SECOND, _TYPE_PTR
|
|
|
42
42
|
_UNIQUE_RECORD_TYPES = (DNSAddress, DNSHinfo, DNSPointer, DNSText, DNSService)
|
|
43
43
|
_UniqueRecordsType = DNSAddress | DNSHinfo | DNSPointer | DNSText | DNSService
|
|
44
44
|
_DNSRecordCacheType = dict[str, dict[DNSRecord, DNSRecord]]
|
|
45
|
+
_UniqueType = tuple[str, int, int]
|
|
45
46
|
_DNSRecord = DNSRecord
|
|
46
47
|
_str = str
|
|
47
48
|
_float = float
|
|
@@ -324,7 +325,7 @@ class DNSCache:
|
|
|
324
325
|
|
|
325
326
|
def async_mark_unique_records_older_than_1s_to_expire(
|
|
326
327
|
self,
|
|
327
|
-
unique_types: set[
|
|
328
|
+
unique_types: set[_UniqueType],
|
|
328
329
|
answers: Iterable[DNSRecord],
|
|
329
330
|
now: _float,
|
|
330
331
|
) -> None:
|
|
@@ -199,6 +199,12 @@ class Zeroconf(QuietLogger):
|
|
|
199
199
|
|
|
200
200
|
self.unicast = unicast
|
|
201
201
|
self._use_asyncio = use_asyncio
|
|
202
|
+
# Retained so async_update_interfaces can re-run create_sockets /
|
|
203
|
+
# normalize_interface_choice against the live interface set later.
|
|
204
|
+
# Copy a mutable list so later caller mutation can't change it.
|
|
205
|
+
self._interfaces = list(interfaces) if isinstance(interfaces, list) else interfaces
|
|
206
|
+
self._ip_version = ip_version
|
|
207
|
+
self._apple_p2p = apple_p2p
|
|
202
208
|
listen_socket, respond_sockets = create_sockets(interfaces, unicast, ip_version, apple_p2p=apple_p2p)
|
|
203
209
|
log.debug("Listen socket %s, respond sockets %s", listen_socket, respond_sockets)
|
|
204
210
|
|
|
@@ -216,6 +222,9 @@ class Zeroconf(QuietLogger):
|
|
|
216
222
|
self.record_manager = RecordManager(self)
|
|
217
223
|
|
|
218
224
|
self._notify_futures: set[asyncio.Future] = set()
|
|
225
|
+
# Serializes async_update_interfaces so overlapping calls (a bursty
|
|
226
|
+
# adapter-change source) don't diff against a stale sender snapshot.
|
|
227
|
+
self._interface_update_lock = asyncio.Lock()
|
|
219
228
|
self.loop: asyncio.AbstractEventLoop | None = None
|
|
220
229
|
self._loop_thread: threading.Thread | None = None
|
|
221
230
|
|
|
@@ -406,6 +415,91 @@ class Zeroconf(QuietLogger):
|
|
|
406
415
|
self.registry.async_update(info)
|
|
407
416
|
return asyncio.ensure_future(self._async_broadcast_service(info, _REGISTER_TIME, None))
|
|
408
417
|
|
|
418
|
+
def update_interfaces(
|
|
419
|
+
self,
|
|
420
|
+
interfaces: InterfacesType | None = None,
|
|
421
|
+
ip_version: IPVersion | None = None,
|
|
422
|
+
apple_p2p: bool | None = None,
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Rescan network interfaces and reconcile the sockets in use.
|
|
425
|
+
|
|
426
|
+
While it is not expected during normal operation,
|
|
427
|
+
this function may raise EventLoopBlocked if the underlying
|
|
428
|
+
call to `async_update_interfaces` cannot be completed. Raises
|
|
429
|
+
RuntimeError if apple_p2p is set on a non-Apple platform.
|
|
430
|
+
"""
|
|
431
|
+
assert self.loop is not None
|
|
432
|
+
# Unlike register/update, the re-announce is awaited inline (to log
|
|
433
|
+
# per-service failures), so the budget must cover the full announce
|
|
434
|
+
# window ((_REGISTER_BROADCASTS - 1) * _REGISTER_TIME) plus the reconcile
|
|
435
|
+
# and wait-for-start overhead; double the register budget for headroom.
|
|
436
|
+
run_coro_with_timeout(
|
|
437
|
+
self.async_update_interfaces(interfaces, ip_version, apple_p2p),
|
|
438
|
+
self.loop,
|
|
439
|
+
_REGISTER_TIME * _REGISTER_BROADCASTS * 2,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
async def async_update_interfaces(
|
|
443
|
+
self,
|
|
444
|
+
interfaces: InterfacesType | None = None,
|
|
445
|
+
ip_version: IPVersion | None = None,
|
|
446
|
+
apple_p2p: bool | None = None,
|
|
447
|
+
) -> None:
|
|
448
|
+
"""Rescan network interfaces and reconcile the sockets in use.
|
|
449
|
+
|
|
450
|
+
Adds sockets for interfaces that appeared, drops sockets for
|
|
451
|
+
interfaces that disappeared, and re-announces existing
|
|
452
|
+
registrations when a new sender appeared. ``interfaces``,
|
|
453
|
+
``ip_version`` and ``apple_p2p`` each default to the value passed at
|
|
454
|
+
construction; pass a new value to switch it. When the resulting
|
|
455
|
+
interface set is unchanged this is a no-op (no sockets touched,
|
|
456
|
+
nothing re-announced). The listen socket is rebuilt if the new set
|
|
457
|
+
needs a different address family; unicast mode is fixed at
|
|
458
|
+
construction. Concurrent calls are serialized. Bringing up interfaces
|
|
459
|
+
is best-effort: a requested interface that fails to bind, or fails to
|
|
460
|
+
re-join after a rebuild, is logged rather than raised, and likewise a
|
|
461
|
+
registration that fails to re-announce is logged so one failure cannot
|
|
462
|
+
block the others. Raises RuntimeError if apple_p2p is set on a non-Apple
|
|
463
|
+
platform (input validation, matching the constructor).
|
|
464
|
+
"""
|
|
465
|
+
# Resolve against the retained config but only commit it after the
|
|
466
|
+
# engine reconcile succeeds, so a failed reconcile leaves the stored
|
|
467
|
+
# config unchanged rather than recording a set that never fully bound
|
|
468
|
+
# (a mid-reconcile failure may still have changed some sockets).
|
|
469
|
+
interfaces = self._interfaces if interfaces is None else interfaces
|
|
470
|
+
ip_version = self._ip_version if ip_version is None else ip_version
|
|
471
|
+
apple_p2p = self._apple_p2p if apple_p2p is None else apple_p2p
|
|
472
|
+
if apple_p2p and sys.platform != "darwin":
|
|
473
|
+
raise RuntimeError("Option `apple_p2p` is not supported on non-Apple platforms.")
|
|
474
|
+
await self.async_wait_for_start()
|
|
475
|
+
# Only the reconcile mutates the sender set, so hold the lock for that
|
|
476
|
+
# alone; the multi-second re-announce runs unlocked so a bursty
|
|
477
|
+
# adapter-change source isn't blocked behind it.
|
|
478
|
+
async with self._interface_update_lock:
|
|
479
|
+
added = await self.engine.async_update_interfaces(interfaces, ip_version, apple_p2p)
|
|
480
|
+
# Copy a mutable list so later caller mutation can't change the
|
|
481
|
+
# retained configuration.
|
|
482
|
+
self._interfaces = list(interfaces) if isinstance(interfaces, list) else interfaces
|
|
483
|
+
self._ip_version = ip_version
|
|
484
|
+
self._apple_p2p = apple_p2p
|
|
485
|
+
if not added:
|
|
486
|
+
return
|
|
487
|
+
# Re-announce every registration; one broadcast failing must not mask
|
|
488
|
+
# the rest, so collect exceptions and log them individually, naming the
|
|
489
|
+
# service so a partial failure is actionable.
|
|
490
|
+
infos = self.registry.async_get_service_infos()
|
|
491
|
+
results = await asyncio.gather(
|
|
492
|
+
*[self._async_broadcast_service(info, _REGISTER_TIME, None) for info in infos],
|
|
493
|
+
return_exceptions=True,
|
|
494
|
+
)
|
|
495
|
+
for info, result in zip(infos, results, strict=True):
|
|
496
|
+
if isinstance(result, Exception):
|
|
497
|
+
log.warning("Error re-announcing %s after interface update: %s", info.name, result)
|
|
498
|
+
elif isinstance(result, BaseException):
|
|
499
|
+
# gather(return_exceptions=True) also captures BaseExceptions
|
|
500
|
+
# such as CancelledError; don't swallow a cancellation/interrupt.
|
|
501
|
+
raise result
|
|
502
|
+
|
|
409
503
|
async def async_get_service_info(
|
|
410
504
|
self,
|
|
411
505
|
type_: str,
|