ucapi-framework 1.9.2__tar.gz → 1.9.4__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.
Files changed (51) hide show
  1. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/PKG-INFO +1 -1
  2. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/pyproject.toml +1 -1
  3. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_discovery.py +67 -8
  4. ucapi_framework-1.9.4/tests/test_helpers.py +1165 -0
  5. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_setup.py +4 -3
  6. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/discovery.py +24 -21
  7. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/driver.py +11 -2
  8. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/setup.py +61 -3
  9. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework.egg-info/PKG-INFO +1 -1
  10. ucapi_framework-1.9.2/tests/test_helpers.py +0 -420
  11. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/README.md +0 -0
  12. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/setup.cfg +0 -0
  13. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_button_entity.py +0 -0
  14. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_climate_entity.py +0 -0
  15. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_config.py +0 -0
  16. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_cover_entity.py +0 -0
  17. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_device.py +0 -0
  18. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_driver.py +0 -0
  19. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_entity.py +0 -0
  20. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_factory_pattern.py +0 -0
  21. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_ir_emitter_entity.py +0 -0
  22. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_light_entity.py +0 -0
  23. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_media_player_entity.py +0 -0
  24. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_migration.py +0 -0
  25. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_remote_entity.py +0 -0
  26. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_select_entity.py +0 -0
  27. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_sensor_entity.py +0 -0
  28. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_switch_entity.py +0 -0
  29. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/tests/test_voice_assistant_entity.py +0 -0
  30. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/__init__.py +0 -0
  31. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/config.py +0 -0
  32. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/device.py +0 -0
  33. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/__init__.py +0 -0
  34. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/button.py +0 -0
  35. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/climate.py +0 -0
  36. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/cover.py +0 -0
  37. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/ir_emitter.py +0 -0
  38. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/light.py +0 -0
  39. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/media_player.py +0 -0
  40. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/remote.py +0 -0
  41. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/select.py +0 -0
  42. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/sensor.py +0 -0
  43. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/switch.py +0 -0
  44. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entities/voice_assistant.py +0 -0
  45. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/entity.py +0 -0
  46. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/helpers.py +0 -0
  47. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework/migration.py +0 -0
  48. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework.egg-info/SOURCES.txt +0 -0
  49. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework.egg-info/dependency_links.txt +0 -0
  50. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework.egg-info/requires.txt +0 -0
  51. {ucapi_framework-1.9.2 → ucapi_framework-1.9.4}/ucapi_framework.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucapi-framework
3
- Version: 1.9.2
3
+ Version: 1.9.4
4
4
  Summary: ucapi framework that provides core functionality for building integrations.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ucapi-framework"
3
- version = "1.9.2"
3
+ version = "1.9.4"
4
4
  description = "ucapi framework that provides core functionality for building integrations."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -451,28 +451,87 @@ class TestMDNSDiscovery:
451
451
 
452
452
  @pytest.mark.asyncio
453
453
  async def test_discover_with_zeroconf(self):
454
- """Test mDNS discovery with mocked zeroconf."""
454
+ """Test mDNS discovery with mocked zeroconf async classes."""
455
455
  mock_service_info = Mock()
456
456
  mock_service_info.name = "test-device._test._tcp.local."
457
457
  mock_service_info.server = "Test Device"
458
458
  mock_service_info.addresses = [b"\xc0\xa8\x01\x64"] # 192.168.1.100 as bytes
459
459
  mock_service_info.port = 8080
460
460
 
461
- mock_zeroconf = Mock()
462
- mock_service_browser = Mock()
461
+ # ServiceListener must be a real class so Listener can inherit from it
462
+ class MockServiceListener:
463
+ pass
464
+
465
+ # Track the Listener instance created inside discover()
466
+ captured_listener: dict = {}
467
+
468
+ # Mock AsyncZeroconf
469
+ mock_aiozc = AsyncMock()
470
+ mock_aiozc.zeroconf = Mock()
471
+ mock_aiozc.async_get_service_info = AsyncMock(return_value=mock_service_info)
472
+ mock_aiozc.async_close = AsyncMock()
473
+
474
+ # Mock AsyncServiceBrowser – capture the listener so we can call add_service
475
+ def mock_browser_init(zc, service_type, listener):
476
+ captured_listener["obj"] = listener
477
+ browser = Mock()
478
+ browser.async_cancel = AsyncMock()
479
+ return browser
463
480
 
464
481
  mock_zeroconf_module = Mock()
465
- mock_zeroconf_module.Zeroconf.return_value = mock_zeroconf
466
- mock_zeroconf_module.ServiceBrowser.return_value = mock_service_browser
482
+ mock_zeroconf_module.ServiceListener = MockServiceListener
483
+
484
+ mock_asyncio_module = Mock()
485
+ mock_asyncio_module.AsyncZeroconf = Mock(return_value=mock_aiozc)
486
+ mock_asyncio_module.AsyncServiceBrowser = Mock(side_effect=mock_browser_init)
487
+
488
+ with (
489
+ patch.dict(
490
+ "sys.modules",
491
+ {
492
+ "zeroconf": mock_zeroconf_module,
493
+ "zeroconf.asyncio": mock_asyncio_module,
494
+ },
495
+ ),
496
+ patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep,
497
+ ):
498
+
499
+ async def trigger_discovery(*args, **kwargs):
500
+ listener = captured_listener.get("obj")
501
+ if listener:
502
+ listener.add_service(
503
+ mock_aiozc.zeroconf,
504
+ "_test._tcp.local.",
505
+ "test-device._test._tcp.local.",
506
+ )
507
+
508
+ mock_sleep.side_effect = trigger_discovery
467
509
 
468
- with patch.dict("sys.modules", {"zeroconf": mock_zeroconf_module}):
469
510
  discovery = ConcreteMDNSDiscovery(
470
511
  service_type="_test._tcp.local.", timeout=1
471
512
  )
472
513
  devices = await discovery.discover()
473
514
 
474
- # Due to asyncio sleep and mocking complexity, this is more of a structure test
475
- assert isinstance(devices, list)
515
+ assert len(devices) == 1
516
+ assert devices[0].identifier == "test-device._test._tcp.local."
517
+ assert devices[0].name == "Test Device"
518
+ mock_aiozc.async_get_service_info.assert_called_once_with(
519
+ "_test._tcp.local.", "test-device._test._tcp.local."
520
+ )
521
+
522
+ @pytest.mark.asyncio
523
+ async def test_discover_handles_import_error(self):
524
+ """Test that discover handles missing zeroconf gracefully."""
525
+ discovery = ConcreteMDNSDiscovery(service_type="_test._tcp.local.", timeout=1)
526
+
527
+ def mock_import(name, *args, **kwargs):
528
+ if name in ("zeroconf", "zeroconf.asyncio"):
529
+ raise ImportError(f"No module named '{name}'")
530
+ return __import__(name, *args, **kwargs)
531
+
532
+ with patch("builtins.__import__", side_effect=mock_import):
533
+ devices = await discovery.discover()
534
+ assert devices == []
476
535
 
477
536
 
478
537
  class TestNetworkScanDiscovery: