ucapi 0.3.1__tar.gz → 0.4.0__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 (40) hide show
  1. {ucapi-0.3.1 → ucapi-0.4.0}/CHANGELOG.md +19 -2
  2. {ucapi-0.3.1/ucapi.egg-info → ucapi-0.4.0}/PKG-INFO +2 -2
  3. {ucapi-0.3.1 → ucapi-0.4.0}/pyproject.toml +1 -1
  4. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/_version.py +16 -3
  5. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/api.py +30 -10
  6. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/api_definitions.py +10 -0
  7. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/entity.py +1 -0
  8. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/ui.py +3 -0
  9. {ucapi-0.3.1 → ucapi-0.4.0/ucapi.egg-info}/PKG-INFO +2 -2
  10. {ucapi-0.3.1 → ucapi-0.4.0}/CONTRIBUTING.md +0 -0
  11. {ucapi-0.3.1 → ucapi-0.4.0}/LICENSE +0 -0
  12. {ucapi-0.3.1 → ucapi-0.4.0}/README.md +0 -0
  13. {ucapi-0.3.1 → ucapi-0.4.0}/docs/code_guidelines.md +0 -0
  14. {ucapi-0.3.1 → ucapi-0.4.0}/docs/setup.md +0 -0
  15. {ucapi-0.3.1 → ucapi-0.4.0}/examples/README.md +0 -0
  16. {ucapi-0.3.1 → ucapi-0.4.0}/examples/hello_integration.json +0 -0
  17. {ucapi-0.3.1 → ucapi-0.4.0}/examples/hello_integration.py +0 -0
  18. {ucapi-0.3.1 → ucapi-0.4.0}/examples/remote.json +0 -0
  19. {ucapi-0.3.1 → ucapi-0.4.0}/examples/remote.py +0 -0
  20. {ucapi-0.3.1 → ucapi-0.4.0}/examples/remote_ui_page.json +0 -0
  21. {ucapi-0.3.1 → ucapi-0.4.0}/examples/setup_flow.json +0 -0
  22. {ucapi-0.3.1 → ucapi-0.4.0}/examples/setup_flow.py +0 -0
  23. {ucapi-0.3.1 → ucapi-0.4.0}/requirements.txt +0 -0
  24. {ucapi-0.3.1 → ucapi-0.4.0}/setup.cfg +0 -0
  25. {ucapi-0.3.1 → ucapi-0.4.0}/test-requirements.txt +0 -0
  26. {ucapi-0.3.1 → ucapi-0.4.0}/tests/test_api.py +0 -0
  27. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/__init__.py +0 -0
  28. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/button.py +0 -0
  29. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/climate.py +0 -0
  30. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/cover.py +0 -0
  31. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/entities.py +0 -0
  32. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/light.py +0 -0
  33. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/media_player.py +0 -0
  34. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/remote.py +0 -0
  35. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/sensor.py +0 -0
  36. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi/switch.py +0 -0
  37. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi.egg-info/SOURCES.txt +0 -0
  38. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi.egg-info/dependency_links.txt +0 -0
  39. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi.egg-info/requires.txt +0 -0
  40. {ucapi-0.3.1 → ucapi-0.4.0}/ucapi.egg-info/top_level.txt +0 -0
@@ -11,11 +11,28 @@ _Changes in the next release_
11
11
 
12
12
  ---
13
13
 
14
- ## v0.3.1 - 2024-05-14
14
+ ## v0.4.0 - 2025-11-24
15
+ ### Breaking Changes
16
+ - A WebSocket disconnection no longer emits the `DISCONNECT` event, but the new `CLIENT_DISCONNECTED` event ([#35](https://github.com/unfoldedcircle/integration-python-library/pull/35)).
17
+
18
+ ### Added
19
+ - New `CLIENT_CONNECTED` event is emitted when a WebSocket client connects ([#35](https://github.com/unfoldedcircle/integration-python-library/pull/35)).
20
+ - WebSocket client identification in disconnect log statements.
21
+
22
+ ### Fixed
23
+ - Null reference exception in log filter ([#33](https://github.com/unfoldedcircle/integration-python-library/pull/33)).
24
+ - Set changed size during iteration for WS broadcast ([#36](https://github.com/unfoldedcircle/integration-python-library/pull/36)).
25
+
26
+ ## v0.3.2 - 2025-09-17
27
+ ### Changed
28
+ - Add support for IR Emitter EntityType ([#31](https://github.com/unfoldedcircle/integration-python-library/pull/31)).
29
+ - Add stop, record and menu for remote entity buttons ([#32](https://github.com/unfoldedcircle/integration-python-library/pull/32)).
30
+
31
+ ## v0.3.1 - 2025-05-14
15
32
  ### Fixed
16
33
  - 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
34
 
18
- ## v0.3.0 - 2024-04-25
35
+ ## v0.3.0 - 2025-04-25
19
36
  ### Added
20
37
  - New media-player attribute MEDIA_POSITION_UPDATED_AT ([feature-and-bug-tracker#443](https://github.com/unfoldedcircle/feature-and-bug-tracker/issues/443)).
21
38
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucapi
3
- Version: 0.3.1
3
+ Version: 0.4.0
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
@@ -10,7 +10,7 @@ Project-URL: Bug Reports, https://github.com/unfoldedcircle/integration-python-l
10
10
  Project-URL: Discord, http://unfolded.chat/
11
11
  Project-URL: Forum, https://unfolded.community/
12
12
  Platform: any
13
- Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
16
16
  Classifier: Operating System :: OS Independent
@@ -10,7 +10,7 @@ authors = [
10
10
  license = {text = "MPL-2.0"}
11
11
  description = "Python wrapper for the Unfolded Circle Integration API"
12
12
  classifiers = [
13
- "Development Status :: 3 - Alpha",
13
+ "Development Status :: 4 - Beta",
14
14
  "Intended Audience :: Developers",
15
15
  "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
16
16
  "Operating System :: OS Independent",
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.3.1'
21
- __version_tuple__ = version_tuple = (0, 3, 1)
31
+ __version__ = version = '0.4.0'
32
+ __version_tuple__ = version_tuple = (0, 4, 0)
33
+
34
+ __commit_id__ = commit_id = 'gca3836121'
@@ -162,24 +162,35 @@ class IntegrationAPI:
162
162
  # authenticate on connection
163
163
  await self._authenticate(websocket, True)
164
164
 
165
+ self._events.emit(uc.Events.CLIENT_CONNECTED)
166
+
165
167
  async for message in websocket:
166
168
  # process message
167
169
  await self._process_ws_message(websocket, message)
168
170
 
169
171
  except ConnectionClosedOK:
170
- _LOG.info("WS: Connection closed")
172
+ _LOG.info("[%s] WS: Connection closed", websocket.remote_address)
171
173
 
172
174
  except websockets.exceptions.ConnectionClosedError as e:
173
175
  # no idea why they made code & reason deprecated...
174
- _LOG.info("WS: Connection closed with error %d: %s", e.code, e.reason)
176
+ _LOG.info(
177
+ "[%s] WS: Connection closed with error %d: %s",
178
+ websocket.remote_address,
179
+ e.code,
180
+ e.reason,
181
+ )
175
182
 
176
183
  except websockets.exceptions.WebSocketException as e:
177
- _LOG.error("WS: Connection closed due to processing error: %s", e)
184
+ _LOG.error(
185
+ "[%s] WS: Connection closed due to processing error: %s",
186
+ websocket.remote_address,
187
+ e,
188
+ )
178
189
 
179
190
  finally:
180
191
  self._clients.remove(websocket)
181
- _LOG.info("WS: Client removed")
182
- self._events.emit(uc.Events.DISCONNECT)
192
+ _LOG.info("[%s] WS: Client removed", websocket.remote_address)
193
+ self._events.emit(uc.Events.CLIENT_DISCONNECTED)
183
194
 
184
195
  async def _send_ok_result(
185
196
  self, websocket, req_id: int, msg_data: dict[str, Any] | list | None = None
@@ -273,7 +284,7 @@ class IntegrationAPI:
273
284
  data = {"kind": "event", "msg": msg, "msg_data": msg_data, "cat": category}
274
285
  data_dump = json.dumps(data)
275
286
 
276
- for websocket in self._clients:
287
+ for websocket in self._clients.copy():
277
288
  if _LOG.isEnabledFor(logging.DEBUG):
278
289
  _LOG.debug(
279
290
  "[%s] =>: %s", websocket.remote_address, filter_log_msg_data(data)
@@ -763,6 +774,11 @@ class IntegrationAPI:
763
774
  # Properties #
764
775
  ##############
765
776
 
777
+ @property
778
+ def client_count(self) -> int:
779
+ """Return number of WebSocket clients."""
780
+ return len(self._clients)
781
+
766
782
  @property
767
783
  def device_state(self) -> uc.DeviceStates:
768
784
  """
@@ -887,9 +903,12 @@ def filter_log_msg_data(data: dict[str, Any]) -> dict[str, Any]:
887
903
  if (
888
904
  "attributes" in log_upd["msg_data"]
889
905
  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:"
906
+ and (
907
+ media_image_url := log_upd["msg_data"]["attributes"][
908
+ MediaAttr.MEDIA_IMAGE_URL
909
+ ]
892
910
  )
911
+ and media_image_url.startswith("data:")
893
912
  ):
894
913
  log_upd["msg_data"]["attributes"][MediaAttr.MEDIA_IMAGE_URL] = "data:***"
895
914
  elif isinstance(log_upd["msg_data"], list):
@@ -897,9 +916,10 @@ def filter_log_msg_data(data: dict[str, Any]) -> dict[str, Any]:
897
916
  if (
898
917
  "attributes" in item
899
918
  and MediaAttr.MEDIA_IMAGE_URL in item["attributes"]
900
- and item["attributes"][MediaAttr.MEDIA_IMAGE_URL].startswith(
901
- "data:"
919
+ and (
920
+ media_image_url := item["attributes"][MediaAttr.MEDIA_IMAGE_URL]
902
921
  )
922
+ and media_image_url.startswith("data:")
903
923
  ):
904
924
  item["attributes"][MediaAttr.MEDIA_IMAGE_URL] = "data:***"
905
925
 
@@ -82,13 +82,23 @@ class WsMsgEvents(str, Enum):
82
82
  class Events(str, Enum):
83
83
  """Internal library events."""
84
84
 
85
+ CLIENT_CONNECTED = "client_connected"
86
+ """WebSocket client connected."""
87
+ CLIENT_DISCONNECTED = "client_disconnected"
88
+ """WebSocket client disconnected."""
85
89
  ENTITY_ATTRIBUTES_UPDATED = "entity_attributes_updated"
86
90
  SUBSCRIBE_ENTITIES = "subscribe_entities"
91
+ """Integration API `subscribe_events` message."""
87
92
  UNSUBSCRIBE_ENTITIES = "unsubscribe_entities"
93
+ """Integration API `unsubscribe_events` message."""
88
94
  CONNECT = "connect"
95
+ """Integration-API `connect` event message."""
89
96
  DISCONNECT = "disconnect"
97
+ """Integration-API `disconnect` event message."""
90
98
  ENTER_STANDBY = "enter_standby"
99
+ """Integration-API `enter_standby` event message."""
91
100
  EXIT_STANDBY = "exit_standby"
101
+ """Integration-API `exit_standby` event message."""
92
102
 
93
103
 
94
104
  # Does EventCategory need to be public?
@@ -26,6 +26,7 @@ class EntityTypes(str, Enum):
26
26
  REMOTE = "remote"
27
27
  SENSOR = "sensor"
28
28
  SWITCH = "switch"
29
+ IR_EMITTER = "ir_emitter"
29
30
 
30
31
 
31
32
  class Entity:
@@ -41,6 +41,9 @@ class Buttons(str, Enum):
41
41
  PLAY = "PLAY"
42
42
  NEXT = "NEXT"
43
43
  POWER = "POWER"
44
+ STOP = "STOP"
45
+ RECORD = "RECORD"
46
+ MENU = "MENU"
44
47
 
45
48
 
46
49
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucapi
3
- Version: 0.3.1
3
+ Version: 0.4.0
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
@@ -10,7 +10,7 @@ Project-URL: Bug Reports, https://github.com/unfoldedcircle/integration-python-l
10
10
  Project-URL: Discord, http://unfolded.chat/
11
11
  Project-URL: Forum, https://unfolded.community/
12
12
  Platform: any
13
- Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
16
16
  Classifier: Operating System :: OS Independent
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes