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.
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/PKG-INFO +4 -2
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/__init__.py +1 -1
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/consts.py +2 -2
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/scanner.py +13 -5
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/utils.py +1 -1
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/pyproject.toml +31 -35
- python_ember_mug-1.1.1/tests/__init__.py +0 -1
- python_ember_mug-1.1.1/tests/cli/__init__.py +0 -1
- python_ember_mug-1.1.1/tests/cli/test_commands.py +0 -380
- python_ember_mug-1.1.1/tests/cli/test_helpers.py +0 -92
- python_ember_mug-1.1.1/tests/conftest.py +0 -99
- python_ember_mug-1.1.1/tests/test_connection.py +0 -584
- python_ember_mug-1.1.1/tests/test_consts.py +0 -33
- python_ember_mug-1.1.1/tests/test_data.py +0 -115
- python_ember_mug-1.1.1/tests/test_formatting.py +0 -24
- python_ember_mug-1.1.1/tests/test_mug_data.py +0 -120
- python_ember_mug-1.1.1/tests/test_scanner.py +0 -63
- python_ember_mug-1.1.1/tests/test_utils.py +0 -224
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/.gitignore +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/LICENSE +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/README.md +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/__main__.py +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/__init__.py +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/commands.py +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/cli/helpers.py +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/data.py +0 -0
- {python_ember_mug-1.1.1 → python_ember_mug-1.2.0}/ember_mug/formatting.py +0 -0
- {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.
|
|
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
|
|
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'
|
|
@@ -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,
|
|
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
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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"
|
|
62
|
+
packages = ["ember_mug"]
|
|
67
63
|
exclude = [".gitignore"]
|
|
68
64
|
|
|
69
65
|
[tool.hatch.build.targets.wheel]
|
|
70
|
-
packages = ["ember_mug"
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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",
|
|
166
|
-
"S101",
|
|
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
|
-
)
|