ucapi-framework 1.2.2__tar.gz → 1.6.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 (28) hide show
  1. {ucapi_framework-1.2.2/ucapi_framework.egg-info → ucapi_framework-1.6.4}/PKG-INFO +51 -3
  2. ucapi_framework-1.2.2/PKG-INFO → ucapi_framework-1.6.4/README.md +49 -11
  3. ucapi_framework-1.6.4/pyproject.toml +102 -0
  4. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/tests/test_device.py +6 -65
  5. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/tests/test_driver.py +849 -147
  6. ucapi_framework-1.6.4/tests/test_entity.py +538 -0
  7. ucapi_framework-1.6.4/tests/test_factory_pattern.py +440 -0
  8. ucapi_framework-1.6.4/tests/test_helpers.py +412 -0
  9. ucapi_framework-1.6.4/tests/test_migration.py +1430 -0
  10. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/tests/test_setup.py +646 -10
  11. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/__init__.py +39 -2
  12. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/config.py +23 -10
  13. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/device.py +125 -17
  14. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/discovery.py +2 -2
  15. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/driver.py +541 -283
  16. ucapi_framework-1.6.4/ucapi_framework/entity.py +324 -0
  17. ucapi_framework-1.6.4/ucapi_framework/helpers.py +278 -0
  18. ucapi_framework-1.6.4/ucapi_framework/migration.py +864 -0
  19. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework/setup.py +822 -52
  20. ucapi_framework-1.2.2/README.md → ucapi_framework-1.6.4/ucapi_framework.egg-info/PKG-INFO +59 -1
  21. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework.egg-info/SOURCES.txt +7 -0
  22. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework.egg-info/requires.txt +1 -1
  23. ucapi_framework-1.2.2/pyproject.toml +0 -34
  24. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/setup.cfg +0 -0
  25. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/tests/test_config.py +0 -0
  26. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/tests/test_discovery.py +0 -0
  27. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework.egg-info/dependency_links.txt +0 -0
  28. {ucapi_framework-1.2.2 → ucapi_framework-1.6.4}/ucapi_framework.egg-info/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucapi-framework
3
- Version: 1.2.2
3
+ Version: 1.6.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
7
7
  Requires-Dist: pyee>=9.0.0
8
- Requires-Dist: ucapi>=0.4.0
8
+ Requires-Dist: ucapi>=0.5.1
9
9
  Requires-Dist: aiohttp>=3.9.0
10
10
 
11
11
  [![Tests](https://github.com/jackjpowell/ucapi-framework/actions/workflows/test.yml/badge.svg)](https://github.com/jackjpowell/ucapi-framework/actions/workflows/test.yml)
@@ -14,7 +14,7 @@ Requires-Dist: aiohttp>=3.9.0
14
14
 
15
15
  # UCAPI Framework
16
16
 
17
- A framework for building Unfolded Circle Remote integrations that handles the repetitive parts of integration development so you can focus on what's important.
17
+ A framework for building Unfolded Circle Remote integrations that handles the repetitive parts of integration development so you can focus on what's important. Full documentation is available at [https://jackjpowell.github.io/ucapi-framework/](https://jackjpowell.github.io/ucapi-framework/)
18
18
 
19
19
  ## What This Solves
20
20
 
@@ -191,6 +191,9 @@ Total: ~295 lines of integration code vs ~885 lines previously. And the new code
191
191
 
192
192
  If you have an existing integration, see [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) for step-by-step instructions with before/after examples.
193
193
 
194
+ For upgrading from a previous version of ucapi-framework:
195
+ - **[Upgrading to 1.6.0](https://jackjpowell.github.io/ucapi-framework/upgrade-to-1.6.0/)** - New dynamic entity management features
196
+
194
197
  ## Requirements
195
198
 
196
199
  - Python 3.11+
@@ -219,6 +222,51 @@ mkdocs serve
219
222
 
220
223
  Visit <http://127.0.0.1:8000> to view the docs.
221
224
 
225
+ ## Development
226
+
227
+ ### Setup
228
+
229
+ ```bash
230
+ # Install development dependencies (includes ruff)
231
+ uv sync --group dev
232
+ ```
233
+
234
+ Git hooks are automatically active from the `git-hooks/` directory:
235
+
236
+ - **pre-commit**: Runs `ruff check --fix` and `ruff format` via `uv run`
237
+
238
+ All development tools run through `uv` and are configured in `pyproject.toml`.
239
+
240
+ ### Code Quality
241
+
242
+ This project uses [Ruff](https://github.com/astral-sh/ruff) for both linting and formatting.
243
+
244
+ ```bash
245
+ # Run linter
246
+ ruff check
247
+
248
+ # Run linter with auto-fix
249
+ ruff check --fix
250
+
251
+ # Run formatter
252
+ ruff format
253
+
254
+ # Check formatting without making changes
255
+ ruff format --check
256
+ ```
257
+
258
+ Ruff is configured in `pyproject.toml` to match Black's formatting style and includes Flake8-compatible rules.
259
+
260
+ ### Testing
261
+
262
+ ```bash
263
+ # Run all tests
264
+ pytest
265
+
266
+ # Run with coverage
267
+ pytest --cov=ucapi_framework --cov-report=term-missing
268
+ ```
269
+
222
270
  ## License
223
271
 
224
272
  Mozilla Public License Version 2.0
@@ -1,20 +1,10 @@
1
- Metadata-Version: 2.4
2
- Name: ucapi-framework
3
- Version: 1.2.2
4
- Summary: ucapi framework that provides core functionality for building integrations.
5
- Requires-Python: >=3.11
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: pyee>=9.0.0
8
- Requires-Dist: ucapi>=0.4.0
9
- Requires-Dist: aiohttp>=3.9.0
10
-
11
1
  [![Tests](https://github.com/jackjpowell/ucapi-framework/actions/workflows/test.yml/badge.svg)](https://github.com/jackjpowell/ucapi-framework/actions/workflows/test.yml)
12
2
  [![Discord](https://badgen.net/discord/online-members/zGVYf58)](https://discord.gg/zGVYf58)
13
3
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee&nbsp;☕-FFDD00?logo=buy-me-a-coffee&logoColor=white&labelColor=grey)](https://buymeacoffee.com/jackpowell)
14
4
 
15
5
  # UCAPI Framework
16
6
 
17
- A framework for building Unfolded Circle Remote integrations that handles the repetitive parts of integration development so you can focus on what's important.
7
+ A framework for building Unfolded Circle Remote integrations that handles the repetitive parts of integration development so you can focus on what's important. Full documentation is available at [https://jackjpowell.github.io/ucapi-framework/](https://jackjpowell.github.io/ucapi-framework/)
18
8
 
19
9
  ## What This Solves
20
10
 
@@ -191,6 +181,9 @@ Total: ~295 lines of integration code vs ~885 lines previously. And the new code
191
181
 
192
182
  If you have an existing integration, see [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) for step-by-step instructions with before/after examples.
193
183
 
184
+ For upgrading from a previous version of ucapi-framework:
185
+ - **[Upgrading to 1.6.0](https://jackjpowell.github.io/ucapi-framework/upgrade-to-1.6.0/)** - New dynamic entity management features
186
+
194
187
  ## Requirements
195
188
 
196
189
  - Python 3.11+
@@ -219,6 +212,51 @@ mkdocs serve
219
212
 
220
213
  Visit <http://127.0.0.1:8000> to view the docs.
221
214
 
215
+ ## Development
216
+
217
+ ### Setup
218
+
219
+ ```bash
220
+ # Install development dependencies (includes ruff)
221
+ uv sync --group dev
222
+ ```
223
+
224
+ Git hooks are automatically active from the `git-hooks/` directory:
225
+
226
+ - **pre-commit**: Runs `ruff check --fix` and `ruff format` via `uv run`
227
+
228
+ All development tools run through `uv` and are configured in `pyproject.toml`.
229
+
230
+ ### Code Quality
231
+
232
+ This project uses [Ruff](https://github.com/astral-sh/ruff) for both linting and formatting.
233
+
234
+ ```bash
235
+ # Run linter
236
+ ruff check
237
+
238
+ # Run linter with auto-fix
239
+ ruff check --fix
240
+
241
+ # Run formatter
242
+ ruff format
243
+
244
+ # Check formatting without making changes
245
+ ruff format --check
246
+ ```
247
+
248
+ Ruff is configured in `pyproject.toml` to match Black's formatting style and includes Flake8-compatible rules.
249
+
250
+ ### Testing
251
+
252
+ ```bash
253
+ # Run all tests
254
+ pytest
255
+
256
+ # Run with coverage
257
+ pytest --cov=ucapi_framework --cov-report=term-missing
258
+ ```
259
+
222
260
  ## License
223
261
 
224
262
  Mozilla Public License Version 2.0
@@ -0,0 +1,102 @@
1
+ [project]
2
+ name = "ucapi-framework"
3
+ version = "1.6.4"
4
+ description = "ucapi framework that provides core functionality for building integrations."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "pyee>=9.0.0",
9
+ "ucapi>=0.5.1",
10
+ "aiohttp>=3.9.0",
11
+ ]
12
+
13
+ [dependency-groups]
14
+ dev = [
15
+ "pytest>=9.0.1",
16
+ "pytest-asyncio>=0.23.0",
17
+ "pytest-cov>=4.1.0",
18
+ "ruff>=0.8.0",
19
+ "ty>=0.0.4",
20
+ ]
21
+ docs = [
22
+ "mkdocs-material>=9.5.0",
23
+ "mkdocstrings[python]>=0.24.0",
24
+ "mkdocstrings-python>=1.0.0",
25
+ ]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
29
+ python_files = ["test_*.py"]
30
+ python_classes = ["Test*"]
31
+ python_functions = ["test_*"]
32
+ asyncio_mode = "auto"
33
+ addopts = [
34
+ "-v",
35
+ "--tb=short",
36
+ "--strict-markers",
37
+ ]
38
+
39
+ [tool.ruff]
40
+ # Exclude a variety of commonly ignored directories.
41
+ exclude = [
42
+ ".bzr",
43
+ ".direnv",
44
+ ".eggs",
45
+ ".git",
46
+ ".git-rewrite",
47
+ ".hg",
48
+ ".ipynb_checkpoints",
49
+ ".mypy_cache",
50
+ ".nox",
51
+ ".pants.d",
52
+ ".pyenv",
53
+ ".pytest_cache",
54
+ ".pytype",
55
+ ".ruff_cache",
56
+ ".svn",
57
+ ".tox",
58
+ ".venv",
59
+ ".vscode",
60
+ "__pypackages__",
61
+ "_build",
62
+ "buck-out",
63
+ "build",
64
+ "dist",
65
+ "node_modules",
66
+ "site-packages",
67
+ "venv",
68
+ ]
69
+
70
+ # Same as Black.
71
+ line-length = 88
72
+ indent-width = 4
73
+
74
+ # Assume Python 3.11
75
+ target-version = "py311"
76
+
77
+ [tool.ruff.lint]
78
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
79
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
80
+ # McCabe complexity (`C901`) by default.
81
+ select = ["E4", "E7", "E9", "F"]
82
+ ignore = []
83
+
84
+ # Allow fix for all enabled rules (when `--fix`) is provided.
85
+ fixable = ["ALL"]
86
+ unfixable = []
87
+
88
+ # Allow unused variables when underscore-prefixed.
89
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
90
+
91
+ [tool.ruff.format]
92
+ # Like Black, use double quotes for strings.
93
+ quote-style = "double"
94
+
95
+ # Like Black, indent with spaces, rather than tabs.
96
+ indent-style = "space"
97
+
98
+ # Like Black, respect magic trailing commas.
99
+ skip-magic-trailing-comma = false
100
+
101
+ # Like Black, automatically detect the appropriate line ending.
102
+ line-ending = "auto"
@@ -536,39 +536,6 @@ class TestStatelessHTTPDevice:
536
536
  assert device._is_connected is False
537
537
  assert len(events_emitted) == 1
538
538
 
539
- @pytest.mark.asyncio
540
- @pytest.mark.asyncio
541
- @pytest.mark.skip(
542
- reason="Complex async context manager mocking - implementation verified manually"
543
- )
544
- async def test_http_request(self, mock_device_config, event_loop):
545
- """Test HTTP request method."""
546
- device = ConcreteStatelessHTTPDevice(mock_device_config, loop=event_loop)
547
-
548
- mock_response = AsyncMock()
549
- mock_response.raise_for_status = Mock()
550
- mock_response.status = 200
551
-
552
- with patch("aiohttp.ClientSession") as mock_session_class:
553
- mock_session = AsyncMock()
554
- mock_session.__aenter__.return_value = mock_session
555
- mock_session.__aexit__.return_value = AsyncMock()
556
-
557
- # Create a proper async context manager for the request
558
- mock_request_ctx = AsyncMock()
559
- mock_request_ctx.__aenter__.return_value = mock_response
560
- mock_request_ctx.__aexit__.return_value = AsyncMock()
561
- mock_session.request.return_value = mock_request_ctx
562
-
563
- mock_session_class.return_value = mock_session
564
-
565
- # Just verify the request completes without error
566
- # Note: The response object can't be tested directly since it's used within a context manager
567
- mock_session.request.assert_not_called() # Before call
568
- await device._http_request("GET", "http://test.com")
569
- mock_session.request.assert_called_once_with("GET", "http://test.com")
570
- mock_response.raise_for_status.assert_called_once()
571
-
572
539
 
573
540
  class TestPollingDevice:
574
541
  """Tests for PollingDevice."""
@@ -1001,32 +968,6 @@ class TestPersistentConnectionDevice:
1001
968
 
1002
969
  assert device.connection_closed is True
1003
970
 
1004
- @pytest.mark.asyncio
1005
- @pytest.mark.skip(reason="Timing-sensitive test - reconnection backoff varies")
1006
- async def test_reconnection_with_backoff(self, mock_device_config, event_loop):
1007
- """Test reconnection with exponential backoff."""
1008
-
1009
- class FailingDevice(ConcretePersistentConnectionDevice):
1010
- def __init__(self, *args, **kwargs):
1011
- super().__init__(*args, **kwargs)
1012
- self.connection_attempts = 0
1013
-
1014
- async def establish_connection(self):
1015
- self.connection_attempts += 1
1016
- if self.connection_attempts < 3:
1017
- raise Exception("Connection failed")
1018
- return await super().establish_connection()
1019
-
1020
- device = FailingDevice(mock_device_config, loop=event_loop, backoff_max=1)
1021
-
1022
- await device.connect()
1023
- await asyncio.sleep(1.5) # Wait for reconnection attempts with backoff
1024
-
1025
- # Should have made multiple connection attempts
1026
- assert device.connection_attempts >= 2
1027
-
1028
- await device.disconnect()
1029
-
1030
971
  @pytest.mark.asyncio
1031
972
  async def test_maintain_connection_called(self, mock_device_config, event_loop):
1032
973
  """Test that maintain_connection is called after connection."""
@@ -1455,12 +1396,12 @@ class TestDeviceEvents:
1455
1396
 
1456
1397
  def test_device_events_values(self):
1457
1398
  """Test that DeviceEvents enum has expected values."""
1458
- assert DeviceEvents.CONNECTING == 0
1459
- assert DeviceEvents.CONNECTED == 1
1460
- assert DeviceEvents.DISCONNECTED == 2
1461
- assert DeviceEvents.PAIRED == 3
1462
- assert DeviceEvents.ERROR == 4
1463
- assert DeviceEvents.UPDATE == 5
1399
+ assert DeviceEvents.CONNECTING == "DEVICE_CONNECTING"
1400
+ assert DeviceEvents.CONNECTED == "DEVICE_CONNECTED"
1401
+ assert DeviceEvents.DISCONNECTED == "DEVICE_DISCONNECTED"
1402
+ assert DeviceEvents.PAIRED == "DEVICE_PAIRED"
1403
+ assert DeviceEvents.ERROR == "DEVICE_ERROR"
1404
+ assert DeviceEvents.UPDATE == "DEVICE_UPDATE"
1464
1405
 
1465
1406
 
1466
1407
  class ConcreteExternalClientDevice(ExternalClientDevice):