Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-zeroconf for openSUSE:Factory checked in at 2022-01-10 23:53:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-zeroconf (Old) and /work/SRC/openSUSE:Factory/.python-zeroconf.new.1892 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-zeroconf" Mon Jan 10 23:53:19 2022 rev:29 rq:945269 version:0.38.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-zeroconf/python-zeroconf.changes 2021-12-02 02:25:12.420617506 +0100 +++ /work/SRC/openSUSE:Factory/.python-zeroconf.new.1892/python-zeroconf.changes 2022-01-10 23:53:46.276813170 +0100 @@ -1,0 +2,9 @@ +Mon Jan 3 10:29:30 UTC 2022 - Dirk M??ller <dmuel...@suse.com> + +- update to 0.38.1: + * Dropped Python 3.6 support + * Handle Service types that end with another service type + * Improve performance of query scheduler + * Avoid linear type searches in ServiceBrowsers + +------------------------------------------------------------------- Old: ---- python-zeroconf-0.37.0.obscpio New: ---- python-zeroconf-0.38.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-zeroconf.spec ++++++ --- /var/tmp/diff_new_pack.494Uhp/_old 2022-01-10 23:53:47.296814064 +0100 +++ /var/tmp/diff_new_pack.494Uhp/_new 2022-01-10 23:53:47.300814068 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-zeroconf # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,8 +18,9 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 +%define skip_python36 1 Name: python-zeroconf -Version: 0.37.0 +Version: 0.38.1 Release: 0 Summary: Pure Python Multicast DNS Service Discovery Library (Bonjour/Avahi compatible) License: LGPL-2.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.494Uhp/_old 2022-01-10 23:53:47.328814092 +0100 +++ /var/tmp/diff_new_pack.494Uhp/_new 2022-01-10 23:53:47.332814095 +0100 @@ -2,8 +2,9 @@ <service name="obs_scm" mode="disabled"> <param name="url">https://github.com/jstasiak/python-zeroconf</param> <param name="scm">git</param> - <param name="revision">0.37.0</param> - <param name="version">0.37.0</param> + <param name="revision">0.38.1</param> + <param name="versionformat">@PARENT_TAG@</param> + <param name="versionrewrite-pattern">(.*)</param> </service> <service name="set_version" mode="disabled"/> <service mode="buildtime" name="tar" /> ++++++ python-zeroconf-0.37.0.obscpio -> python-zeroconf-0.38.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/.github/workflows/ci.yml new/python-zeroconf-0.38.1/.github/workflows/ci.yml --- old/python-zeroconf-0.37.0/.github/workflows/ci.yml 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/.github/workflows/ci.yml 2021-12-24 03:47:53.000000000 +0100 @@ -14,7 +14,7 @@ strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "pypy-3.6", "pypy-3.7"] + python-version: [3.7, 3.8, 3.9, "3.10", "pypy-3.7"] include: - os: ubuntu-latest venvcmd: . env/bin/activate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/README.rst new/python-zeroconf-0.38.1/README.rst --- old/python-zeroconf-0.37.0/README.rst 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/README.rst 2021-12-24 03:47:53.000000000 +0100 @@ -44,8 +44,8 @@ Python compatibility -------------------- -* CPython 3.6+ -* PyPy3 7.2+ +* CPython 3.7+ +* PyPy3.7 7.3+ Versioning ---------- @@ -138,6 +138,21 @@ Changelog ========= +0.38.1 +====== + +* Improve performance of query scheduler (#1043) @bdraco +* Avoid linear type searches in ServiceBrowsers (#1044) @bdraco + +0.38.0 +====== + +* Handle Service types that end with another service type (#1041) @apworks1 + +Backwards incompatible: + +* Dropped Python 3.6 support (#1009) @bdraco + 0.37.0 ====== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/setup.cfg new/python-zeroconf-0.38.1/setup.cfg --- old/python-zeroconf-0.37.0/setup.cfg 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/setup.cfg 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.37.0 +current_version = 0.38.1 commit = True tag = True tag_name = {new_version} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/services/test_browser.py new/python-zeroconf-0.38.1/tests/services/test_browser.py --- old/python-zeroconf-0.37.0/tests/services/test_browser.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/services/test_browser.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ Unit tests for zeroconf._services.browser. """ @@ -682,7 +681,7 @@ info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -902,7 +901,7 @@ now = current_time_millis() for i in range(120): name = f"_hap{i}._tcp._local." - questions_with_known_answers[DNSQuestion(name, const._TYPE_PTR, const._CLASS_IN)] = set( + questions_with_known_answers[DNSQuestion(name, const._TYPE_PTR, const._CLASS_IN)] = { DNSPointer( name, const._TYPE_PTR, @@ -911,7 +910,7 @@ f"zoo{counter}.{name}", ) for counter in range(i) - ) + } outs = _services_browser._group_ptr_queries_with_known_answers(now, True, questions_with_known_answers) for out in outs: packets = out.packets() @@ -937,7 +936,7 @@ 10000, f'known-to-other.{name}', ) - other_known_answers = set([answer]) + other_known_answers = {answer} zc.question_history.add_question_at_time(question, now, other_known_answers) assert zc.question_history.suppresses(question, now, other_known_answers) @@ -976,7 +975,7 @@ @pytest.mark.asyncio async def test_query_scheduler(): delay = const._BROWSER_TIME - types_ = set(["_hap._tcp.local.", "_http._tcp.local."]) + types_ = {"_hap._tcp.local.", "_http._tcp.local."} query_scheduler = _services_browser.QueryScheduler(types_, delay, (0, 0)) now = current_time_millis() @@ -984,8 +983,8 @@ # Test query interval is increasing assert query_scheduler.millis_to_wait(now - 1) == 1 - assert query_scheduler.millis_to_wait(now) is 0 - assert query_scheduler.millis_to_wait(now + 1) is 0 + assert query_scheduler.millis_to_wait(now) == 0 + assert query_scheduler.millis_to_wait(now + 1) == 0 assert set(query_scheduler.process_ready_types(now)) == types_ assert set(query_scheduler.process_ready_types(now)) == set() @@ -1013,8 +1012,91 @@ assert set(query_scheduler.process_ready_types(now + delay * 15)) == set() # Test if we reschedule 1 second later... and its ready for processing - assert set(query_scheduler.process_ready_types(now + delay * 16)) == set(["_hap._tcp.local."]) + assert set(query_scheduler.process_ready_types(now + delay * 16)) == {"_hap._tcp.local."} assert query_scheduler.millis_to_wait(now) == pytest.approx(delay * 31, 0.00001) assert set(query_scheduler.process_ready_types(now + delay * 20)) == set() - assert set(query_scheduler.process_ready_types(now + delay * 31)) == set(["_http._tcp.local."]) + assert set(query_scheduler.process_ready_types(now + delay * 31)) == {"_http._tcp.local."} + + +def test_service_browser_matching(): + """Test that the ServiceBrowser matching does not match partial names.""" + + # instantiate a zeroconf instance + zc = Zeroconf(interfaces=['127.0.0.1']) + # start a browser + type_ = "_http._tcp.local." + registration_name = "xxxyyy.%s" % type_ + not_match_type_ = "_asustor-looksgood_http._tcp.local." + not_match_registration_name = "xxxyyy.%s" % not_match_type_ + callbacks = [] + + class MyServiceListener(r.ServiceListener): + def add_service(self, zc, type_, name) -> None: + nonlocal callbacks + if name == registration_name: + callbacks.append(("add", type_, name)) + + def remove_service(self, zc, type_, name) -> None: + nonlocal callbacks + if name == registration_name: + callbacks.append(("remove", type_, name)) + + def update_service(self, zc, type_, name) -> None: + nonlocal callbacks + if name == registration_name: + callbacks.append(("update", type_, name)) + + listener = MyServiceListener() + + browser = r.ServiceBrowser(zc, type_, None, listener) + + desc = {'path': '/~paulsm/'} + address_parsed = "10.0.1.2" + address = socket.inet_aton(address_parsed) + info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address]) + should_not_match = ServiceInfo( + not_match_type_, not_match_registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address] + ) + + def mock_incoming_msg(records) -> r.DNSIncoming: + generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE) + for record in records: + generated.add_answer_at_time(record, 0) + return r.DNSIncoming(generated.packets()[0]) + + _inject_response( + zc, + mock_incoming_msg([info.dns_pointer(), info.dns_service(), info.dns_text(), *info.dns_addresses()]), + ) + _inject_response( + zc, + mock_incoming_msg( + [ + should_not_match.dns_pointer(), + should_not_match.dns_service(), + should_not_match.dns_text(), + *should_not_match.dns_addresses(), + ] + ), + ) + time.sleep(0.2) + info.port = 400 + _inject_response( + zc, + mock_incoming_msg([info.dns_service()]), + ) + should_not_match.port = 400 + _inject_response( + zc, + mock_incoming_msg([should_not_match.dns_service()]), + ) + time.sleep(0.2) + + assert callbacks == [ + ('add', type_, registration_name), + ('update', type_, registration_name), + ] + browser.cancel() + + zc.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/services/test_info.py new/python-zeroconf-0.38.1/tests/services/test_info.py --- old/python-zeroconf-0.37.0/tests/services/test_info.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/services/test_info.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ Unit tests for zeroconf._services.info. """ @@ -607,7 +606,7 @@ desc = {'path': '/~paulsm/'} type_ = "_homeassistant._tcp.local." name = "MyTestHome" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" ipv4 = socket.inet_aton("10.0.1.2") ipv6 = socket.inet_pton(socket.AF_INET6, "2001:db8::1") info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[ipv4, ipv6]) @@ -627,7 +626,7 @@ name = "MyTestHome" info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -649,7 +648,7 @@ with pytest.raises(TypeError): info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -661,7 +660,7 @@ info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -680,12 +679,12 @@ addresses = [socket.inet_aton("10.0.1.2")] server_name = "ash-2.local." info_service = ServiceInfo( - type_, '%s.%s' % (name, type_), 80, 0, 0, {b'path': b'/~paulsm/'}, server_name, addresses=addresses + type_, f'{name}.{type_}', 80, 0, 0, {b'path': b'/~paulsm/'}, server_name, addresses=addresses ) assert info_service.dns_text().text == b'\x0epath=/~paulsm/' info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -696,7 +695,7 @@ assert info_service.dns_text().text == b'\x0epath=/~paulsm/' info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, @@ -707,7 +706,7 @@ assert info_service.dns_text().text == b'\x0epath=/~paulsm/' info_service = ServiceInfo( type_, - '%s.%s' % (name, type_), + f'{name}.{type_}', 80, 0, 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/services/test_registry.py new/python-zeroconf-0.38.1/tests/services/test_registry.py --- old/python-zeroconf-0.37.0/tests/services/test_registry.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/services/test_registry.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Unit tests for zeroconf._services.registry.""" @@ -15,7 +14,7 @@ def test_only_register_once(self): type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( @@ -32,8 +31,8 @@ type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" name2 = "xxxyyy2" - registration_name = "%s.%s" % (name, type_) - registration_name2 = "%s.%s" % (name2, type_) + registration_name = f"{name}.{type_}" + registration_name2 = f"{name2}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( @@ -61,7 +60,7 @@ """ type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( @@ -77,7 +76,7 @@ def test_lookups(self): type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( @@ -96,7 +95,7 @@ def test_lookups_upper_case_by_lower_case(self): type_ = "_test-SRVC-type._tcp.local." name = "Xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( @@ -115,7 +114,7 @@ def test_lookups_lower_case_by_upper_case(self): type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" desc = {'path': '/~paulsm/'} info = ServiceInfo( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/services/test_types.py new/python-zeroconf-0.38.1/tests/services/test_types.py --- old/python-zeroconf-0.37.0/tests/services/test_types.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/services/test_types.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Unit tests for zeroconf._services.types.""" @@ -36,7 +35,7 @@ type_ = "_test-listen-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} @@ -72,7 +71,7 @@ type_ = "_test-listenv6rec-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" addr = "2606:2800:220:1:248:1893:25c8:1946" # example.com zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) @@ -109,7 +108,7 @@ type_ = "_test-listenv6ip-type._tcp.local." name = "xxxyyy" - registration_name = "%s.%s" % (name, type_) + registration_name = f"{name}.{type_}" addr = "2606:2800:220:1:248:1893:25c8:1946" # example.com zeroconf_registrar = Zeroconf(ip_version=r.IPVersion.V6Only) @@ -145,8 +144,8 @@ type_ = "_listen._tcp.local." name = "xxxyyy" # Note: discovery returns only DNS-SD type not subtype - discovery_type = "%s.%s" % (subtype_, type_) - registration_name = "%s.%s" % (name, type_) + discovery_type = f"{subtype_}.{type_}" + registration_name = f"{name}.{type_}" zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/test_asyncio.py new/python-zeroconf-0.38.1/tests/test_asyncio.py --- old/python-zeroconf-0.37.0/tests/test_asyncio.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/test_asyncio.py 2021-12-24 03:47:53.000000000 +0100 @@ -923,7 +923,7 @@ # Increase simulated time shift by 1/4 of the TTL in seconds time_offset += expected_ttl / 4 now = _new_current_time_millis() - browser.reschedule_type(type_, now) + browser.reschedule_type(type_, now, now) sleep_count += 1 await asyncio.wait_for(got_query.wait(), 1) got_query.clear() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/test_exceptions.py new/python-zeroconf-0.38.1/tests/test_exceptions.py --- old/python-zeroconf-0.37.0/tests/test_exceptions.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/test_exceptions.py 2021-12-24 03:47:53.000000000 +0100 @@ -76,6 +76,8 @@ def test_good_instance_names(self): assert r.service_type_name('.._x._tcp.local.') == '_x._tcp.local.' + assert r.service_type_name('x.y._http._tcp.local.') == '_http._tcp.local.' + assert r.service_type_name('1.2.3._mqtt._tcp.local.') == '_mqtt._tcp.local.' assert r.service_type_name('x.sub._http._tcp.local.') == '_http._tcp.local.' assert ( r.service_type_name('6d86f882b90facee9170ad3439d72a4d6ee9f511._zget._http._tcp.local.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/test_services.py new/python-zeroconf-0.38.1/tests/test_services.py --- old/python-zeroconf-0.37.0/tests/test_services.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/test_services.py 2021-12-24 03:47:53.000000000 +0100 @@ -38,12 +38,13 @@ class ListenerTest(unittest.TestCase): def test_integration_with_listener_class(self): + sub_service_added = Event() service_added = Event() service_removed = Event() - service_updated = Event() - service_updated2 = Event() + sub_service_updated = Event() + duplicate_service_added = Event() - subtype_name = "My special Subtype" + subtype_name = "_printer" type_ = "_http._tcp.local." subtype = subtype_name + "._sub." + type_ name = "UPPERxxxyyy??????" @@ -58,21 +59,32 @@ service_removed.set() def update_service(self, zeroconf, type, name): - service_updated2.set() + pass + + class DuplicateListener(r.ServiceListener): + def add_service(self, zeroconf, type, name): + duplicate_service_added.set() + + def remove_service(self, zeroconf, type, name): + pass + + def update_service(self, zeroconf, type, name): + pass class MySubListener(r.ServiceListener): def add_service(self, zeroconf, type, name): + sub_service_added.set() pass def remove_service(self, zeroconf, type, name): pass def update_service(self, zeroconf, type, name): - service_updated.set() + sub_service_updated.set() listener = MyListener() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) - zeroconf_browser.add_service_listener(subtype, listener) + zeroconf_browser.add_service_listener(type_, listener) properties = dict( prop_none=None, @@ -107,6 +119,11 @@ # short pause to allow multicast timers to expire time.sleep(3) + zeroconf_browser.add_service_listener(type_, DuplicateListener()) + duplicate_service_added.wait( + 1 + ) # Ensure a listener for the same type calls back right away from cache + # clear the answer cache to force query _clear_cache(zeroconf_browser) @@ -160,7 +177,9 @@ # test TXT record update sublistener = MySubListener() - zeroconf_browser.add_service_listener(registration_name, sublistener) + + zeroconf_browser.add_service_listener(subtype, sublistener) + properties['prop_blank'] = b'an updated string' desc.update(properties) info_service = ServiceInfo( @@ -174,8 +193,9 @@ addresses=[socket.inet_aton("10.0.1.2")], ) zeroconf_registrar.update_service(info_service) - service_updated.wait(1) - assert service_updated.is_set() + + sub_service_added.wait(1) # we cleared the cache above + assert sub_service_added.is_set() info = zeroconf_browser.get_service_info(type_, registration_name) assert info is not None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/utils/test_asyncio.py new/python-zeroconf-0.38.1/tests/utils/test_asyncio.py --- old/python-zeroconf-0.37.0/tests/utils/test_asyncio.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/utils/test_asyncio.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Unit tests for zeroconf._utils.asyncio.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/utils/test_name.py new/python-zeroconf-0.38.1/tests/utils/test_name.py --- old/python-zeroconf-0.37.0/tests/utils/test_name.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/utils/test_name.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Unit tests for zeroconf._utils.name.""" @@ -24,3 +23,25 @@ nameutils.service_type_name(f"{long_name}._tivo-videostream._tcp.local.") with pytest.raises(BadTypeInNameException): nameutils.service_type_name(f"{long_name}._tivo-videostream._tcp.local.", strict=False) + + +def test_possible_types(): + """Test possible types from name.""" + assert nameutils.possible_types('.') == set() + assert nameutils.possible_types('local.') == set() + assert nameutils.possible_types('_tcp.local.') == set() + assert nameutils.possible_types('_test-srvc-type._tcp.local.') == {'_test-srvc-type._tcp.local.'} + assert nameutils.possible_types('_any._tcp.local.') == {'_any._tcp.local.'} + assert nameutils.possible_types('.._x._tcp.local.') == {'_x._tcp.local.'} + assert nameutils.possible_types('x.y._http._tcp.local.') == {'_http._tcp.local.'} + assert nameutils.possible_types('1.2.3._mqtt._tcp.local.') == {'_mqtt._tcp.local.'} + assert nameutils.possible_types('x.sub._http._tcp.local.') == {'_http._tcp.local.'} + assert nameutils.possible_types('6d86f882b90facee9170ad3439d72a4d6ee9f511._zget._http._tcp.local.') == { + '_http._tcp.local.', + '_zget._http._tcp.local.', + } + assert nameutils.possible_types('my._printer._sub._http._tcp.local.') == { + '_http._tcp.local.', + '_sub._http._tcp.local.', + '_printer._sub._http._tcp.local.', + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/tests/utils/test_net.py new/python-zeroconf-0.38.1/tests/utils/test_net.py --- old/python-zeroconf-0.37.0/tests/utils/test_net.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/tests/utils/test_net.py 2021-12-24 03:47:53.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Unit tests for zeroconf._utils.net.""" @@ -87,7 +86,7 @@ def test_add_multicast_member_socket_errors(errno, expected_result): """Test we handle socket errors when adding multicast members.""" if errno: - setsockopt_mock = unittest.mock.Mock(side_effect=OSError(errno, "Error: {}".format(errno))) + setsockopt_mock = unittest.mock.Mock(side_effect=OSError(errno, f"Error: {errno}")) else: setsockopt_mock = unittest.mock.Mock() fileno_mock = unittest.mock.PropertyMock(return_value=10) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/__init__.py new/python-zeroconf-0.38.1/zeroconf/__init__.py --- old/python-zeroconf-0.37.0/zeroconf/__init__.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/__init__.py 2021-12-24 03:47:53.000000000 +0100 @@ -79,7 +79,7 @@ __author__ = 'Paul Scott-Murphy, William McBrine' __maintainer__ = 'Jakub Stasiak <ja...@stasiak.at>' -__version__ = '0.37.0' +__version__ = '0.38.1' __license__ = 'LGPL' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/_handlers.py new/python-zeroconf-0.38.1/zeroconf/_handlers.py --- old/python-zeroconf-0.37.0/zeroconf/_handlers.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/_handlers.py 2021-12-24 03:47:53.000000000 +0100 @@ -326,7 +326,7 @@ self._add_address_answers(question.name, answer_set, known_answers, now, type_) if type_ in (_TYPE_SRV, _TYPE_TXT, _TYPE_ANY): - service = self.registry.async_get_info_name(question.name) # type: ignore + service = self.registry.async_get_info_name(question.name) if service is not None: if type_ in (_TYPE_SRV, _TYPE_ANY): # Add recommended additional answers according to @@ -515,12 +515,12 @@ This function must be run from the event loop. """ now = current_time_millis() - records: List[RecordUpdate] = [] - for question in questions: - for record in self.cache.async_entries_with_name(question.name): - if not record.is_expired(now) and question.answered_by(record): - records.append(RecordUpdate(record, None)) - + records: List[RecordUpdate] = [ + RecordUpdate(record, None) + for question in questions + for record in self.cache.async_entries_with_name(question.name) + if not record.is_expired(now) and question.answered_by(record) + ] if not records: return listener.async_update_records(self.zc, now, records) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/_services/browser.py new/python-zeroconf-0.38.1/zeroconf/_services/browser.py --- old/python-zeroconf-0.37.0/zeroconf/_services/browser.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/_services/browser.py 2021-12-24 03:47:53.000000000 +0100 @@ -26,7 +26,7 @@ import threading import warnings from collections import OrderedDict -from typing import Callable, Dict, List, Optional, Set, TYPE_CHECKING, Tuple, Union, cast +from typing import Callable, Dict, Iterable, List, Optional, Set, TYPE_CHECKING, Tuple, Union, cast from .._dns import DNSAddress, DNSPointer, DNSQuestion, DNSQuestionType, DNSRecord from .._logger import log @@ -38,8 +38,7 @@ SignalRegistrationInterface, ) from .._updates import RecordUpdate, RecordUpdateListener -from .._utils.asyncio import get_best_available_queue -from .._utils.name import service_type_name +from .._utils.name import possible_types, service_type_name from .._utils.time import current_time_millis, millis_to_seconds from ..const import ( _BROWSER_BACKOFF_LIMIT, @@ -145,11 +144,11 @@ for type_ in types_: question = DNSQuestion(type_, _TYPE_PTR, _CLASS_IN) question.unicast = qu_question - known_answers = set( + known_answers = { cast(DNSPointer, record) for record in zc.cache.get_all_by_details(type_, _TYPE_PTR, _CLASS_IN) if not record.is_stale(now) - ) + } if not qu_question and zc.question_history.suppresses( question, now, cast(Set[DNSRecord], known_answers) ): @@ -293,7 +292,7 @@ self._pending_handlers: OrderedDict[Tuple[str, str], ServiceStateChange] = OrderedDict() self._service_state_changed = Signal() self.query_scheduler = QueryScheduler(self.types, delay, _FIRST_QUERY_DELAY_RANDOM_INTERVAL) - self.queue: Optional[queue.Queue] = None + self.queue: Optional[queue.SimpleQueue] = None self.done = False self._first_request: bool = True self._next_send_timer: Optional[asyncio.TimerHandle] = None @@ -325,9 +324,9 @@ def service_state_changed(self) -> SignalRegistrationInterface: return self._service_state_changed.registration_interface - def _record_matching_type(self, record: DNSRecord) -> Optional[str]: - """Return the type if the record matches one of the types we are browsing.""" - return next((type_ for type_ in self.types if record.name.endswith(type_)), None) + def _names_matching_types(self, names: Iterable[str]) -> List[Tuple[str, str]]: + """Return the type and name for records matching the types we are browsing.""" + return [(type_, name) for name in names for type_ in self.types.intersection(possible_types(name))] def _enqueue_callback( self, @@ -353,14 +352,13 @@ ) -> None: """Process a single record update from a batch of updates.""" if isinstance(record, DNSPointer): - if record.name not in self.types: - return - if old_record is None: - self._enqueue_callback(ServiceStateChange.Added, record.name, record.alias) - elif record.is_expired(now): - self._enqueue_callback(ServiceStateChange.Removed, record.name, record.alias) - else: - self.reschedule_type(record.name, record.get_expiration_time(_EXPIRE_REFRESH_TIME_PERCENT)) + for type_ in self.types.intersection(possible_types(record.name)): + if old_record is None: + self._enqueue_callback(ServiceStateChange.Added, type_, record.alias) + elif record.is_expired(now): + self._enqueue_callback(ServiceStateChange.Removed, type_, record.alias) + else: + self.reschedule_type(type_, now, record.get_expiration_time(_EXPIRE_REFRESH_TIME_PERCENT)) return # If its expired or already exists in the cache it cannot be updated. @@ -369,17 +367,14 @@ if isinstance(record, DNSAddress): # Iterate through the DNSCache and callback any services that use this address - for service in self.zc.cache.async_entries_with_server(record.name): - type_ = self._record_matching_type(service) - if type_: - self._enqueue_callback(ServiceStateChange.Updated, type_, service.name) - break - + for type_, name in self._names_matching_types( + {service.name for service in self.zc.cache.async_entries_with_server(record.name)} + ): + self._enqueue_callback(ServiceStateChange.Updated, type_, name) return - type_ = self._record_matching_type(record) - if type_: - self._enqueue_callback(ServiceStateChange.Updated, type_, record.name) + for type_, name in self._names_matching_types((record.name,)): + self._enqueue_callback(ServiceStateChange.Updated, type_, name) def async_update_records(self, zc: 'Zeroconf', now: float, records: List[RecordUpdate]) -> None: """Callback invoked by Zeroconf when new information arrives. @@ -431,9 +426,8 @@ self._cancel_send_timer() self.zc.async_remove_listener(self) - def _generate_ready_queries(self, first_request: bool) -> List[DNSOutgoing]: + def _generate_ready_queries(self, first_request: bool, now: float) -> List[DNSOutgoing]: """Generate the service browser query for any type that is due.""" - now = current_time_millis() ready_types = self.query_scheduler.process_ready_types(now) if not ready_types: return [] @@ -448,40 +442,40 @@ async def _async_start_query_sender(self) -> None: """Start scheduling queries.""" await self.zc.async_wait_for_start() - self._async_send_ready_queries() - self._async_schedule_next() + self._async_send_ready_queries_schedule_next() def _cancel_send_timer(self) -> None: """Cancel the next send.""" if self._next_send_timer: self._next_send_timer.cancel() - def reschedule_type(self, type_: str, next_time: float) -> None: + def reschedule_type(self, type_: str, now: float, next_time: float) -> None: """Reschedule a type to be refreshed in the future.""" if self.query_scheduler.reschedule_type(type_, next_time): self._cancel_send_timer() - self._async_schedule_next() - self._async_send_ready_queries() + self._async_schedule_next(now) + self._async_send_ready_queries(now) - def _async_send_ready_queries(self) -> None: + def _async_send_ready_queries(self, now: float) -> None: """Send any ready queries.""" - outs = self._generate_ready_queries(self._first_request) + outs = self._generate_ready_queries(self._first_request, now) if outs: self._first_request = False for out in outs: self.zc.async_send(out, addr=self.addr, port=self.port) def _async_send_ready_queries_schedule_next(self) -> None: - """Send ready queries and schedule next one.""" + """Send ready queries and schedule next one checking for done first.""" if self.done or self.zc.done: return - self._async_send_ready_queries() - self._async_schedule_next() + now = current_time_millis() + self._async_send_ready_queries(now) + self._async_schedule_next(now) - def _async_schedule_next(self) -> None: + def _async_schedule_next(self, now: float) -> None: """Scheule the next time.""" assert self.zc.loop is not None - delay = millis_to_seconds(self.query_scheduler.millis_to_wait(current_time_millis())) + delay = millis_to_seconds(self.query_scheduler.millis_to_wait(now)) self._next_send_timer = self.zc.loop.call_later(delay, self._async_send_ready_queries_schedule_next) @@ -511,11 +505,11 @@ # Add the queue before the listener is installed in _setup # to ensure that events run in the dedicated thread and do # not block the event loop - self.queue = get_best_available_queue() + self.queue = queue.SimpleQueue() self.daemon = True self.start() zc.loop.call_soon_threadsafe(self._async_start) - self.name = "zeroconf-ServiceBrowser-%s-%s" % ( + self.name = "zeroconf-ServiceBrowser-{}-{}".format( '-'.join([type_[:-7] for type_ in self.types]), getattr(self, 'native_id', self.ident), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/_utils/asyncio.py new/python-zeroconf-0.38.1/zeroconf/_utils/asyncio.py --- old/python-zeroconf-0.37.0/zeroconf/_utils/asyncio.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/_utils/asyncio.py 2021-12-24 03:47:53.000000000 +0100 @@ -23,8 +23,7 @@ import asyncio import concurrent.futures import contextlib -import queue -from typing import Any, Awaitable, Coroutine, List, Optional, Set, cast +from typing import Any, Awaitable, Coroutine, Optional, Set from .time import millis_to_seconds from .._exceptions import EventLoopBlocked @@ -36,13 +35,6 @@ _WAIT_FOR_LOOP_TASKS_TIMEOUT = 3 # Must be larger than _TASK_AWAIT_TIMEOUT -def get_best_available_queue() -> queue.Queue: - """Create the best available queue type.""" - if hasattr(queue, "SimpleQueue"): - return queue.SimpleQueue() # type: ignore # pylint: disable=all - return queue.Queue() - - # Switch to asyncio.wait_for once https://bugs.python.org/issue39032 is fixed async def wait_event_or_timeout(event: asyncio.Event, timeout: float) -> None: """Wait for an event or timeout.""" @@ -67,7 +59,7 @@ await event_wait -async def _async_get_all_tasks(loop: asyncio.AbstractEventLoop) -> List[asyncio.Task]: +async def _async_get_all_tasks(loop: asyncio.AbstractEventLoop) -> Set[asyncio.Task]: """Return all tasks running.""" await asyncio.sleep(0) # flush out any call_soon_threadsafe # If there are multiple event loops running, all_tasks is not @@ -75,10 +67,8 @@ # under PyPy so we have to try a few times. for _ in range(3): with contextlib.suppress(RuntimeError): - if hasattr(asyncio, 'all_tasks'): - return asyncio.all_tasks(loop) # type: ignore # pylint: disable=no-member - return asyncio.Task.all_tasks(loop) # type: ignore # pylint: disable=no-member - return [] + return asyncio.all_tasks(loop) + return set() async def _wait_for_loop_tasks(wait_tasks: Set[asyncio.Task]) -> None: @@ -116,7 +106,7 @@ pending_tasks = set( asyncio.run_coroutine_threadsafe(_async_get_all_tasks(loop), loop).result(_GET_ALL_TASKS_TIMEOUT) ) - pending_tasks -= set(task for task in pending_tasks if task.done()) + pending_tasks -= {task for task in pending_tasks if task.done()} if pending_tasks: asyncio.run_coroutine_threadsafe(_wait_for_loop_tasks(pending_tasks), loop).result( _WAIT_FOR_LOOP_TASKS_TIMEOUT @@ -128,10 +118,5 @@ def get_running_loop() -> Optional[asyncio.AbstractEventLoop]: """Check if an event loop is already running.""" with contextlib.suppress(RuntimeError): - if hasattr(asyncio, "get_running_loop"): - return cast( - asyncio.AbstractEventLoop, - asyncio.get_running_loop(), # type: ignore # pylint: disable=no-member # noqa - ) - return asyncio._get_running_loop() # pylint: disable=no-member,protected-access + return asyncio.get_running_loop() return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/_utils/name.py new/python-zeroconf-0.38.1/zeroconf/_utils/name.py --- old/python-zeroconf-0.37.0/zeroconf/_utils/name.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/_utils/name.py 2021-12-24 03:47:53.000000000 +0100 @@ -20,6 +20,8 @@ USA """ +from typing import Set + from .._exceptions import BadTypeInNameException from ..const import ( _HAS_ASCII_CONTROL_CHARS, @@ -92,7 +94,7 @@ trailer = type_[-len(_LOCAL_TRAILER) + 1 :] has_protocol = False else: - raise BadTypeInNameException("Type '%s' must end with '%s'" % (type_, _LOCAL_TRAILER)) + raise BadTypeInNameException(f"Type '{type_}' must end with '{_LOCAL_TRAILER}'") if strict or has_protocol: service_name = remaining.pop() @@ -155,3 +157,16 @@ ) return service_name + trailer + + +def possible_types(name: str) -> Set[str]: + """Build a set of all possible types from a fully qualified name.""" + labels = name.split('.') + label_count = len(labels) + types = set() + for count in range(label_count): + parts = labels[label_count - count - 4 :] + if not parts[0].startswith('_'): + break + types.add('.'.join(parts)) + return types diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-zeroconf-0.37.0/zeroconf/_utils/net.py new/python-zeroconf-0.38.1/zeroconf/_utils/net.py --- old/python-zeroconf-0.37.0/zeroconf/_utils/net.py 2021-11-18 21:30:53.000000000 +0100 +++ new/python-zeroconf-0.38.1/zeroconf/_utils/net.py 2021-12-24 03:47:53.000000000 +0100 @@ -71,14 +71,14 @@ def get_all_addresses() -> List[str]: - return list(set(addr.ip for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv4)) + return list({addr.ip for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv4}) def get_all_addresses_v6() -> List[Tuple[Tuple[str, int, int], int]]: # IPv6 multicast uses positive indexes for interfaces # TODO: What about multi-address interfaces? return list( - set((addr.ip, iface.index) for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv6) + {(addr.ip, iface.index) for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv6} ) @@ -203,7 +203,7 @@ try: s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, loop) - except socket.error as e: + except OSError as e: if bind_addr[0] != '' or get_errno(e) != errno.EINVAL: # Fails to set on MacOS raise @@ -286,7 +286,7 @@ else: _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(cast(str, interface)) listen_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value) - except socket.error as e: + except OSError as e: _errno = get_errno(e) if _errno == errno.EADDRINUSE: log.info( ++++++ python-zeroconf.obsinfo ++++++ --- /var/tmp/diff_new_pack.494Uhp/_old 2022-01-10 23:53:47.460814208 +0100 +++ /var/tmp/diff_new_pack.494Uhp/_new 2022-01-10 23:53:47.464814211 +0100 @@ -1,6 +1,5 @@ name: python-zeroconf -version: 0.37.0 -mtime: 1637267453 -commit: 2996e642f6b1abba1dbb8242ccca4cd4b96696f6 - +version: 0.38.1 +mtime: 1640314073 +commit: 6a11f24e1fc9d73f0dbb62efd834f17a9bd451c4