Skip to content

Commit 615aabd

Browse files
Remove legacy client dependency and use local REST data models
1 parent 13e2dd7 commit 615aabd

11 files changed

Lines changed: 200 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 0.2.0
44

55
- Replace inherited network methods with one validated, bounded HTTP transport.
6+
- Remove the legacy pycasatunes dependency; use local REST data models and HA's shared aiohttp session.
67
- Preserve zone unique IDs and native search, TTS and doorbell services.
78
- Use typed runtime data and current Home Assistant service/discovery interfaces.
89
- Publish complete poll snapshots and cache system/source metadata.

custom_components/casatunes/api.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
from urllib.parse import quote, urlencode
1111

1212
from aiohttp import ClientError, ClientSession, ClientTimeout
13-
from pycasatunes.const import API_PORT
14-
from pycasatunes.exceptions import CasaException
15-
from pycasatunes.objects.nowplaying import CasaTunesNowPlaying
16-
from pycasatunes.objects.source import CasaTunesSource
17-
from pycasatunes.objects.system import CasaTunesSystem
18-
from pycasatunes.objects.zone import CasaTunesZone
13+
14+
from .models import (
15+
CasaException,
16+
CasaTunesNowPlaying,
17+
CasaTunesSource,
18+
CasaTunesSystem,
19+
CasaTunesZone,
20+
)
1921

2022
_CasaTunesObjectT = TypeVar("_CasaTunesObjectT")
2123

2224

2325
_LOGGER = logging.getLogger(__name__)
26+
API_PORT = 8735
2427
REQUEST_TIMEOUT = ClientTimeout(total=10)
2528

2629

custom_components/casatunes/browse_media.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
77
from homeassistant.components.media_player.errors import BrowseError
8-
from pycasatunes.exceptions import CasaException
8+
9+
from .models import CasaException
910

1011

1112
class UnknownMediaType(BrowseError):

custom_components/casatunes/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
ATTR_UPNP_FRIENDLY_NAME,
1919
SsdpServiceInfo,
2020
)
21-
from pycasatunes.exceptions import CasaException
2221

2322
from .api import CasaTunesClient
2423
from .const import DOMAIN
24+
from .models import CasaException
2525

2626
_LOGGER = logging.getLogger(__name__)
2727
CONNECT_ERRORS = (CasaException, ClientError, TimeoutError)

custom_components/casatunes/coordinator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
from homeassistant.exceptions import HomeAssistantError
99
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1010
from homeassistant.util.dt import utcnow
11-
from pycasatunes.exceptions import CasaException
1211

1312
from .api import CasaTunesClient, CasaTunesData
1413
from .const import DOMAIN
14+
from .models import CasaException
1515

1616
_LOGGER = logging.getLogger(__name__)
1717

custom_components/casatunes/manifest.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
"documentation": "https://github.com/alphasixtyfive/casatunes",
99
"iot_class": "local_polling",
1010
"issue_tracker": "https://github.com/alphasixtyfive/casatunes/issues",
11-
"requirements": [
12-
"pycasatunes==0.1.3",
13-
"async-timeout==5.0.1"
14-
],
11+
"requirements": [],
1512
"ssdp": [
1613
{
1714
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

custom_components/casatunes/media_player.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from homeassistant.exceptions import ServiceValidationError
2222
from homeassistant.helpers import config_validation as cv
2323
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
24-
from pycasatunes.objects.zone import CasaTunesZone
2524

2625
from .browse_media import CT_ALLOWSELECT, CT_COLLECTION, build_item_response
2726
from .const import (
@@ -42,6 +41,7 @@
4241
)
4342
from .coordinator import CasaTunesDataUpdateCoordinator
4443
from .entity import CasaTunesDeviceEntity
44+
from .models import CasaTunesZone
4545

4646
_LOGGER = logging.getLogger(__name__)
4747

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
"""Small data models for the documented CasaTunes REST fields."""
2+
3+
4+
class CasaException(Exception):
5+
"""A transport or protocol failure."""
6+
7+
8+
class CasaTunesObject:
9+
"""Retain one validated JSON record without any network behavior."""
10+
11+
def __init__(self, client, attributes):
12+
self.attributes = dict(attributes)
13+
14+
15+
class CasaTunesSystem(CasaTunesObject):
16+
"""Read-only accessors for System data."""
17+
18+
@property
19+
def MACAddress(self):
20+
return self.attributes.get("MACAddress", "")
21+
22+
@property
23+
def AppName(self):
24+
return self.attributes.get("AppName", "")
25+
26+
@property
27+
def CasaTunesVersion(self):
28+
return self.attributes.get("CasaTunesVersion", "")
29+
30+
@property
31+
def RESTServicesVersion(self):
32+
return self.attributes.get("RESTServicesVersion", "")
33+
34+
35+
class CasaTunesZone(CasaTunesObject):
36+
"""Read-only accessors for Zone data."""
37+
38+
@property
39+
def ZoneID(self):
40+
return self.attributes.get("ZoneID", None)
41+
42+
@property
43+
def Name(self):
44+
return self.attributes.get("Name", "")
45+
46+
@property
47+
def Power(self):
48+
return self.attributes.get("Power", None)
49+
50+
@property
51+
def SourceID(self):
52+
return self.attributes.get("SourceID", None)
53+
54+
@property
55+
def Volume(self):
56+
return self.attributes.get("Volume", None)
57+
58+
@property
59+
def Mute(self):
60+
return self.attributes.get("Mute", False)
61+
62+
@property
63+
def Hidden(self):
64+
return self.attributes.get("Hidden", False)
65+
66+
@property
67+
def EnabledSources(self):
68+
return self.attributes.get("EnabledSources", None)
69+
70+
@property
71+
def FixedVolumeEnabled(self):
72+
return self.attributes.get("FixedVolumeEnabled", False)
73+
74+
@property
75+
def VolumeControlType(self):
76+
return self.attributes.get("VolumeControlType", 1)
77+
78+
@property
79+
def HidePowerControl(self):
80+
return self.attributes.get("HidePowerControl", False)
81+
82+
@property
83+
def HideSourceControl(self):
84+
return self.attributes.get("HideSourceControl", False)
85+
86+
@property
87+
def SharedRoomID(self):
88+
return self.attributes.get("SharedRoomID", 0)
89+
90+
@property
91+
def MasterMode(self):
92+
return self.attributes.get("MasterMode", False)
93+
94+
@property
95+
def SleepEnabled(self):
96+
return self.attributes.get("SleepEnabled", False)
97+
98+
@property
99+
def DND(self):
100+
return self.attributes.get("DND", False)
101+
102+
103+
class CasaTunesSource(CasaTunesObject):
104+
"""Read-only accessors for Source data."""
105+
106+
@property
107+
def SourceID(self):
108+
return self.attributes.get("SourceID", None)
109+
110+
@property
111+
def Name(self):
112+
return self.attributes.get("Name", "")
113+
114+
@property
115+
def Hidden(self):
116+
return self.attributes.get("Hidden", False)
117+
118+
@property
119+
def MediaTypesSupported(self):
120+
return self.attributes.get("MediaTypesSupported", 0)
121+
122+
@property
123+
def Type(self):
124+
return self.attributes.get("Type", None)
125+
126+
127+
class CasaTunesNowPlaying(CasaTunesObject):
128+
"""Read-only accessors for NowPlaying data."""
129+
130+
@property
131+
def SourceID(self):
132+
return self.attributes.get("SourceID", None)
133+
134+
@property
135+
def Status(self):
136+
return self.attributes.get("Status", 0)
137+
138+
@property
139+
def ShuffleMode(self):
140+
return self.attributes.get("ShuffleMode", False)
141+
142+
@property
143+
def RepeatMode(self):
144+
return self.attributes.get("RepeatMode", 0)
145+
146+
@property
147+
def CurrProgress(self):
148+
return self.attributes.get("CurrProgress", None)
149+
150+
@property
151+
def CurrSong(self):
152+
return CasaTunesSong(None, self.attributes.get("CurrSong") or {})
153+
154+
155+
class CasaTunesSong(CasaTunesObject):
156+
"""Read-only accessors for Song data."""
157+
158+
@property
159+
def Title(self):
160+
return self.attributes.get("Title", "")
161+
162+
@property
163+
def Artists(self):
164+
return self.attributes.get("Artists", "")
165+
166+
@property
167+
def Album(self):
168+
return self.attributes.get("Album", "")
169+
170+
@property
171+
def Duration(self):
172+
return self.attributes.get("Duration", None)
173+
174+
@property
175+
def ArtworkURI(self):
176+
return self.attributes.get("ArtworkURI", "")

requirements_test.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
pytest-homeassistant-custom-component==0.13.364
2-
pycasatunes==0.1.3
3-
async-timeout==5.0.1
42
ruff==0.16.6

tests/test_api.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
import importlib.util
44
import sys
5+
import types
56
from pathlib import Path
67
from unittest.mock import AsyncMock
78

89
import pytest
910
from aiohttp import ClientError
10-
from pycasatunes.exceptions import CasaException
1111

12+
package = types.ModuleType("casatunes_transport")
13+
package.__path__ = [str(Path(__file__).parents[1] / "custom_components/casatunes")]
14+
sys.modules[package.__name__] = package
1215
spec = importlib.util.spec_from_file_location(
13-
"casatunes_api", Path(__file__).parents[1] / "custom_components/casatunes/api.py"
16+
"casatunes_transport.api", Path(package.__path__[0]) / "api.py"
1417
)
1518
api = importlib.util.module_from_spec(spec)
1619
sys.modules[spec.name] = api
1720
spec.loader.exec_module(api)
21+
CasaException = api.CasaException
1822

1923

2024
class Response:

0 commit comments

Comments
 (0)