Skip to content

Commit

Permalink
Refactor: move hubitat config to it's own class in hubitat_gt.py so t…
Browse files Browse the repository at this point in the history
…hat config can be used by component without circular import; fix hubitat address generation
  • Loading branch information
anschweitzer committed Oct 15, 2023
1 parent 13d0321 commit 500f176
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 112 deletions.
21 changes: 9 additions & 12 deletions src/gwproto/data_classes/components/hubitat_component.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
from typing import Optional

import yarl

from gwproto.data_classes.component import Component
from gwproto.types.hubitat_gt import HubitatGt


class HubitatComponent(Component):
host: str
maker_api_id: int
access_token: str
mac_address: str
hubitat_gt: HubitatGt

def __init__(
self,
component_id: str,
component_attribute_class_id: str,
host: str,
maker_api_id: int,
access_token: str,
mac_address: str,
hubitat_gt: HubitatGt,
display_name: Optional[str] = None,
hw_uid: Optional[str] = None,
):
self.host = host
self.maker_api_id = maker_api_id
self.access_token = access_token
self.mac_address = mac_address
self.hubitat_gt = hubitat_gt
super().__init__(
component_id=component_id,
component_attribute_class_id=component_attribute_class_id,
display_name=display_name,
hw_uid=hw_uid,
)

def urls(self) -> dict[str, Optional[yarl.URL]]:
return self.hubitat_gt.urls()
19 changes: 15 additions & 4 deletions src/gwproto/data_classes/components/hubitat_tank_component.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Optional

import yarl

from gwproto.data_classes.component import Component
from gwproto.data_classes.components.hubitat_component import HubitatComponent
from gwproto.data_classes.sh_node import ShNode
from gwproto.types.hubitat_component_gt import HubitatComponentGt
from gwproto.types.hubitat_component_gt import HubitatRESTResolutionSettings
from gwproto.types.hubitat_gt import HubitatGt
from gwproto.types.hubitat_tank_gt import DEFAULT_SENSOR_NODE_NAME_FORMAT
from gwproto.types.hubitat_tank_gt import FibaroTempSensorSettings
from gwproto.types.hubitat_tank_gt import FibaroTempSensorSettingsGt
Expand All @@ -31,10 +34,12 @@ def __init__(
self.hubitat = HubitatComponentGt(
ComponentId=tank_gt.hubitat_component_id,
ComponentAttributeClassId="00000000-0000-0000-0000-000000000000",
Host="",
MakerApiId=-1,
AccessToken="",
MacAddress="000000000000",
Hubitat=HubitatGt(
Host="",
MakerApiId=-1,
AccessToken="",
MacAddress="000000000000",
),
)
self.devices_gt = list(tank_gt.devices)
super().__init__(
Expand Down Expand Up @@ -85,3 +90,9 @@ def resolve(
# with the actual hubitat component containing data.
self.hubitat = hubitat_component_gt
self.devices = devices

def urls(self) -> dict[str, Optional[yarl.URL]]:
urls = self.hubitat.urls()
for device in self.devices:
urls[device.node_name] = device.url
return urls
48 changes: 11 additions & 37 deletions src/gwproto/types/hubitat_component_gt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,21 @@
from typing import Any
from typing import Dict
from typing import Literal
from typing import Optional

from gridworks.property_format import predicate_validator
import yarl

from gwproto.data_classes.component import Component
from gwproto.data_classes.components.hubitat_component import HubitatComponent
from gwproto.types.component_gt import ComponentGt
from gwproto.types.rest_poller_gt import URLArgs
from gwproto.types.hubitat_gt import HubitatGt
from gwproto.types.rest_poller_gt import URLConfig
from gwproto.utils import has_mac_address_format


class HubitatComponentGt(ComponentGt):
Host: str
MakerApiId: int
AccessToken: str
MacAddress: str
Hubitat: HubitatGt
TypeName: Literal["hubitat.component.gt"] = "hubitat.component.gt"

_is_mac_address = predicate_validator("MacAddress", has_mac_address_format)

def as_dict(self) -> Dict[str, Any]:
return self.dict(exclude_unset=True)

Expand All @@ -33,40 +28,22 @@ def __hash__(self):
return hash((type(self),) + tuple(self.__dict__.values()))

def url_config(self) -> URLConfig:
return URLConfig(
url_args=URLArgs(
scheme="http",
host=self.Host,
query=[("access_token", self.AccessToken)],
),
url_path_format="/apps/api/{app_id}",
url_path_args={"app_id": self.MakerApiId},
)
return self.Hubitat.url_config()

def maker_api_url_config(self) -> URLConfig:
config = self.url_config()
if config.url_args.query is None:
config.url_args.query = []
config.url_args.query.append(("access_token", self.AccessToken))
if config.url_path_format is None:
config.url_path_format = ""
config.url_path_format += "/apps/api/{app_id}"
if config.url_path_args is None:
config.url_path_args = {}
config.url_path_args.update({"app_id": self.MakerApiId})
return config
return self.Hubitat.maker_api_url_config()

def urls(self) -> dict[str, Optional[yarl.URL]]:
return self.Hubitat.urls()

@classmethod
def from_data_class(cls, component: HubitatComponent) -> "HubitatComponentGt":
return HubitatComponentGt(
ComponentId=component.component_id,
ComponentAttributeClassId=component.component_attribute_class_id,
Host=component.host,
MakerApiId=component.maker_api_id,
AccessToken=component.access_token,
Hubitat=component.hubitat_gt,
DisplayName=component.display_name,
HwUid=component.hw_uid,
MacAddress=component.mac_address,
)

def to_data_class(self) -> HubitatComponent:
Expand All @@ -76,12 +53,9 @@ def to_data_class(self) -> HubitatComponent:
return HubitatComponent(
component_id=self.ComponentId,
component_attribute_class_id=self.ComponentAttributeClassId,
host=self.Host,
maker_api_id=self.MakerApiId,
access_token=self.AccessToken,
hubitat_gt=self.Hubitat,
display_name=self.DisplayName,
hw_uid=self.HwUid,
mac_address=self.MacAddress,
)


Expand Down
48 changes: 48 additions & 0 deletions src/gwproto/types/hubitat_gt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Optional

import yarl
from gridworks.property_format import predicate_validator
from pydantic import BaseModel

from gwproto.types.rest_poller_gt import URLArgs
from gwproto.types.rest_poller_gt import URLConfig
from gwproto.utils import has_mac_address_format


class HubitatGt(BaseModel):
Host: str
MakerApiId: int
AccessToken: str
MacAddress: str

_is_mac_address = predicate_validator("MacAddress", has_mac_address_format)

def url_config(self) -> URLConfig:
return URLConfig(
url_args=URLArgs(
scheme="http",
host=self.Host,
),
)

def maker_api_url_config(self) -> URLConfig:
config = self.url_config()
if config.url_args.query is None:
config.url_args.query = []
config.url_args.query.append(("access_token", self.AccessToken))
if config.url_path_format is None:
config.url_path_format = ""
config.url_path_format += "/apps/api/{app_id}"
if config.url_path_args is None:
config.url_path_args = {}
config.url_path_args.update({"app_id": self.MakerApiId})
return config

def url_configs(self) -> dict[str, URLConfig]:
return dict(base=self.url_config(), maker_api=self.maker_api_url_config())

def urls(self) -> dict[str, Optional[yarl.URL]]:
return {
name: URLConfig.make_url(config)
for name, config in self.url_configs().items()
}
67 changes: 8 additions & 59 deletions src/gwproto/types/hubitat_tank_gt.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,57 +103,6 @@ def clear_property_cache(self):
]:
self.__dict__.pop(prop, None)

def validate_url(self, hubitat: HubitatRESTResolutionSettings):
url_str = ""
try:
url_str = str(self.rest.url)

# check host
if hubitat.component_gt.Host != self.rest.url.host:
raise ValueError(
"ERROR host expected to be "
f"{hubitat.component_gt.Host} but host in url is "
f"{self.rest.url.host}, from url: <{url_str}>"
)

# check api_id
if hubitat.component_gt.MakerApiId != self.api_id:
raise ValueError(
"ERROR api_id expected to be "
f"{hubitat.component_gt.MakerApiId} but api_id in url is "
f"{self.api_id}, from url: <{url_str}>"
)

# check device_id
id_match = HUBITAT_ID_REGEX.match(url_str)
if not id_match:
raise ValueError(
f"ERROR. ID regex <{HUBITAT_ID_REGEX.pattern}> failed to match "
f" url <{url_str}>"
)
found_device_id = int(id_match.group("device_id"))
if self.device_id != found_device_id:
raise ValueError(
"ERROR explicit device_id is "
f"{self.device_id} but device in url is "
f"{found_device_id}, from url: <{url_str}>"
)

# check token match
if hubitat.component_gt.AccessToken != self.access_token:
raise ValueError(
"ERROR explicit access_token is "
f"{hubitat.component_gt.AccessToken} but device in url is "
f"{self.access_token}, from url: <{url_str}>"
)

except BaseException as e:
if isinstance(e, ValueError):
raise e
raise ValueError(
f"ERROR in FibaroTempSensorSettings.validate_url() for url <{url_str}>"
) from e

def resolve_rest(
self,
hubitat: HubitatRESTResolutionSettings,
Expand Down Expand Up @@ -188,24 +137,24 @@ def resolve_rest(
self.rest.clear_property_cache()

# Verify new URL produced by combining any inline REST configuration
# with hubitate configuration is valid.
# with hubitat configuration is valid.
url_str = ""
try:
url_str = str(self.rest.url)

hubitat_gt = hubitat.component_gt.Hubitat
# check host
if hubitat.component_gt.Host != self.rest.url.host:
if hubitat_gt.Host != self.rest.url.host:
raise ValueError(
"ERROR host expected to be "
f"{hubitat.component_gt.Host} but host in url is "
f"{hubitat_gt.Host} but host in url is "
f"{self.rest.url.host}, from url: <{url_str}>"
)

# check api_id
if hubitat.component_gt.MakerApiId != self.api_id:
if hubitat_gt.MakerApiId != self.api_id:
raise ValueError(
"ERROR api_id expected to be "
f"{hubitat.component_gt.MakerApiId} but api_id in url is "
f"{hubitat_gt.MakerApiId} but api_id in url is "
f"{self.api_id}, from url: <{url_str}>"
)

Expand All @@ -225,10 +174,10 @@ def resolve_rest(
)

# check token match
if hubitat.component_gt.AccessToken != self.access_token:
if hubitat_gt.AccessToken != self.access_token:
raise ValueError(
"ERROR explicit access_token is "
f"{hubitat.component_gt.AccessToken} but device in url is "
f"{hubitat_gt.AccessToken} but device in url is "
f"{self.access_token}, from url: <{url_str}>"
)

Expand Down

0 comments on commit 500f176

Please sign in to comment.