Skip to content

Commit a0d868e

Browse files
mdegat01Copilot
andauthored
Add FeatureFlag enum and feature_flags support to supervisor models (#312)
- Add FeatureFlag StrEnum with SUPERVISOR_V2_API entry and incomplete-list docstring (matching SuggestionType pattern) - Add feature_flags: dict[FeatureFlag | str, bool] to SupervisorInfo for non-strict validation (unknown flags fall back to str keys) - Add feature_flags: dict[FeatureFlag, bool] | None to SupervisorOptions for strict validation - Export FeatureFlag from models __init__ - Update supervisor_info.json fixture with feature_flags field - Add tests for info parsing, unknown flag fallback, and options serialization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 60468e6 commit a0d868e

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

aiohasupervisor/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
)
153153
from aiohasupervisor.models.supervisor import (
154154
DetectBlockingIO,
155+
FeatureFlag,
155156
SupervisorInfo,
156157
SupervisorOptions,
157158
SupervisorStats,
@@ -203,6 +204,7 @@
203204
"DiscoveryConfig",
204205
"DockerNetwork",
205206
"DownloadBackupOptions",
207+
"FeatureFlag",
206208
"Folder",
207209
"FreezeOptions",
208210
"FullBackupOptions",

aiohasupervisor/models/supervisor.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ class DetectBlockingIO(StrEnum):
1818
ON_AT_STARTUP = "on_at_startup"
1919

2020

21+
class FeatureFlag(StrEnum):
22+
"""FeatureFlag type.
23+
24+
This is an incomplete list. Supervisor regularly adds new feature flags as
25+
new development features are introduced. Therefore when returning feature flags,
26+
some keys may not be in this list and will be parsed as strings on older versions
27+
of the client.
28+
"""
29+
30+
SUPERVISOR_V2_API = "supervisor_v2_api"
31+
32+
2133
# --- OBJECTS ----
2234

2335

@@ -41,6 +53,7 @@ class SupervisorInfo(ResponseData):
4153
auto_update: bool
4254
country: str | None
4355
detect_blocking_io: bool
56+
feature_flags: dict[FeatureFlag | str, bool]
4457

4558

4659
@dataclass(frozen=True, slots=True)
@@ -70,3 +83,4 @@ class SupervisorOptions(Options):
7083
auto_update: bool | None = None
7184
country: str | None = None
7285
detect_blocking_io: DetectBlockingIO | None = None
86+
feature_flags: dict[FeatureFlag, bool] | None = None

tests/fixtures/supervisor_info.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
"country": null,
1919
"wait_boot": 5,
2020
"detect_blocking_io": false,
21+
"feature_flags": {
22+
"supervisor_v2_api": false
23+
},
2124
"addons": [
2225
{
2326
"name": "Terminal & SSH",

tests/test_supervisor.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Test for supervisor management client."""
22

33
from ipaddress import IPv4Address
4+
import json
45

56
from aioresponses import aioresponses
67
import pytest
78
from yarl import URL
89

910
from aiohasupervisor import SupervisorClient
1011
from aiohasupervisor.models import SupervisorOptions, SupervisorUpdateOptions
11-
from aiohasupervisor.models.supervisor import DetectBlockingIO
12+
from aiohasupervisor.models.supervisor import DetectBlockingIO, FeatureFlag
1213

1314
from . import load_fixture
1415
from .const import SUPERVISOR_URL
@@ -45,6 +46,7 @@ async def test_supervisor_info(
4546
assert info.ip_address == IPv4Address("172.30.32.2")
4647
assert info.country is None
4748
assert info.detect_blocking_io is False
49+
assert info.feature_flags == {FeatureFlag.SUPERVISOR_V2_API: False}
4850

4951

5052
@pytest.mark.parametrize("field", ["version_latest", "arch"])
@@ -54,8 +56,6 @@ async def test_supervisor_info_optional_fields_none(
5456
field: str,
5557
) -> None:
5658
"""Test supervisor info API when optional fields are None."""
57-
import json
58-
5959
fixture = json.loads(load_fixture("supervisor_info.json"))
6060
fixture["data"][field] = None
6161
responses.get(
@@ -154,6 +154,45 @@ async def test_supervisor_options(
154154
}
155155

156156

157+
async def test_supervisor_info_unknown_feature_flag(
158+
responses: aioresponses, supervisor_client: SupervisorClient
159+
) -> None:
160+
"""Test supervisor info with an unknown feature flag returns it as a string key."""
161+
fixture = json.loads(load_fixture("supervisor_info.json"))
162+
fixture["data"]["feature_flags"]["future_feature"] = True
163+
responses.get(
164+
f"{SUPERVISOR_URL}/supervisor/info",
165+
status=200,
166+
payload=fixture,
167+
)
168+
info = await supervisor_client.supervisor.info()
169+
assert info.feature_flags[FeatureFlag.SUPERVISOR_V2_API] is False
170+
assert info.feature_flags["future_feature"] is True
171+
172+
173+
async def test_supervisor_options_feature_flags(
174+
responses: aioresponses, supervisor_client: SupervisorClient
175+
) -> None:
176+
"""Test supervisor options API with feature_flags."""
177+
responses.post(f"{SUPERVISOR_URL}/supervisor/options", status=200)
178+
assert (
179+
await supervisor_client.supervisor.set_options(
180+
SupervisorOptions(
181+
feature_flags={FeatureFlag.SUPERVISOR_V2_API: True},
182+
)
183+
)
184+
is None
185+
)
186+
assert (
187+
request := responses.requests[
188+
("POST", URL(f"{SUPERVISOR_URL}/supervisor/options"))
189+
]
190+
)
191+
assert request[0].kwargs["json"] == {
192+
"feature_flags": {"supervisor_v2_api": True},
193+
}
194+
195+
157196
async def test_supervisor_repair(
158197
responses: aioresponses, supervisor_client: SupervisorClient
159198
) -> None:

0 commit comments

Comments
 (0)