Skip to content

Commit

Permalink
Refactor and modernise package (#74)
Browse files Browse the repository at this point in the history
* Docstrings

* Fixes

* Refactor Lyric object names

* Update

* Modernise

* Refactor Lyric client headers handling

* Update and format tests

* Move

* Fix
  • Loading branch information
timmo001 authored Apr 1, 2024
1 parent c45ef69 commit c1c5a45
Show file tree
Hide file tree
Showing 18 changed files with 422 additions and 441 deletions.
78 changes: 42 additions & 36 deletions aiolyric/__init__.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
"""Lyric: Init"""
from typing import List
"""Lyric."""

import logging

from aiohttp import ClientResponse

from .client import LyricClient
from .const import BASE_URL
from .objects.base import LyricBase
from .objects.device import LyricDevice
from .objects.location import LyricLocation
from .objects.priority import LyricPriority, LyricRoom


class Lyric(LyricBase):
class Lyric:
"""Handles authentication refresh tokens."""

logger = logging.getLogger(__name__)

def __init__(
self,
client: "LyricClient",
client: LyricClient,
client_id: str,
) -> None:
"""Initialize the token manager class."""
self._client = client
self._client_id = client_id
self._devices: List[LyricDevice] = []
self._devices: list[LyricDevice] = []
self._devices_dict: dict = {}
self._locations: List[LyricLocation] = []
self._locations: list[LyricLocation] = []
self._locations_dict: dict = {}
self._rooms_dict: dict = {}

@property
def client_id(self) -> str:
"""Return the client id."""
return self._client_id

@property
def devices(self) -> List[LyricDevice]:
def devices(self) -> list[LyricDevice]:
"""Return the devices."""
return self._devices

@property
def devices_dict(self) -> dict:
"""Return the devices dict."""
return self._devices_dict

@property
def locations(self) -> List[LyricLocation]:
def locations(self) -> list[LyricLocation]:
"""Return the locations."""
return self._locations

@property
def locations_dict(self) -> dict:
"""Return the locations dict."""
return self._locations_dict

@property
def rooms_dict(self) -> dict[str, dict[str, LyricRoom]]:
"""Return the rooms dict."""
return self._rooms_dict

async def get_devices(
Expand Down Expand Up @@ -80,11 +89,7 @@ async def get_locations(self) -> None:
for location in self._locations:
self._locations_dict[location.locationID] = location

async def get_thermostat_rooms(
self,
location_id: int,
device_id: str
) -> None:
async def get_thermostat_rooms(self, location_id: int, device_id: str) -> None:
"""Get Priority, which contains accessory information."""
response: ClientResponse = await self._client.get(
f"{BASE_URL}/devices/thermostats/{device_id}/priority?apikey={self.client_id}&locationId={location_id}"
Expand All @@ -94,23 +99,24 @@ async def get_thermostat_rooms(

priority = LyricPriority(json)

macId = priority.deviceId # device id in the priority payload refers to the mac address of the device
self._rooms_dict[macId]: dict = {}
# device id in the priority payload refers to the mac address of the device
mac_id = priority.deviceId
self._rooms_dict[mac_id] = {}

# add each room to the room dictionary. Rooms contain motion, temp, and humidity averages for all accessories in a room
for room in priority.currentPriority.rooms:
self._rooms_dict[macId][room.id] = room
self._rooms_dict[mac_id][room.id] = room

async def update_thermostat(
self,
location: LyricLocation,
device: LyricDevice,
mode=None,
heatSetpoint=None,
coolSetpoint=None,
autoChangeoverActive=None,
thermostatSetpointStatus=None,
nextPeriodTime=None,
heat_setpoint=None,
cool_setpoint=None,
auto_changeover_active=None,
thermostat_setpoint_status=None,
next_period_time=None,
) -> ClientResponse:
"""Update Theremostat."""
self.logger.debug("Update Thermostat")
Expand All @@ -122,35 +128,35 @@ async def update_thermostat(
else:
data["mode"] = device.changeableValues.mode

if heatSetpoint is not None:
data["heatSetpoint"] = heatSetpoint
if heat_setpoint is not None:
data["heatSetpoint"] = heat_setpoint
else:
data["heatSetpoint"] = device.changeableValues.heatSetpoint
if coolSetpoint is not None:
data["coolSetpoint"] = coolSetpoint
if cool_setpoint is not None:
data["coolSetpoint"] = cool_setpoint
else:
data["coolSetpoint"] = device.changeableValues.coolSetpoint

# Only for TCC devices
if autoChangeoverActive is not None:
data["autoChangeoverActive"] = autoChangeoverActive
if auto_changeover_active is not None:
data["autoChangeoverActive"] = auto_changeover_active
elif device.changeableValues.autoChangeoverActive is not None:
data["autoChangeoverActive"] = device.changeableValues.autoChangeoverActive

# Only for LCC devices
if thermostatSetpointStatus is not None:
data["thermostatSetpointStatus"] = thermostatSetpointStatus
if thermostat_setpoint_status is not None:
data["thermostatSetpointStatus"] = thermostat_setpoint_status
elif device.changeableValues.thermostatSetpointStatus is not None:
if device.changeableValues.thermostatSetpointStatus == "NoHold":
data["thermostatSetpointStatus"] = "TemporaryHold"
else:
data[
"thermostatSetpointStatus"
] = device.changeableValues.thermostatSetpointStatus
data["thermostatSetpointStatus"] = (
device.changeableValues.thermostatSetpointStatus
)

if data.get("thermostatSetpointStatus", "") == "HoldUntil":
if nextPeriodTime is not None:
data["nextPeriodTime"] = nextPeriodTime
if next_period_time is not None:
data["nextPeriodTime"] = next_period_time
elif device.changeableValues.nextPeriodTime == "NoHold" and mode is None:
data["nextPeriodTime"] = "TemporaryHold"
else:
Expand All @@ -177,7 +183,7 @@ async def update_fan(
if mode is not None:
data["mode"] = mode
else:
data["mode"] = device.fanMode
data["mode"] = device.settings.fanMode.fan

self.logger.debug(data)

Expand Down
2 changes: 1 addition & 1 deletion aiolyric/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@

from incremental import Version

__version__ = Version("aiolyric", 1, 1, 2, dev=0)
__version__ = Version("aiolyric", 2, 0, 0, dev=0)
__all__ = ["__version__"]
72 changes: 44 additions & 28 deletions aiolyric/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
"""Lyric: Client"""
"""Lyric client."""

from abc import abstractmethod
from asyncio import CancelledError, TimeoutError, get_event_loop
import asyncio
import logging

from aiohttp import ClientError, ClientResponse, ClientSession
import async_timeout
from aiohttp import ClientResponse, ClientSession

from ..exceptions import LyricAuthenticationException, LyricException
from ..objects.base import LyricBase


class LyricClient(LyricBase):
class LyricClient:
"""Client to handle API calls."""

def __init__(self, session: ClientSession) -> None:
logger = logging.getLogger(__name__)

def __init__(
self,
session: ClientSession,
) -> None:
"""Initialize the client."""
self._session = session

@abstractmethod
async def async_get_access_token(self) -> str:
"""Return a valid access token."""

async def get(self, url: str, **kwargs) -> ClientResponse:
async def get(
self,
url: str,
**kwargs,
) -> ClientResponse:
"""Make a GET request."""
return await self.request("GET", url, **kwargs)

async def post(self, url: str, **kwargs) -> ClientResponse:
async def post(
self,
url: str,
**kwargs,
) -> ClientResponse:
"""Make a POST request."""
return await self.request("POST", url, **kwargs)
return await self.request(
"POST",
url,
**kwargs,
)

async def request(
self, method: str in ["GET", "POST"], url: str, **kwargs
self,
method: str,
url: str,
**kwargs,
) -> ClientResponse:
"""Make a request."""
headers = kwargs.get("headers")

if headers is None:
if (headers := kwargs.get("headers")) is None:
headers = {}
else:
headers = dict(headers)
Expand All @@ -44,7 +61,7 @@ async def request(
headers["Authorization"] = f"Bearer {access_token}"
headers["Content-Type"] = "application/json"

async with async_timeout.timeout(20):
async with asyncio.timeout(20):
response: ClientResponse = await self._session.request(
method,
url,
Expand All @@ -65,17 +82,16 @@ async def request(
"status": response.status,
}
)
else:
raise LyricException(
{
"request": {
"method": method,
"url": url,
"headers": headers,
**kwargs,
},
"response": await response.json(),
"status": response.status,
}
)
raise LyricException(
{
"request": {
"method": method,
"url": url,
"headers": headers,
**kwargs,
},
"response": await response.json(),
"status": response.status,
}
)
return response
2 changes: 1 addition & 1 deletion aiolyric/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Lyric: Constants"""
"""Lyric: Constants."""

AUTH_URL = "https://api.honeywell.com/oauth2/authorize"
BASE_URL = "https://api.honeywell.com/v2"
Expand Down
2 changes: 1 addition & 1 deletion aiolyric/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Lyric: Exceptions"""
"""Lyric: Exceptions."""


class LyricException(BaseException):
Expand Down
2 changes: 1 addition & 1 deletion aiolyric/objects/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""Lyric: Objects"""
"""Lyric objects."""
20 changes: 15 additions & 5 deletions aiolyric/objects/base.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
"""Lyric: Base"""
"""Lyric base."""

import logging

from ..client import LyricClient


class LyricBase:
class LyricBaseObject:
"""Base class for Lyric."""

logger = logging.getLogger(__name__)

def __init__(self, attributes) -> None:
def __init__(
self,
attributes: dict,
) -> None:
"""Initialize."""
self.attributes = attributes


class LyricBaseClient(LyricBase):
class LyricBaseClient(LyricBaseObject):
"""Base class for Lyric."""

def __init__(self, client: "AIOGitHubAPIClient", attributes: dict) -> None:
def __init__(
self,
client: LyricClient,
attributes: dict,
) -> None:
"""Initialise."""
super().__init__(attributes)
self.client = client
Loading

0 comments on commit c1c5a45

Please sign in to comment.