Skip to content

Commit 8f0495e

Browse files
committed
Add classes to convert standard enums into XML values
1 parent 231973c commit 8f0495e

8 files changed

Lines changed: 191 additions & 38 deletions

File tree

deebot_client/events/__init__.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from enum import IntEnum, StrEnum, unique
7-
from typing import TYPE_CHECKING, Any, Self
6+
from enum import IntEnum, unique
7+
from typing import TYPE_CHECKING, Any
8+
9+
from deebot_client.util.enum import StrEnumWithXml
810

911
from . import auto_empty, station
1012
from .auto_empty import AutoEmptyEvent
@@ -126,27 +128,9 @@ class ErrorEvent(Event):
126128

127129

128130
@unique
129-
class LifeSpan(StrEnum):
131+
class LifeSpan(StrEnumWithXml):
130132
"""Enum class for all possible life span components."""
131133

132-
xml_value: str
133-
134-
def __new__(cls, value: str, xml_value: str = "") -> Self:
135-
obj = str.__new__(cls, value)
136-
obj._value_ = value
137-
obj.xml_value = xml_value
138-
return obj
139-
140-
@classmethod
141-
def from_xml(cls, value: str) -> LifeSpan:
142-
"""Get LifeSpan from xml value."""
143-
for life_span in LifeSpan:
144-
if life_span.xml_value == value:
145-
return life_span
146-
147-
msg = f"{value} is not a valid {cls.__name__}"
148-
raise ValueError(msg)
149-
150134
BRUSH = "brush", "Brush"
151135
FILTER = "heap", "Heap"
152136
SIDE_BRUSH = "sideBrush", "SideBrush"

deebot_client/events/fan_speed.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from enum import IntEnum, unique
6+
from enum import unique
7+
8+
from deebot_client.util.enum import IntEnumWithXml
79

810
from .base import Event
911

1012

1113
@unique
12-
class FanSpeedLevel(IntEnum):
14+
class FanSpeedLevel(IntEnumWithXml):
1315
"""Enum class for all possible fan speed levels."""
1416

1517
# Values should be sort from low to high on their meanings
16-
QUIET = 1000
17-
NORMAL = 0
18-
MAX = 1
19-
MAX_PLUS = 2
18+
QUIET = 1000, ""
19+
NORMAL = 0, "standard"
20+
MAX = 1, "strong"
21+
MAX_PLUS = 2, ""
2022

2123

2224
@dataclass(frozen=True)

deebot_client/models.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from enum import IntEnum, StrEnum, unique
6+
from enum import IntEnum, unique
77
from pathlib import Path
88
from typing import TYPE_CHECKING, Required, TypedDict
99

10+
from deebot_client.util.enum import StrEnumWithXml
11+
1012
if TYPE_CHECKING:
1113
from deebot_client.capabilities import Capabilities
1214
from deebot_client.const import DataType
@@ -64,22 +66,22 @@ class State(IntEnum):
6466

6567

6668
@unique
67-
class CleanAction(StrEnum):
69+
class CleanAction(StrEnumWithXml):
6870
"""Enum class for all possible clean actions."""
6971

70-
START = "start"
71-
PAUSE = "pause"
72-
RESUME = "resume"
73-
STOP = "stop"
72+
START = "start", "s"
73+
PAUSE = "pause", "p"
74+
RESUME = "resume", "r"
75+
STOP = "stop", "h"
7476

7577

7678
@unique
77-
class CleanMode(StrEnum):
79+
class CleanMode(StrEnumWithXml):
7880
"""Enum class for all possible clean modes."""
7981

80-
AUTO = "auto"
81-
SPOT_AREA = "spotArea"
82-
CUSTOM_AREA = "customArea"
82+
AUTO = "auto", "auto"
83+
SPOT_AREA = "spotArea", "SpotArea"
84+
CUSTOM_AREA = "customArea", "spot"
8385

8486

8587
@dataclass(frozen=True)

deebot_client/util/enum.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Enum util."""
2+
3+
from __future__ import annotations
4+
5+
from enum import IntEnum, StrEnum
6+
from typing import Self
7+
8+
9+
class StrEnumWithXml(StrEnum):
10+
"""String enum with xml value."""
11+
12+
xml_value: str
13+
14+
def __new__(cls, value: str, xml_value: str = "") -> Self:
15+
"""Create new StrEnumWithXml."""
16+
obj = str.__new__(cls, value)
17+
obj._value_ = value
18+
obj.xml_value = xml_value
19+
return obj
20+
21+
@classmethod
22+
def from_xml(cls, value: str) -> Self:
23+
"""Convert from xml value."""
24+
for member in cls:
25+
if member.xml_value == value:
26+
return member
27+
28+
msg = f"{value} is not a valid {cls.__name__}"
29+
raise ValueError(msg)
30+
31+
32+
class IntEnumWithXml(IntEnum):
33+
"""Int enum with xml value."""
34+
35+
xml_value: str
36+
37+
def __new__(cls, value: int, xml_value: str = "") -> Self:
38+
"""Create new StrEnumWithXml."""
39+
obj = int.__new__(cls, value)
40+
obj._value_ = value
41+
obj.xml_value = xml_value
42+
return obj
43+
44+
@classmethod
45+
def from_xml(cls, value: str) -> Self:
46+
"""Convert from xml value."""
47+
for member in cls:
48+
if member.xml_value == value:
49+
return member
50+
51+
msg = f"{value} is not a valid {cls.__name__}"
52+
raise ValueError(msg)

tests/events/test_events.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
from __future__ import annotations
44

5+
import pytest
6+
57
from deebot_client.events import LifeSpan
68

79

810
def test_life_span() -> None:
911
"""Test life span events."""
10-
assert LifeSpan.BRUSH != LifeSpan.FILTER
12+
assert LifeSpan.BRUSH != LifeSpan.FILTER # type: ignore[comparison-overlap]
1113
assert LifeSpan.FILTER not in {LifeSpan.BLADE, LifeSpan.BRUSH, LifeSpan.SIDE_BRUSH}
14+
15+
16+
@pytest.mark.parametrize("value", list(LifeSpan))
17+
def test_life_span_xml_conversion(value: LifeSpan) -> None:
18+
assert LifeSpan.from_xml(value.xml_value) == value
19+
20+
21+
@pytest.mark.parametrize("value", list(LifeSpan))
22+
def test_life_span_conversion(value: LifeSpan) -> None:
23+
assert LifeSpan(value.value) == value

tests/events/test_fan_speed.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.events import FanSpeedLevel
6+
7+
8+
@pytest.mark.parametrize(
9+
"value", [level for level in FanSpeedLevel if level.xml_value != ""]
10+
)
11+
def test_clean_action_xml_conversion(value: FanSpeedLevel) -> None:
12+
assert FanSpeedLevel.from_xml(value.xml_value) == value
13+
14+
15+
@pytest.mark.parametrize("value", list(FanSpeedLevel))
16+
def test_clean_action_conversion(value: FanSpeedLevel) -> None:
17+
assert FanSpeedLevel(value.value) == value

tests/test_models.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.models import CleanAction, CleanMode
6+
7+
8+
@pytest.mark.parametrize("value", list(CleanAction))
9+
def test_clean_action_xml_conversion(value: CleanAction) -> None:
10+
assert CleanAction.from_xml(value.xml_value) == value
11+
12+
13+
@pytest.mark.parametrize("value", list(CleanAction))
14+
def test_clean_action_conversion(value: CleanAction) -> None:
15+
assert CleanAction(value.value) == value
16+
17+
18+
@pytest.mark.parametrize("value", list(CleanMode))
19+
def test_clean_mode_xml_conversion(value: CleanMode) -> None:
20+
assert CleanMode.from_xml(value.xml_value) == value
21+
22+
23+
@pytest.mark.parametrize("value", list(CleanMode))
24+
def test_clean_mode_conversion(value: CleanMode) -> None:
25+
assert CleanMode(value.value) == value

tests/util/test_enum.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.util.enum import IntEnumWithXml, StrEnumWithXml
6+
7+
8+
class _TestStrEnumWithXml(StrEnumWithXml):
9+
"""Simple Enum for testing."""
10+
11+
ENUM1 = "value1", "xmlvalue1"
12+
ENUM2 = "value2", "xmlvalue2"
13+
14+
15+
class _TestIntEnumWithXml(IntEnumWithXml):
16+
ENUM1 = 1, "xmlvalue1"
17+
ENUM2 = 2, "xmlvalue2"
18+
19+
20+
@pytest.mark.parametrize(
21+
("test_enum", "test_value", "test_xml_value"),
22+
[
23+
(_TestStrEnumWithXml.ENUM1, "value1", "xmlvalue1"),
24+
(_TestStrEnumWithXml.ENUM2, "value2", "xmlvalue2"),
25+
],
26+
)
27+
def test_StrEnumWithXml_values(
28+
test_enum: _TestStrEnumWithXml, test_value: str, test_xml_value: str
29+
) -> None:
30+
assert test_enum.value == test_value
31+
assert test_enum.xml_value == test_xml_value
32+
assert test_value == _TestStrEnumWithXml.from_xml(test_xml_value).value
33+
assert test_xml_value == _TestStrEnumWithXml(test_value).xml_value
34+
35+
36+
def test_StrEnumWithXml_invalid_value() -> None:
37+
with pytest.raises(ValueError, match="this_is_invalid"):
38+
_TestStrEnumWithXml.from_xml("this_is_invalid")
39+
40+
41+
@pytest.mark.parametrize(
42+
("test_enum", "test_value", "test_xml_value"),
43+
[
44+
(_TestIntEnumWithXml.ENUM1, 1, "xmlvalue1"),
45+
(_TestIntEnumWithXml.ENUM2, 2, "xmlvalue2"),
46+
],
47+
)
48+
def test_IntEnumWithXml_values(
49+
test_enum: _TestIntEnumWithXml, test_value: int, test_xml_value: str
50+
) -> None:
51+
assert test_enum.value == test_value
52+
assert test_enum.xml_value == test_xml_value
53+
assert test_value == _TestIntEnumWithXml.from_xml(test_xml_value).value
54+
assert test_xml_value == _TestIntEnumWithXml(test_value).xml_value
55+
56+
57+
def test_IntEnumWithXml_invalid_value() -> None:
58+
with pytest.raises(ValueError, match="this_is_invalid"):
59+
_TestIntEnumWithXml.from_xml("this_is_invalid")

0 commit comments

Comments
 (0)