python-ember-mug 1.1.1__tar.gz → 1.2.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 (28) hide show
  1. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/PKG-INFO +4 -2
  2. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/__init__.py +1 -1
  3. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/consts.py +2 -2
  4. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/scanner.py +13 -5
  5. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/utils.py +1 -1
  6. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/pyproject.toml +31 -35
  7. python_ember_mug-1.1.1/tests/__init__.py +0 -1
  8. python_ember_mug-1.1.1/tests/cli/__init__.py +0 -1
  9. python_ember_mug-1.1.1/tests/cli/test_commands.py +0 -380
  10. python_ember_mug-1.1.1/tests/cli/test_helpers.py +0 -92
  11. python_ember_mug-1.1.1/tests/conftest.py +0 -99
  12. python_ember_mug-1.1.1/tests/test_connection.py +0 -584
  13. python_ember_mug-1.1.1/tests/test_consts.py +0 -33
  14. python_ember_mug-1.1.1/tests/test_data.py +0 -115
  15. python_ember_mug-1.1.1/tests/test_formatting.py +0 -24
  16. python_ember_mug-1.1.1/tests/test_mug_data.py +0 -120
  17. python_ember_mug-1.1.1/tests/test_scanner.py +0 -63
  18. python_ember_mug-1.1.1/tests/test_utils.py +0 -224
  19. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/.gitignore +0 -0
  20. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/LICENSE +0 -0
  21. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/README.md +0 -0
  22. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/__main__.py +0 -0
  23. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/__init__.py +0 -0
  24. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/commands.py +0 -0
  25. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/helpers.py +0 -0
  26. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/data.py +0 -0
  27. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/formatting.py +0 -0
  28. {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/mug.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-ember-mug
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Python Library for Ember Mugs.
5
5
  Project-URL: Changelog, https://sopelj.github.io/python-ember-mug/changelog/
6
6
  Project-URL: Documentation, https://sopelj.github.io/python-ember-mug/
7
7
  Project-URL: Source code, https://github.com/sopelj/python-ember-mug/
8
8
  Project-URL: Bug Tracker, https://github.com/sopelj/python-ember-mug/issues
9
- Author-email: Jesse Sopel <jesse.sopel@gmail.com>
9
+ Author-email: Jesse Sopel <jesse@sopelj.ca>
10
10
  License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  Classifier: Intended Audience :: Developers
@@ -20,6 +20,8 @@ Requires-Python: >=3.11
20
20
  Requires-Dist: bleak-retry-connector>=3.5.0
21
21
  Requires-Dist: bleak>=0.22.2; python_version < '3.13'
22
22
  Requires-Dist: bleak>=0.22.3; python_version <= '3.13'
23
+ Provides-Extra: dev
24
+ Requires-Dist: ipython; extra == 'dev'
23
25
  Provides-Extra: docs
24
26
  Requires-Dist: black; extra == 'docs'
25
27
  Requires-Dist: mkdocs-autorefs; extra == 'docs'
@@ -6,4 +6,4 @@ __all__ = ("EmberMug",)
6
6
 
7
7
  __author__ = """Jesse Sopel"""
8
8
  __email__ = "jesse.sopel@gmail.com"
9
- __version__ = "1.1.1"
9
+ __version__ = "1.2.0"
@@ -149,10 +149,10 @@ TRAVEL_MUG_SERVICE_UUIDS = (
149
149
  str(MugCharacteristic.TRAVEL_MUG_SERVICE_OTHER),
150
150
  )
151
151
 
152
- DEVICE_SERVICE_UUIDS = (
152
+ DEVICE_SERVICE_UUIDS = [
153
153
  str(MugCharacteristic.STANDARD_SERVICE),
154
154
  *TRAVEL_MUG_SERVICE_UUIDS,
155
- )
155
+ ]
156
156
 
157
157
 
158
158
  class LiquidState(IntEnum):
@@ -5,29 +5,37 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import contextlib
7
7
  import logging
8
- from typing import TYPE_CHECKING, Any
8
+ from typing import TYPE_CHECKING, cast
9
9
 
10
10
  from bleak import BleakScanner
11
11
 
12
12
  from .consts import DEVICE_SERVICE_UUIDS, IS_LINUX
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from typing import NotRequired, TypedDict
16
+
15
17
  from bleak.backends.device import BLEDevice
16
18
  from bleak.backends.scanner import AdvertisementData
17
19
 
20
+ class ScannerKwargs(TypedDict):
21
+ """Optional kwargs for scanner."""
22
+
23
+ adapter: NotRequired[str]
24
+ service_uuids: NotRequired[list[str]]
25
+
18
26
 
19
27
  DEFAULT_TIMEOUT = 30
20
28
 
21
29
  logger = logging.getLogger(__name__)
22
30
 
23
31
 
24
- def build_scanner_kwargs(adapter: str | None = None) -> dict[str, Any]:
32
+ def build_scanner_kwargs(adapter: str | None = None, *, service_uuids: list[str] | None = None) -> ScannerKwargs:
25
33
  """Add Adapter to kwargs for scanner if specified and using BlueZ."""
26
34
  if adapter and IS_LINUX is not True:
27
35
  msg = "The adapter option is only valid for the Linux BlueZ Backend."
28
36
  raise ValueError(msg)
29
- kwargs = {"service_uuids": DEVICE_SERVICE_UUIDS}
30
- return kwargs | {"adapter": adapter} if adapter else kwargs
37
+ kwargs = {"service_uuids": service_uuids} if service_uuids else {}
38
+ return cast("ScannerKwargs", kwargs | {"adapter": adapter} if adapter else kwargs)
31
39
 
32
40
 
33
41
  async def discover_devices(
@@ -47,7 +55,7 @@ async def discover_devices(
47
55
  ```
48
56
 
49
57
  """
50
- async with BleakScanner(**build_scanner_kwargs(adapter)) as scanner:
58
+ async with BleakScanner(**build_scanner_kwargs(adapter, service_uuids=DEVICE_SERVICE_UUIDS)) as scanner:
51
59
  await asyncio.sleep(wait)
52
60
  return [
53
61
  (d, a)
@@ -134,7 +134,7 @@ def guess_model_from_name(name: str | None) -> DeviceModel | None:
134
134
 
135
135
  def get_model_info_from_advertiser_data(advertisement: AdvertisementData) -> ModelInfo:
136
136
  """Extract model info from manufacturer data in advertiser data."""
137
- from ember_mug.data import ModelInfo
137
+ from ember_mug.data import ModelInfo # noqa: PLC0415
138
138
 
139
139
  model_data = advertisement.manufacturer_data.get(EMBER_BLE_SIG, None)
140
140
  if model_data is not None:
@@ -1,16 +1,12 @@
1
- [tool]
2
-
3
1
  [project]
4
2
  name = "python-ember-mug"
5
3
  readme = "README.md"
6
4
  description = "Python Library for Ember Mugs."
7
- authors = [
8
- { name = "Jesse Sopel", email = "jesse.sopel@gmail.com" },
9
- ]
10
- license = "MIT"
5
+ authors = [{ name = "Jesse Sopel", email = "jesse@sopelj.ca" }]
6
+ license = "MIT"
11
7
  requires-python = ">=3.11"
12
8
  dynamic = ["version"]
13
- classifiers=[
9
+ classifiers = [
14
10
  'Intended Audience :: Developers',
15
11
  'License :: OSI Approved :: MIT License',
16
12
  'Natural Language :: English',
@@ -26,23 +22,20 @@ dependencies = [
26
22
  ]
27
23
 
28
24
  [project.optional-dependencies]
29
- test = [
30
- "pytest>=7.2.1",
31
- "pytest-cov",
32
- "pytest-asyncio",
33
- ]
25
+ test = ["pytest>=7.2.1", "pytest-cov", "pytest-asyncio"]
34
26
  docs = [
35
- "mkdocs>=1.6.1",
36
- "mkdocs-include-markdown-plugin>=7.0.0,<8.0.0",
37
- "mkdocs-material>=9.5.44,<10.0.0",
38
- "mkdocs-material-extensions",
39
- "mkdocstrings-python>=1.12.0",
40
- "mkdocs-autorefs",
41
- "mkdocs-literate-nav",
42
- "mkdocs-gen-files",
43
- "black",
44
- "termynal",
27
+ "mkdocs>=1.6.1",
28
+ "mkdocs-include-markdown-plugin>=7.0.0,<8.0.0",
29
+ "mkdocs-material>=9.5.44,<10.0.0",
30
+ "mkdocs-material-extensions",
31
+ "mkdocstrings-python>=1.12.0",
32
+ "mkdocs-autorefs",
33
+ "mkdocs-literate-nav",
34
+ "mkdocs-gen-files",
35
+ "black",
36
+ "termynal",
45
37
  ]
38
+ dev = ["ipython"]
46
39
 
47
40
  [project.urls]
48
41
  "Changelog" = "https://sopelj.github.io/python-ember-mug/changelog/"
@@ -62,12 +55,15 @@ build-backend = "hatchling.build"
62
55
  [tool.hatch.version]
63
56
  path = "ember_mug/__init__.py"
64
57
 
58
+ [tool.hatch.build]
59
+ exclude = ["/docs", "/tests"]
60
+
65
61
  [tool.hatch.build.targets.sdist]
66
- packages = ["ember_mug", "tests"]
62
+ packages = ["ember_mug"]
67
63
  exclude = [".gitignore"]
68
64
 
69
65
  [tool.hatch.build.targets.wheel]
70
- packages = ["ember_mug", "tests"]
66
+ packages = ["ember_mug"]
71
67
  exclude = [".gitignore"]
72
68
 
73
69
  [tool.hatch.envs.default]
@@ -111,15 +107,15 @@ exclude = '''
111
107
 
112
108
  [tool.coverage.report]
113
109
  exclude_lines = [
114
- "pragma: no cover",
115
- "def __repr__",
116
- "def __str__",
117
- "def main",
118
- "raise AssertionError",
119
- "raise NotImplementedError",
120
- "if __name__ == .__main__.:",
121
- "if TYPE_CHECKING:",
122
- "if typing.TYPE_CHECKING:"
110
+ "pragma: no cover",
111
+ "def __repr__",
112
+ "def __str__",
113
+ "def main",
114
+ "raise AssertionError",
115
+ "raise NotImplementedError",
116
+ "if __name__ == .__main__.:",
117
+ "if TYPE_CHECKING:",
118
+ "if typing.TYPE_CHECKING:",
123
119
  ]
124
120
 
125
121
  [tool.ruff]
@@ -162,7 +158,7 @@ ignore = ["D203", "D212", "PLR2004"]
162
158
 
163
159
  [tool.ruff.lint.per-file-ignores]
164
160
  "tests/**/*.py" = [
165
- "D103", # No docstrings in tests needed
166
- "S101", # We needs asserts in tests
161
+ "D103", # No docstrings in tests needed
162
+ "S101", # We needs asserts in tests
167
163
  "SLF001",
168
164
  ]
@@ -1 +0,0 @@
1
- """Unit test package for python-ember-mug."""
@@ -1 +0,0 @@
1
- """Tests for the CLI module."""
@@ -1,380 +0,0 @@
1
- """Test the CLI commands."""
2
-
3
- from __future__ import annotations
4
-
5
- import sys
6
- from argparse import ArgumentTypeError, Namespace
7
- from typing import TYPE_CHECKING, Any
8
- from unittest.mock import AsyncMock, Mock, call, patch
9
-
10
- import pytest
11
- from bleak import BleakError, BLEDevice
12
-
13
- from ember_mug import EmberMug
14
- from ember_mug.cli.commands import (
15
- EmberMugCli,
16
- colour_type,
17
- discover_cmd,
18
- fetch_info_cmd,
19
- find_device_cmd,
20
- get_device,
21
- get_device_value_cmd,
22
- poll_device_cmd,
23
- set_device_value_cmd,
24
- )
25
- from ember_mug.consts import DeviceColour, DeviceModel
26
- from ember_mug.data import Colour, ModelInfo, MugData
27
-
28
- from ..conftest import TEST_MAC, TEST_MUG_ADVERTISEMENT, mock_connection
29
-
30
- if TYPE_CHECKING:
31
- from collections.abc import Generator
32
-
33
- from pytest import CaptureFixture # noqa: PT013
34
-
35
-
36
- @pytest.fixture
37
- def mock_mug_with_connection() -> Generator[AsyncMock, None, None]:
38
- with patch("ember_mug.cli.commands.get_device") as mock:
39
- mock_mug = AsyncMock()
40
- mock_mug.connection = Mock(return_value=mock_connection)
41
- mock.return_value = mock_mug
42
- yield mock_mug
43
-
44
-
45
- def mock_namespace(**kwargs: Any) -> Namespace:
46
- defaults = {
47
- "imperial": False,
48
- "extra": False,
49
- "raw": False,
50
- "debug": False,
51
- "adapter": None,
52
- }
53
- defaults.update(**kwargs)
54
- return Namespace(**defaults)
55
-
56
-
57
- @patch("ember_mug.cli.commands.EmberMug", spec=EmberMug)
58
- @patch("ember_mug.cli.commands.find_device_cmd")
59
- async def test_get_device(
60
- mock_find_device_cmd: AsyncMock,
61
- mock_ember_mug: AsyncMock,
62
- capsys: CaptureFixture,
63
- ble_device: BLEDevice,
64
- ) -> None:
65
- mock_find_device_cmd.return_value = ble_device, TEST_MUG_ADVERTISEMENT
66
- args = mock_namespace(extra=True)
67
- mug = await get_device(args)
68
- assert mug is not None
69
- mock_find_device_cmd.assert_called_once_with(args)
70
- mock_ember_mug.assert_called_once_with(
71
- ble_device,
72
- ModelInfo(DeviceModel.MUG_2_10_OZ, DeviceColour.BLACK),
73
- use_metric=True,
74
- debug=False,
75
- )
76
- captured = capsys.readouterr()
77
- assert captured.out == "Connecting...\n"
78
-
79
- # Raw prints nothing
80
- args = mock_namespace(extra=True, raw=True)
81
- mug = await get_device(args)
82
- assert mug is not None
83
- captured = capsys.readouterr()
84
- assert captured.out == ""
85
-
86
-
87
- @patch("ember_mug.cli.commands.find_device")
88
- async def test_find_device(mock_find_mug: AsyncMock, capsys: CaptureFixture, ble_device: BLEDevice) -> None:
89
- mock_find_mug.return_value = (ble_device, TEST_MUG_ADVERTISEMENT)
90
- args = mock_namespace(mac=ble_device.address)
91
- device, advertisement = await find_device_cmd(args)
92
- assert device == ble_device
93
- assert advertisement == TEST_MUG_ADVERTISEMENT
94
- mock_find_mug.assert_called_once_with(mac=ble_device.address, adapter=None)
95
- captured = capsys.readouterr()
96
- assert captured.out == f"Found device: {ble_device}\n"
97
-
98
- # Raw prints nothing
99
- args = Namespace(mac=ble_device.address, adapter=None, raw=True)
100
- await find_device_cmd(args)
101
- captured = capsys.readouterr()
102
- assert captured.out == ""
103
-
104
-
105
- @patch("ember_mug.cli.commands.find_device")
106
- async def test_find_device_no_device(mock_find_mug: AsyncMock, capsys: CaptureFixture) -> None:
107
- mock_find_mug.return_value = (None, None)
108
- args = mock_namespace(mac=TEST_MAC)
109
- with pytest.raises(SystemExit, match="1"):
110
- await find_device_cmd(args)
111
- mock_find_mug.assert_called_once_with(mac=TEST_MAC, adapter=None)
112
- captured = capsys.readouterr()
113
- assert captured.out == "No device was found.\n"
114
-
115
-
116
- @patch("ember_mug.cli.commands.find_device")
117
- async def test_find_device_bleak_error(mock_find_mug: AsyncMock, capsys: CaptureFixture) -> None:
118
- mock_find_mug.side_effect = BleakError("Test Error")
119
- args = mock_namespace(mac=TEST_MAC)
120
- with pytest.raises(SystemExit, match="1"):
121
- await find_device_cmd(args)
122
- mock_find_mug.assert_called_once_with(mac=TEST_MAC, adapter=None)
123
- captured = capsys.readouterr()
124
- assert captured.out == "An error occurred trying to find a device: Test Error\n"
125
-
126
-
127
- @patch("ember_mug.cli.commands.discover_devices")
128
- async def test_discover(mock_discover_mugs: AsyncMock, capsys: CaptureFixture, ble_device: BLEDevice) -> None:
129
- mock_discover_mugs.return_value = [(ble_device, TEST_MUG_ADVERTISEMENT)]
130
- args = mock_namespace(mac=TEST_MAC)
131
- mugs = await discover_cmd(args)
132
- assert mugs == [(ble_device, TEST_MUG_ADVERTISEMENT)]
133
- mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
134
- captured = capsys.readouterr()
135
- assert captured.out == (
136
- f"Found mug: {ble_device}\n"
137
- "Name: Ember Ceramic Mug\n"
138
- "Model: Ember Mug 2 (10oz) [CM19/CM21M]\n"
139
- "Colour: Black\n"
140
- "Capacity: 295ml\n"
141
- )
142
-
143
- mock_discover_mugs.reset_mock()
144
- args = mock_namespace(mac=TEST_MAC, raw=True)
145
- mugs = await discover_cmd(args)
146
- assert mugs == [(ble_device, TEST_MUG_ADVERTISEMENT)]
147
- mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
148
- captured = capsys.readouterr()
149
- assert captured.out == f"{TEST_MAC}\n"
150
-
151
-
152
- @patch("ember_mug.cli.commands.discover_devices")
153
- async def test_discover_no_device(mock_discover_mugs: AsyncMock, capsys: CaptureFixture) -> None:
154
- mock_discover_mugs.return_value = []
155
- args = mock_namespace(mac=TEST_MAC)
156
- with pytest.raises(SystemExit, match="1"):
157
- await discover_cmd(args)
158
- mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
159
- captured = capsys.readouterr()
160
- assert captured.out == 'No devices were found. Be sure it is in pairing mode. Or use "find" if already paired.\n'
161
-
162
-
163
- @patch("ember_mug.cli.commands.discover_devices")
164
- async def test_discover_bleak_error(mock_discover_mugs: AsyncMock, capsys: CaptureFixture) -> None:
165
- mock_discover_mugs.side_effect = BleakError("Test Error")
166
- args = mock_namespace(mac=TEST_MAC)
167
- with pytest.raises(SystemExit, match="1"):
168
- await discover_cmd(args)
169
- mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
170
- captured = capsys.readouterr()
171
- assert captured.out == "An error occurred trying to discover devices: Test Error\n"
172
-
173
-
174
- @patch("ember_mug.cli.commands.print_info")
175
- async def test_fetch_info(
176
- mock_print_info: AsyncMock,
177
- mock_mug_with_connection: AsyncMock,
178
- capsys: CaptureFixture,
179
- ) -> None:
180
- # Test normal
181
- args = mock_namespace(mac=TEST_MAC)
182
- await fetch_info_cmd(args)
183
- captured = capsys.readouterr()
184
- assert captured.out == "Connected.\nFetching Info\n"
185
- mock_print_info.assert_called_once_with(mock_mug_with_connection)
186
-
187
- # Test with Raw
188
- args = mock_namespace(mac=TEST_MAC, raw=True)
189
- await fetch_info_cmd(args)
190
- captured = capsys.readouterr()
191
- assert captured.out == ""
192
-
193
-
194
- @patch("asyncio.sleep")
195
- @patch("ember_mug.cli.commands.print_info")
196
- @patch("ember_mug.cli.commands.print_changes")
197
- @patch("ember_mug.cli.commands.CommandLoop", lambda: [1])
198
- async def test_poll_device_cmd(
199
- mock_print_changes: AsyncMock,
200
- mock_print_info: AsyncMock,
201
- mock_sleep: AsyncMock,
202
- mock_mug_with_connection: AsyncMock,
203
- capsys: CaptureFixture,
204
- ) -> None:
205
- # Test normal
206
- args = mock_namespace(mac=TEST_MAC)
207
- await poll_device_cmd(args)
208
- captured = capsys.readouterr()
209
- assert captured.out == "Connected.\nFetching Info\n\nWatching for changes\n"
210
- mock_sleep.assert_has_calls([call(1)] * 60)
211
-
212
- # Test with Raw
213
- mock_sleep.reset_mock()
214
- args = mock_namespace(mac=TEST_MAC, raw=True)
215
- await poll_device_cmd(args)
216
- mock_sleep.assert_has_calls([call(1)] * 60)
217
- captured = capsys.readouterr()
218
- assert captured.out == ""
219
-
220
-
221
- @patch("ember_mug.cli.commands.print_table")
222
- async def test_get_device_value_cmd(
223
- mocked_print_table: Mock,
224
- mock_mug_with_connection: AsyncMock,
225
- capsys: CaptureFixture,
226
- ) -> None:
227
- mock_mug_with_connection.data = MugData(ModelInfo())
228
- mock_mug_with_connection.data.get_formatted_attr = Mock(return_value="test") # type: ignore[assignment]
229
- mock_mug_with_connection.get_target_temp.return_value = 55.5
230
- mock_mug_with_connection.get_name.return_value = "test"
231
- args = mock_namespace(attributes=["target_temp", "name"])
232
- await get_device_value_cmd(args)
233
- mock_mug_with_connection.get_target_temp.assert_called_once()
234
- mocked_print_table.assert_called_once_with([("Target Temp", "test"), ("Device Name", "test")])
235
-
236
- mock_mug_with_connection.get_led_colour.return_value = 55.5
237
- args = mock_namespace(attributes=["led_colour", "name"], raw=True)
238
- await get_device_value_cmd(args)
239
- captured = capsys.readouterr()
240
- assert captured.out == "55.5\ntest\n"
241
-
242
- mock_mug_with_connection.get_name.side_effect = NotImplementedError
243
- args = mock_namespace(attributes=["name"], raw=True)
244
- with pytest.raises(SystemExit, match="1"):
245
- await get_device_value_cmd(args)
246
-
247
-
248
- async def test_set_device_value_cmd_no_value(capsys: CaptureFixture) -> None:
249
- with pytest.raises(SystemExit, match="1"):
250
- await set_device_value_cmd(Namespace())
251
- captured = capsys.readouterr()
252
- assert captured.out == (
253
- "Please specify at least one attribute and value to set.\n"
254
- "Options: --name, --target-temp, --temperature-unit, --led-colour, --volume-level\n"
255
- )
256
-
257
-
258
- async def test_set_device_value_cmd(mock_mug_with_connection: AsyncMock) -> None:
259
- mock_mug_with_connection.data = MugData(ModelInfo())
260
- args = mock_namespace(name="test")
261
- await set_device_value_cmd(args)
262
- mock_mug_with_connection.set_name.assert_called_once_with("test")
263
-
264
- mock_mug_with_connection.reset_mock()
265
- mock_mug_with_connection.set_name.side_effect = NotImplementedError("Unable to set name on Cup")
266
- with pytest.raises(SystemExit, match="1"):
267
- await set_device_value_cmd(args)
268
-
269
-
270
- @pytest.mark.parametrize(
271
- ("value", "error"),
272
- [
273
- ("1,2,3,4,5,6", "Three or four values should be specified for colour"),
274
- ("260,1,1,1", "Colour values must be between 0 and 255"),
275
- ("invalid", '"invalid" is not a valid rgba or hex colour'),
276
- ],
277
- )
278
- def test_colour_type_raises(value: str, error: str) -> None:
279
- with pytest.raises(ArgumentTypeError, match=error):
280
- colour_type(value)
281
-
282
-
283
- def test_colour_type() -> None:
284
- assert colour_type("#ffffff") == Colour(255, 255, 255, 255)
285
- assert colour_type("#ffffffaa") == Colour(255, 255, 255, 170)
286
- assert colour_type("1,2,3") == Colour(1, 2, 3, 255)
287
- assert colour_type("1,2,3,4") == Colour(1, 2, 3, 4)
288
-
289
-
290
- def test_ember_cli():
291
- cli = EmberMugCli()
292
-
293
- args = cli.parser.parse_args(["find"])
294
- assert args.command == "find"
295
-
296
- args = cli.parser.parse_args(["discover"])
297
- assert args.command == "discover"
298
-
299
- args = cli.parser.parse_args(["info", "-m", TEST_MAC, "--imperial"])
300
- assert args.command == "info"
301
- assert args.mac == TEST_MAC
302
- assert args.imperial is True
303
-
304
- args = cli.parser.parse_args(["poll"])
305
- assert args.command == "poll"
306
-
307
- args = cli.parser.parse_args(["get", "led-colour"])
308
- assert args.command == "get"
309
- assert args.attributes == ["led-colour"]
310
-
311
- args = cli.parser.parse_args(["set", "--name", "TEST"])
312
- assert args.command == "set"
313
- assert args.name == "TEST"
314
-
315
-
316
- @patch("ember_mug.consts.IS_LINUX", False)
317
- def test_ember_cli_windows():
318
- del sys.modules["ember_mug.cli.commands"] # force re-import
319
- from ember_mug.cli.commands import EmberMugCli
320
-
321
- cli = EmberMugCli()
322
-
323
- args = cli.parser.parse_args(["find"])
324
- assert args.command == "find"
325
-
326
- with pytest.raises(SystemExit, match="2"):
327
- cli.parser.parse_args(["info", "--adapter", "hci0"])
328
-
329
-
330
- @patch("sys.argv", ["file.py", "find", "-m", TEST_MAC])
331
- async def test_cli_run():
332
- cli = EmberMugCli()
333
- mock_find = AsyncMock()
334
- with patch.object(cli, "_commands", {"find": mock_find}):
335
- await cli.run()
336
-
337
- mock_find.assert_called_once()
338
- args = mock_find.mock_calls[0].args[0]
339
- assert args.command == "find"
340
- assert args.mac == TEST_MAC
341
- assert args.debug is False
342
- assert args.raw is False
343
- assert args.adapter is None
344
-
345
-
346
- @patch("sys.argv", ["file.py", "discover", "--debug"])
347
- @patch("logging.basicConfig")
348
- async def test_cli_run_discover_debug(mock_logging_config: Mock):
349
- cli = EmberMugCli()
350
- mock_discover = AsyncMock()
351
- with patch.object(cli, "_commands", {"discover": mock_discover}):
352
- await cli.run()
353
-
354
- mock_discover.assert_called_once()
355
- mock_logging_config.assert_called_once()
356
- args = mock_discover.mock_calls[0].args[0]
357
- assert args.command == "discover"
358
- assert args.debug is True
359
- assert args.raw is False
360
- assert args.adapter is None
361
-
362
-
363
- @patch("ember_mug.consts.IS_LINUX", False)
364
- @patch("sys.argv", ["file.py", "find", "-m", TEST_MAC, "--raw"])
365
- async def test_cli_run_non_linux():
366
- del sys.modules["ember_mug.cli.commands"] # force re-import
367
- from ember_mug.cli.commands import EmberMugCli
368
-
369
- cli = EmberMugCli()
370
- mock_find = AsyncMock()
371
- with patch.object(cli, "_commands", {"find": mock_find}):
372
- await cli.run()
373
-
374
- mock_find.assert_called_once()
375
- args = mock_find.mock_calls[0].args[0]
376
- assert args.command == "find"
377
- assert args.mac == TEST_MAC
378
- assert args.debug is False
379
- assert args.raw is True
380
- assert args.adapter is None
@@ -1,92 +0,0 @@
1
- """Test the CLI helper functions."""
2
-
3
- from __future__ import annotations
4
-
5
- from argparse import ArgumentTypeError
6
- from textwrap import dedent
7
- from typing import TYPE_CHECKING
8
-
9
- import pytest
10
-
11
- from ember_mug.cli.helpers import build_sub_rows, print_changes, print_info, print_table, validate_mac
12
- from ember_mug.consts import LiquidState
13
- from ember_mug.data import Change
14
-
15
- if TYPE_CHECKING:
16
- from pytest import CaptureFixture # noqa: PT013
17
-
18
- from ember_mug import EmberMug
19
-
20
-
21
- def test_validate_mac() -> None:
22
- with pytest.raises(ArgumentTypeError, match="Invalid MAC Address"):
23
- validate_mac("potato")
24
- assert validate_mac("9C:DA:8C:19:27:DA") == "9c:da:8c:19:27:da"
25
-
26
-
27
- def test_build_sub_rows() -> None:
28
- sub_rows = build_sub_rows(("Test", "test1, test2, test3", "test4"))
29
- assert sub_rows[0][0] == "Test"
30
- assert sub_rows[0][1] == "test1"
31
- assert sub_rows[0][2] == "test4"
32
- assert sub_rows[1][0] == ""
33
- assert sub_rows[1][1] == "test2"
34
- assert sub_rows[1][2] == ""
35
- assert sub_rows[2][1] == "test3"
36
-
37
-
38
- def test_print_changes(capsys: CaptureFixture) -> None:
39
- changes = [
40
- Change("name", "Mug Name", "Test Mug"),
41
- Change("liquid_level", 1, 2),
42
- Change("liquid_state", LiquidState.EMPTY, LiquidState.HEATING),
43
- Change("target_temp", 45, 55),
44
- ]
45
- print_changes(changes, True)
46
- captured = capsys.readouterr()
47
- assert captured.out == dedent(
48
- """\
49
- Name changed from "Mug Name" to "Test Mug"
50
- Liquid Level changed from "3.33%" to "6.67%"
51
- Liquid State changed from "Empty" to "Heating"
52
- Target Temp changed from "45.00°C" to "55.00°C"
53
- """,
54
- )
55
-
56
-
57
- def test_print_table(ember_mug: EmberMug, capsys: CaptureFixture) -> None:
58
- print_table([])
59
- captured = capsys.readouterr()
60
- assert captured.out == ""
61
-
62
-
63
- def test_print_info(ember_mug: EmberMug, capsys: CaptureFixture) -> None:
64
- ember_mug.debug = False
65
- print_info(ember_mug)
66
- captured = capsys.readouterr()
67
- assert captured.out == dedent(
68
- """\
69
- Device Data
70
- +--------------+---------+
71
- | Device Name | |
72
- +--------------+---------+
73
- | Meta | None |
74
- +--------------+---------+
75
- | Battery | None |
76
- +--------------+---------+
77
- | Firmware | None |
78
- +--------------+---------+
79
- | LED Colour | #ffffff |
80
- +--------------+---------+
81
- | Liquid State | Unknown |
82
- +--------------+---------+
83
- | Liquid Level | 0.00% |
84
- +--------------+---------+
85
- | Current Temp | 0.00°C |
86
- +--------------+---------+
87
- | Target Temp | 0.00°C |
88
- +--------------+---------+
89
- | Use Metric | True |
90
- +--------------+---------+
91
- """,
92
- )