ucapi 0.2.0__tar.gz → 0.3.1__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.
- {ucapi-0.2.0 → ucapi-0.3.1}/CHANGELOG.md +11 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/CONTRIBUTING.md +8 -4
- {ucapi-0.2.0/ucapi.egg-info → ucapi-0.3.1}/PKG-INFO +6 -5
- {ucapi-0.2.0 → ucapi-0.3.1}/README.md +1 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/pyproject.toml +2 -2
- {ucapi-0.2.0 → ucapi-0.3.1}/requirements.txt +1 -1
- ucapi-0.3.1/tests/test_api.py +95 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/_version.py +9 -4
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/api.py +57 -29
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/climate.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/cover.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/entity.py +1 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/light.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/media_player.py +3 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/remote.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/sensor.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/switch.py +2 -1
- {ucapi-0.2.0 → ucapi-0.3.1/ucapi.egg-info}/PKG-INFO +6 -5
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi.egg-info/SOURCES.txt +1 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi.egg-info/requires.txt +1 -1
- {ucapi-0.2.0 → ucapi-0.3.1}/LICENSE +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/docs/code_guidelines.md +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/docs/setup.md +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/README.md +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/hello_integration.json +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/hello_integration.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/remote.json +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/remote.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/remote_ui_page.json +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/setup_flow.json +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/examples/setup_flow.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/setup.cfg +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/test-requirements.txt +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/__init__.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/api_definitions.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/button.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/entities.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi/ui.py +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi.egg-info/dependency_links.txt +0 -0
- {ucapi-0.2.0 → ucapi-0.3.1}/ucapi.egg-info/top_level.txt +0 -0
|
@@ -11,6 +11,17 @@ _Changes in the next release_
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## v0.3.1 - 2024-05-14
|
|
15
|
+
### Fixed
|
|
16
|
+
- Filtered log messages may not modify original data. This sporadically removed media artwork URLs ([#27](https://github.com/unfoldedcircle/integration-python-library/pull/27)).
|
|
17
|
+
|
|
18
|
+
## v0.3.0 - 2024-04-25
|
|
19
|
+
### Added
|
|
20
|
+
- New media-player attribute MEDIA_POSITION_UPDATED_AT ([feature-and-bug-tracker#443](https://github.com/unfoldedcircle/feature-and-bug-tracker/issues/443)).
|
|
21
|
+
### Changed
|
|
22
|
+
- Filter out base64 encoded media-player image fields in entity_states response log messages ([#22](https://github.com/unfoldedcircle/integration-python-library/issues/22)).
|
|
23
|
+
- Require websockets version v14 or newer.
|
|
24
|
+
|
|
14
25
|
## v0.2.0 - 2024-04-28
|
|
15
26
|
### Added
|
|
16
27
|
- New remote-entity type. Requires remote-core / Core Simulator version 0.43.0 or newer.
|
|
@@ -25,7 +25,7 @@ We love contributions from everyone.
|
|
|
25
25
|
Either by opening a feature request describing your proposed changes before submitting code, or by contacting us on
|
|
26
26
|
one of the other [feedback channels](#feedback-speech_balloon).
|
|
27
27
|
|
|
28
|
-
Since this library is being used in integration drivers running on the embedded Remote
|
|
28
|
+
Since this library is being used in integration drivers running on the embedded UC Remote devices,
|
|
29
29
|
we have to make sure it remains compatible with the embedded runtime environment and runs smoothly.
|
|
30
30
|
|
|
31
31
|
Submitting pull requests for typos, formatting issues etc. are happily accepted and usually approved relatively quick.
|
|
@@ -36,7 +36,8 @@ With that out of the way, here's the process of creating a pull request and maki
|
|
|
36
36
|
|
|
37
37
|
1. Fork the repo.
|
|
38
38
|
|
|
39
|
-
2. Make your changes or enhancements
|
|
39
|
+
2. Make your changes or enhancements.
|
|
40
|
+
This should be done in a dedicated branch and not on the main branch to easily submit individual pull requests.
|
|
40
41
|
|
|
41
42
|
Contributed code must be licensed under the [Mozilla Public License 2.0](https://choosealicense.com/licenses/mpl-2.0/),
|
|
42
43
|
or a compatible license, if existing parts of other projects are reused (e.g. MIT licensed code).
|
|
@@ -53,13 +54,16 @@ With that out of the way, here's the process of creating a pull request and maki
|
|
|
53
54
|
|
|
54
55
|
3. Make sure your changes follow the project's code style and the lints pass described in [Code Style](docs/code_guidelines.md).
|
|
55
56
|
|
|
56
|
-
4. Push to your fork.
|
|
57
|
+
4. Push to your fork.
|
|
58
|
+
Do not include any project configuration changes in the `.idea` folder! If you are also using an IntelliJ product,
|
|
59
|
+
chances are that you're using a different IDE, version or other settings, which can cause issues.
|
|
60
|
+
For example, between IntelliJ Ultimate and PyCharm.
|
|
57
61
|
|
|
58
62
|
5. Submit a pull request.
|
|
59
63
|
|
|
60
64
|
At this point we will review the PR and give constructive feedback.
|
|
61
65
|
This is a time for discussion and improvements, and making the necessary changes will be required before we can
|
|
62
|
-
merge the contribution.
|
|
66
|
+
merge the contribution. Furthermore, all the automated checks must pass, otherwise the pull request will not be merged.
|
|
63
67
|
|
|
64
68
|
### Feedback :speech_balloon:
|
|
65
69
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ucapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Python wrapper for the Unfolded Circle Integration API
|
|
5
5
|
Author-email: Unfolded Circle ApS <hello@unfoldedcircle.com>
|
|
6
6
|
License: MPL-2.0
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://www.unfoldedcircle.com/
|
|
|
8
8
|
Project-URL: Source Code, https://github.com/unfoldedcircle/integration-python-library
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/unfoldedcircle/integration-python-library/issues
|
|
10
10
|
Project-URL: Discord, http://unfolded.chat/
|
|
11
|
-
Project-URL: Forum,
|
|
11
|
+
Project-URL: Forum, https://unfolded.community/
|
|
12
12
|
Platform: any
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.10
|
|
|
22
22
|
Description-Content-Type: text/markdown; charset=UTF-8
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: pyee>=9.0
|
|
25
|
-
Requires-Dist: websockets>=
|
|
25
|
+
Requires-Dist: websockets>=14.0
|
|
26
26
|
Requires-Dist: zeroconf>=0.120.0
|
|
27
27
|
Provides-Extra: testing
|
|
28
28
|
Requires-Dist: pylint; extra == "testing"
|
|
@@ -30,13 +30,14 @@ Requires-Dist: flake8-docstrings; extra == "testing"
|
|
|
30
30
|
Requires-Dist: flake8; extra == "testing"
|
|
31
31
|
Requires-Dist: black; extra == "testing"
|
|
32
32
|
Requires-Dist: isort; extra == "testing"
|
|
33
|
+
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
# Python API wrapper for the UC Integration API
|
|
35
36
|
[](https://pypi.org/project/ucapi)
|
|
36
37
|
[](LICENSE)
|
|
37
38
|
[](https://github.com/psf/black)
|
|
38
39
|
|
|
39
|
-
This library simplifies writing Python
|
|
40
|
+
This library simplifies writing Python-based integrations for the [Unfolded Circle Remote devices](https://www.unfoldedcircle.com/)
|
|
40
41
|
by wrapping the [WebSocket Integration API](https://github.com/unfoldedcircle/core-api/tree/main/integration-api).
|
|
41
42
|
|
|
42
43
|
It's an alpha release (in our eyes). Breaking changes are to be expected and missing features will be continuously added.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://github.com/psf/black)
|
|
5
5
|
|
|
6
|
-
This library simplifies writing Python
|
|
6
|
+
This library simplifies writing Python-based integrations for the [Unfolded Circle Remote devices](https://www.unfoldedcircle.com/)
|
|
7
7
|
by wrapping the [WebSocket Integration API](https://github.com/unfoldedcircle/core-api/tree/main/integration-api).
|
|
8
8
|
|
|
9
9
|
It's an alpha release (in our eyes). Breaking changes are to be expected and missing features will be continuously added.
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
requires-python = ">=3.10"
|
|
23
23
|
dependencies = [
|
|
24
24
|
"pyee>=9.0",
|
|
25
|
-
"websockets>=
|
|
25
|
+
"websockets>=14.0",
|
|
26
26
|
"zeroconf>=0.120.0",
|
|
27
27
|
]
|
|
28
28
|
dynamic = ["version"]
|
|
@@ -36,7 +36,7 @@ content-type = "text/markdown; charset=UTF-8"
|
|
|
36
36
|
"Source Code" = "https://github.com/unfoldedcircle/integration-python-library"
|
|
37
37
|
"Bug Reports" = "https://github.com/unfoldedcircle/integration-python-library/issues"
|
|
38
38
|
"Discord" = "http://unfolded.chat/"
|
|
39
|
-
"Forum" = "
|
|
39
|
+
"Forum" = "https://unfolded.community/"
|
|
40
40
|
|
|
41
41
|
[project.optional-dependencies]
|
|
42
42
|
testing = [
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from ucapi.api import filter_log_msg_data
|
|
5
|
+
from ucapi.media_player import Attributes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestFilterLogMsgData(unittest.TestCase):
|
|
9
|
+
|
|
10
|
+
def test_no_modification_when_no_msg_data(self):
|
|
11
|
+
data = {}
|
|
12
|
+
result = filter_log_msg_data(data)
|
|
13
|
+
self.assertEqual(result, {}, "The result should be an empty dictionary")
|
|
14
|
+
|
|
15
|
+
def test_no_changes_when_media_image_url_not_present(self):
|
|
16
|
+
data = {"msg_data": {"attributes": {"state": "playing", "volume": 50}}}
|
|
17
|
+
original = deepcopy(data)
|
|
18
|
+
|
|
19
|
+
result = filter_log_msg_data(data)
|
|
20
|
+
|
|
21
|
+
self.assertEqual(
|
|
22
|
+
result,
|
|
23
|
+
original,
|
|
24
|
+
"The result should be the same as the input when no filtered attributes are present",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def test_filtering_media_image_url_in_dict(self):
|
|
28
|
+
data = {
|
|
29
|
+
"msg_data": {
|
|
30
|
+
"attributes": {
|
|
31
|
+
"state": "playing",
|
|
32
|
+
Attributes.MEDIA_IMAGE_URL: "data:image/png;base64,encodeddata",
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
expected_result = deepcopy(data)
|
|
37
|
+
expected_result["msg_data"]["attributes"][
|
|
38
|
+
Attributes.MEDIA_IMAGE_URL
|
|
39
|
+
] = "data:***"
|
|
40
|
+
|
|
41
|
+
result = filter_log_msg_data(data)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(
|
|
44
|
+
result, expected_result, "The MEDIA_IMAGE_URL attribute should be filtered"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def test_filtering_media_image_url_in_list(self):
|
|
48
|
+
data = {
|
|
49
|
+
"msg_data": [
|
|
50
|
+
{
|
|
51
|
+
"attributes": {
|
|
52
|
+
"state": "paused",
|
|
53
|
+
Attributes.MEDIA_IMAGE_URL: "data:image/png;base64,exampledata",
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"attributes": {
|
|
58
|
+
"state": "playing",
|
|
59
|
+
Attributes.MEDIA_IMAGE_URL: "data:image/jpg;base64,exampledata",
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{"attributes": {"state": "stopped"}},
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
expected_result = deepcopy(data)
|
|
66
|
+
expected_result["msg_data"][0]["attributes"][
|
|
67
|
+
Attributes.MEDIA_IMAGE_URL
|
|
68
|
+
] = "data:***"
|
|
69
|
+
expected_result["msg_data"][1]["attributes"][
|
|
70
|
+
Attributes.MEDIA_IMAGE_URL
|
|
71
|
+
] = "data:***"
|
|
72
|
+
|
|
73
|
+
result = filter_log_msg_data(data)
|
|
74
|
+
|
|
75
|
+
self.assertEqual(
|
|
76
|
+
result,
|
|
77
|
+
expected_result,
|
|
78
|
+
"All MEDIA_IMAGE_URL attributes in the list should be filtered",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def test_input_is_not_modified(self):
|
|
82
|
+
data = {
|
|
83
|
+
"msg_data": {
|
|
84
|
+
"attributes": {
|
|
85
|
+
Attributes.MEDIA_IMAGE_URL: "data:image/png;base64,encodeddata"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
original_data = deepcopy(data)
|
|
90
|
+
|
|
91
|
+
filter_log_msg_data(data)
|
|
92
|
+
|
|
93
|
+
self.assertEqual(
|
|
94
|
+
data, original_data, "The input data should not be modified by the function"
|
|
95
|
+
)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.3.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
@@ -11,22 +11,28 @@ import logging
|
|
|
11
11
|
import os
|
|
12
12
|
import socket
|
|
13
13
|
from asyncio import AbstractEventLoop
|
|
14
|
+
from copy import deepcopy
|
|
14
15
|
from typing import Any, Callable
|
|
15
16
|
|
|
16
17
|
import websockets
|
|
17
18
|
from pyee.asyncio import AsyncIOEventEmitter
|
|
18
19
|
|
|
20
|
+
# Note: websockets v14 doesn't have websockets.server anymore
|
|
21
|
+
from websockets import serve
|
|
22
|
+
|
|
19
23
|
# workaround for pylint error: E0611: No name 'ConnectionClosedOK' in module 'websockets' (no-name-in-module) # noqa
|
|
20
24
|
from websockets.exceptions import ConnectionClosedOK
|
|
21
|
-
|
|
22
|
-
# workaround for pylint error: E1101: Module 'websockets' has no 'serve' member (no-member) # noqa
|
|
23
|
-
from websockets.server import serve
|
|
24
25
|
from zeroconf import IPVersion
|
|
25
26
|
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf
|
|
26
27
|
|
|
27
28
|
import ucapi.api_definitions as uc
|
|
28
|
-
from ucapi import media_player
|
|
29
29
|
from ucapi.entities import Entities
|
|
30
|
+
from ucapi.media_player import Attributes as MediaAttr
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from ._version import version as __version__
|
|
34
|
+
except ImportError:
|
|
35
|
+
__version__ = "unknown"
|
|
30
36
|
|
|
31
37
|
_LOG = logging.getLogger(__name__)
|
|
32
38
|
_LOG.setLevel(logging.DEBUG)
|
|
@@ -125,9 +131,10 @@ class IntegrationAPI:
|
|
|
125
131
|
)
|
|
126
132
|
|
|
127
133
|
_LOG.info(
|
|
128
|
-
"Driver is up: %s, version: %s, listening on: %s:%d",
|
|
134
|
+
"Driver is up: %s, version: %s, api: %s, listening on: %s:%d",
|
|
129
135
|
self._driver_info["driver_id"],
|
|
130
136
|
self._driver_info["version"],
|
|
137
|
+
__version__,
|
|
131
138
|
host,
|
|
132
139
|
port,
|
|
133
140
|
)
|
|
@@ -163,6 +170,7 @@ class IntegrationAPI:
|
|
|
163
170
|
_LOG.info("WS: Connection closed")
|
|
164
171
|
|
|
165
172
|
except websockets.exceptions.ConnectionClosedError as e:
|
|
173
|
+
# no idea why they made code & reason deprecated...
|
|
166
174
|
_LOG.info("WS: Connection closed with error %d: %s", e.code, e.reason)
|
|
167
175
|
|
|
168
176
|
except websockets.exceptions.WebSocketException as e:
|
|
@@ -210,6 +218,7 @@ class IntegrationAPI:
|
|
|
210
218
|
"""
|
|
211
219
|
await self._send_ws_response(websocket, req_id, "result", msg_data, status_code)
|
|
212
220
|
|
|
221
|
+
# pylint: disable=R0917
|
|
213
222
|
async def _send_ws_response(
|
|
214
223
|
self,
|
|
215
224
|
websocket,
|
|
@@ -240,7 +249,10 @@ class IntegrationAPI:
|
|
|
240
249
|
|
|
241
250
|
if websocket in self._clients:
|
|
242
251
|
data_dump = json.dumps(data)
|
|
243
|
-
_LOG.
|
|
252
|
+
if _LOG.isEnabledFor(logging.DEBUG):
|
|
253
|
+
_LOG.debug(
|
|
254
|
+
"[%s] ->: %s", websocket.remote_address, filter_log_msg_data(data)
|
|
255
|
+
)
|
|
244
256
|
await websocket.send(data_dump)
|
|
245
257
|
else:
|
|
246
258
|
_LOG.error("Error sending response: connection no longer established")
|
|
@@ -260,13 +272,12 @@ class IntegrationAPI:
|
|
|
260
272
|
"""
|
|
261
273
|
data = {"kind": "event", "msg": msg, "msg_data": msg_data, "cat": category}
|
|
262
274
|
data_dump = json.dumps(data)
|
|
263
|
-
# filter fields
|
|
264
|
-
if _LOG.isEnabledFor(logging.DEBUG):
|
|
265
|
-
data_log = json.dumps(data) if filter_log_msg_data(data) else data_dump
|
|
266
275
|
|
|
267
276
|
for websocket in self._clients:
|
|
268
277
|
if _LOG.isEnabledFor(logging.DEBUG):
|
|
269
|
-
_LOG.debug(
|
|
278
|
+
_LOG.debug(
|
|
279
|
+
"[%s] =>: %s", websocket.remote_address, filter_log_msg_data(data)
|
|
280
|
+
)
|
|
270
281
|
try:
|
|
271
282
|
await websocket.send(data_dump)
|
|
272
283
|
except websockets.exceptions.WebSocketException:
|
|
@@ -291,8 +302,9 @@ class IntegrationAPI:
|
|
|
291
302
|
|
|
292
303
|
if websocket in self._clients:
|
|
293
304
|
if _LOG.isEnabledFor(logging.DEBUG):
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
_LOG.debug(
|
|
306
|
+
"[%s] ->: %s", websocket.remote_address, filter_log_msg_data(data)
|
|
307
|
+
)
|
|
296
308
|
await websocket.send(data_dump)
|
|
297
309
|
else:
|
|
298
310
|
_LOG.error("Error sending event: connection no longer established")
|
|
@@ -469,7 +481,7 @@ class IntegrationAPI:
|
|
|
469
481
|
self, websocket, req_id: int, msg_data: dict[str, Any] | None
|
|
470
482
|
) -> None:
|
|
471
483
|
if not msg_data:
|
|
472
|
-
_LOG.warning("Ignoring
|
|
484
|
+
_LOG.warning("Ignoring entity command: called with empty msg_data")
|
|
473
485
|
await self.acknowledge_command(
|
|
474
486
|
websocket, req_id, uc.StatusCodes.BAD_REQUEST
|
|
475
487
|
)
|
|
@@ -635,6 +647,7 @@ class IntegrationAPI:
|
|
|
635
647
|
websocket, uc.WsMsgEvents.DRIVER_SETUP_CHANGE, data, uc.EventCategory.DEVICE
|
|
636
648
|
)
|
|
637
649
|
|
|
650
|
+
# pylint: disable=R0917
|
|
638
651
|
async def request_driver_setup_user_confirmation(
|
|
639
652
|
self,
|
|
640
653
|
websocket,
|
|
@@ -852,27 +865,42 @@ def local_hostname() -> str:
|
|
|
852
865
|
)
|
|
853
866
|
|
|
854
867
|
|
|
855
|
-
def filter_log_msg_data(data: dict[str, Any]) ->
|
|
868
|
+
def filter_log_msg_data(data: dict[str, Any]) -> dict[str, Any]:
|
|
856
869
|
"""
|
|
857
870
|
Filter attribute fields to exclude for log messages in the given msg data dict.
|
|
858
871
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
872
|
+
- Attributes are filtered in `data["msg_data"]`:
|
|
873
|
+
- dict object: key `attributes`
|
|
874
|
+
- list object: every list item `attributes`
|
|
862
875
|
- Filtered attributes: `MEDIA_IMAGE_URL`
|
|
863
876
|
|
|
864
877
|
:param data: the message data dict
|
|
865
|
-
:return:
|
|
878
|
+
:return: copy of the message data dict with filtered attributes
|
|
866
879
|
"""
|
|
880
|
+
# do not modify the original dict
|
|
881
|
+
log_upd = deepcopy(data)
|
|
882
|
+
if not log_upd:
|
|
883
|
+
return {}
|
|
884
|
+
|
|
867
885
|
# filter out base64 encoded images in the media player's media_image_url attribute
|
|
868
|
-
if
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
886
|
+
if "msg_data" in log_upd:
|
|
887
|
+
if (
|
|
888
|
+
"attributes" in log_upd["msg_data"]
|
|
889
|
+
and MediaAttr.MEDIA_IMAGE_URL in log_upd["msg_data"]["attributes"]
|
|
890
|
+
and log_upd["msg_data"]["attributes"][MediaAttr.MEDIA_IMAGE_URL].startswith(
|
|
891
|
+
"data:"
|
|
892
|
+
)
|
|
893
|
+
):
|
|
894
|
+
log_upd["msg_data"]["attributes"][MediaAttr.MEDIA_IMAGE_URL] = "data:***"
|
|
895
|
+
elif isinstance(log_upd["msg_data"], list):
|
|
896
|
+
for item in log_upd["msg_data"]:
|
|
897
|
+
if (
|
|
898
|
+
"attributes" in item
|
|
899
|
+
and MediaAttr.MEDIA_IMAGE_URL in item["attributes"]
|
|
900
|
+
and item["attributes"][MediaAttr.MEDIA_IMAGE_URL].startswith(
|
|
901
|
+
"data:"
|
|
902
|
+
)
|
|
903
|
+
):
|
|
904
|
+
item["attributes"][MediaAttr.MEDIA_IMAGE_URL] = "data:***"
|
|
905
|
+
|
|
906
|
+
return log_upd
|
|
@@ -78,6 +78,7 @@ class Attributes(str, Enum):
|
|
|
78
78
|
MUTED = "muted"
|
|
79
79
|
MEDIA_DURATION = "media_duration"
|
|
80
80
|
MEDIA_POSITION = "media_position"
|
|
81
|
+
MEDIA_POSITION_UPDATED_AT = "media_position_updated_at"
|
|
81
82
|
MEDIA_TYPE = "media_type"
|
|
82
83
|
MEDIA_IMAGE_URL = "media_image_url"
|
|
83
84
|
MEDIA_TITLE = "media_title"
|
|
@@ -193,8 +194,9 @@ class MediaPlayer(Entity):
|
|
|
193
194
|
|
|
194
195
|
See https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_media_player.md
|
|
195
196
|
for more information.
|
|
196
|
-
"""
|
|
197
|
+
"""
|
|
197
198
|
|
|
199
|
+
# pylint: disable=R0917
|
|
198
200
|
def __init__(
|
|
199
201
|
self,
|
|
200
202
|
identifier: str,
|
|
@@ -119,8 +119,9 @@ class Remote(Entity):
|
|
|
119
119
|
|
|
120
120
|
See https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_remote.md
|
|
121
121
|
for more information.
|
|
122
|
-
"""
|
|
122
|
+
"""
|
|
123
123
|
|
|
124
|
+
# pylint: disable=R0917
|
|
124
125
|
def __init__(
|
|
125
126
|
self,
|
|
126
127
|
identifier: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ucapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Python wrapper for the Unfolded Circle Integration API
|
|
5
5
|
Author-email: Unfolded Circle ApS <hello@unfoldedcircle.com>
|
|
6
6
|
License: MPL-2.0
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://www.unfoldedcircle.com/
|
|
|
8
8
|
Project-URL: Source Code, https://github.com/unfoldedcircle/integration-python-library
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/unfoldedcircle/integration-python-library/issues
|
|
10
10
|
Project-URL: Discord, http://unfolded.chat/
|
|
11
|
-
Project-URL: Forum,
|
|
11
|
+
Project-URL: Forum, https://unfolded.community/
|
|
12
12
|
Platform: any
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.10
|
|
|
22
22
|
Description-Content-Type: text/markdown; charset=UTF-8
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: pyee>=9.0
|
|
25
|
-
Requires-Dist: websockets>=
|
|
25
|
+
Requires-Dist: websockets>=14.0
|
|
26
26
|
Requires-Dist: zeroconf>=0.120.0
|
|
27
27
|
Provides-Extra: testing
|
|
28
28
|
Requires-Dist: pylint; extra == "testing"
|
|
@@ -30,13 +30,14 @@ Requires-Dist: flake8-docstrings; extra == "testing"
|
|
|
30
30
|
Requires-Dist: flake8; extra == "testing"
|
|
31
31
|
Requires-Dist: black; extra == "testing"
|
|
32
32
|
Requires-Dist: isort; extra == "testing"
|
|
33
|
+
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
# Python API wrapper for the UC Integration API
|
|
35
36
|
[](https://pypi.org/project/ucapi)
|
|
36
37
|
[](LICENSE)
|
|
37
38
|
[](https://github.com/psf/black)
|
|
38
39
|
|
|
39
|
-
This library simplifies writing Python
|
|
40
|
+
This library simplifies writing Python-based integrations for the [Unfolded Circle Remote devices](https://www.unfoldedcircle.com/)
|
|
40
41
|
by wrapping the [WebSocket Integration API](https://github.com/unfoldedcircle/core-api/tree/main/integration-api).
|
|
41
42
|
|
|
42
43
|
It's an alpha release (in our eyes). Breaking changes are to be expected and missing features will be continuously added.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|