Skip to content

Commit d531c89

Browse files
nanomadedenhaus
andauthored
Add classes to convert standard enums into XML values (#907)
Co-authored-by: Robert Resch <robert@resch.dev>
1 parent 69fa58d commit d531c89

8 files changed

Lines changed: 183 additions & 36 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: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@
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
1618
QUIET = 1000
17-
NORMAL = 0
18-
MAX = 1
19+
NORMAL = 0, "standard"
20+
MAX = 1, "strong"
1921
MAX_PLUS = 2
2022

2123

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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 | None
13+
14+
def __new__(cls, value: str, xml_value: str | None = None) -> 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 | None) -> Self:
23+
"""Convert from xml value."""
24+
if value:
25+
for member in cls:
26+
if member.xml_value == value:
27+
return member
28+
29+
msg = f"{value} is not a valid {cls.__name__}"
30+
raise ValueError(msg)
31+
32+
33+
class IntEnumWithXml(IntEnum):
34+
"""Int enum with xml value."""
35+
36+
xml_value: str | None
37+
38+
def __new__(cls, value: int, xml_value: str | None = None) -> Self:
39+
"""Create new StrEnumWithXml."""
40+
obj = int.__new__(cls, value)
41+
obj._value_ = value
42+
obj.xml_value = xml_value
43+
return obj
44+
45+
@classmethod
46+
def from_xml(cls, value: str | None) -> Self:
47+
"""Convert from xml value."""
48+
if value:
49+
for member in cls:
50+
if member.xml_value == value:
51+
return member
52+
53+
msg = f"{value} is not a valid {cls.__name__}"
54+
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.events import FanSpeedLevel
6+
7+
8+
@pytest.mark.parametrize("value", [level for level in FanSpeedLevel if level.xml_value])
9+
def test_clean_action_xml_conversion(value: FanSpeedLevel) -> None:
10+
assert FanSpeedLevel.from_xml(value.xml_value) == value
11+
12+
13+
@pytest.mark.parametrize("value", list(FanSpeedLevel))
14+
def test_clean_action_conversion(value: FanSpeedLevel) -> None:
15+
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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import annotations
2+
3+
from typing import TypeVar
4+
5+
import pytest
6+
7+
from deebot_client.util.enum import IntEnumWithXml, StrEnumWithXml
8+
9+
10+
class _TestStrEnumWithXml(StrEnumWithXml):
11+
ENUM1 = "value1", "xmlvalue1"
12+
ENUM2 = "value2", "xmlvalue2"
13+
14+
def assert_self_conversion(self) -> None:
15+
assert self == _TestStrEnumWithXml(self.value)
16+
assert self == _TestStrEnumWithXml.from_xml(self.xml_value)
17+
18+
19+
class _TestIntEnumWithXml(IntEnumWithXml):
20+
ENUM1 = 1, "xmlvalue1"
21+
ENUM2 = 2, "xmlvalue2"
22+
23+
def assert_self_conversion(self) -> None:
24+
assert self == _TestIntEnumWithXml(self.value)
25+
assert self == _TestIntEnumWithXml.from_xml(self.xml_value)
26+
27+
28+
T = TypeVar("T", _TestStrEnumWithXml, _TestIntEnumWithXml)
29+
30+
31+
@pytest.mark.parametrize(
32+
"test_enum", list(_TestStrEnumWithXml) + list(_TestIntEnumWithXml)
33+
)
34+
def test_EnumWithXml_conversion(
35+
test_enum: T,
36+
) -> None:
37+
test_enum.assert_self_conversion()
38+
39+
40+
@pytest.mark.parametrize(
41+
("test_enum_cls", "invalid_value"),
42+
[
43+
(_TestStrEnumWithXml, "this_is_invalid"),
44+
(_TestStrEnumWithXml, None),
45+
(_TestIntEnumWithXml, "this_is_invalid"),
46+
(_TestIntEnumWithXml, None),
47+
],
48+
)
49+
def test_EnumWithXml_invalid_value(
50+
test_enum_cls: type[T], invalid_value: str | None
51+
) -> None:
52+
with pytest.raises(ValueError, match=str(invalid_value)):
53+
test_enum_cls.from_xml(invalid_value)

0 commit comments

Comments
 (0)