Skip to content

Commit

Permalink
Restore getmac, some cleanup
Browse files Browse the repository at this point in the history
The Hubitat HA integration needs the mac address
  • Loading branch information
jason0x43 committed Dec 28, 2020
1 parent 446b9e6 commit 5da6a4a
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 32 deletions.
72 changes: 41 additions & 31 deletions hubitatmaker/hub.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Hubitat API."""
from contextlib import contextmanager
from logging import getLogger
import re
import socket
from types import MappingProxyType
from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Union
from urllib.parse import ParseResult, quote, urlparse

import aiohttp
import getmac

from . import server
from .const import ID_HSM_STATUS, ID_MODE
Expand All @@ -31,6 +33,7 @@ class Hub:
host: str
scheme: str
token: str
mac: str

_server: server.Server

Expand Down Expand Up @@ -60,10 +63,11 @@ def __init__(
if not host or not app_id or not access_token:
raise InvalidConfig()

self.event_url = self._get_event_url(port, event_url)
self.port = self._get_event_port(port, event_url)
self.event_url = _get_event_url(port, event_url)
self.port = _get_event_port(port, event_url)
self.app_id = app_id
self.token = access_token
self.mac = ""

self.set_host(host)

Expand Down Expand Up @@ -224,6 +228,7 @@ def set_host(self, host: str) -> None:
self.host = host_url.netloc or host_url.path
self.base_url = f"{self.scheme}://{self.host}"
self.api_url = f"{self.base_url}/apps/api/{self.app_id}"
self.mac = _get_mac_address(self.host) or ""

async def set_port(self, port: int) -> None:
"""Set the port that the event listener server will listen on.
Expand All @@ -242,35 +247,6 @@ async def _check_api(self) -> None:
"""
await self._api_request("devices")

def _get_event_port(
self, port: Optional[int], event_url: Optional[str]
) -> Optional[int]:
"""Given an optional port and event URL, return the event port"""
if port is not None:
return port
if event_url is not None:
u = urlparse(event_url)
return u.port
return None

def _get_event_url(
self, port: Optional[int], event_url: Optional[str]
) -> Optional[str]:
"""Given an optional port and event URL, return a complete event URL"""
if event_url is not None:
u = urlparse(event_url)
if u.port is None and port is not None:
return ParseResult(
scheme=u.scheme,
netloc=f"{u.hostname}:{port}",
path=u.path,
params=u.params,
query=u.query,
fragment=u.fragment,
).geturl()
return event_url
return None

def _process_event(self, event: Dict[str, Any]) -> None:
"""Process an event received from the hub."""
try:
Expand Down Expand Up @@ -418,3 +394,37 @@ def _open_socket(*args: Any, **kwargs: Any) -> Iterator[socket.socket]:
yield s
finally:
s.close()


def _get_event_port(port: Optional[int], event_url: Optional[str]) -> Optional[int]:
"""Given an optional port and event URL, return the event port"""
if port is not None:
return port
if event_url is not None:
u = urlparse(event_url)
return u.port
return None


def _get_event_url(port: Optional[int], event_url: Optional[str]) -> Optional[str]:
"""Given an optional port and event URL, return a complete event URL"""
if event_url is not None:
u = urlparse(event_url)
if u.port is None and port is not None:
return ParseResult(
scheme=u.scheme,
netloc=f"{u.hostname}:{port}",
path=u.path,
params=u.params,
query=u.query,
fragment=u.fragment,
).geturl()
return event_url
return None


def _get_mac_address(host: str) -> Optional[str]:
"""Return the mac address of a remote host."""
if re.match("\\d+\\.\\d+\\.\\d+\\.\\d+", host):
return getmac.get_mac_address(ip=host)
return getmac.get_mac_address(hostname=host)
24 changes: 24 additions & 0 deletions hubitatmaker/tests/test_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,24 +147,31 @@ async def __aexit__(self, exc_type, exc, tb):
return fake_request


def fake_get_mac_address(**kwargs: str):
return "aa:bb:cc:dd:ee:ff"


class TestHub(TestCase):
def setUp(self):
global requests
requests = []
load_data()

@patch("getmac.get_mac_address", new=fake_get_mac_address)
def test_hub_checks_arguments(self) -> None:
"""The hub should check for its required inputs."""
self.assertRaises(InvalidConfig, Hub, "", "1234", "token")
self.assertRaises(InvalidConfig, Hub, "1.2.3.4", "", "token")
self.assertRaises(InvalidConfig, Hub, "1.2.3.4", "1234", "")
Hub("1.2.3.4", "1234", "token")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
def test_initial_values(self) -> None:
"""Hub properties should have expected initial values."""
hub = Hub("1.2.3.4", "1234", "token")
self.assertEqual(list(hub.devices), [])

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_start_server(self, MockServer) -> None:
Expand All @@ -173,6 +180,7 @@ def test_start_server(self, MockServer) -> None:
wait_for(hub.start())
self.assertTrue(MockServer.called)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_start(self, MockServer) -> None:
Expand All @@ -192,6 +200,7 @@ def test_start(self, MockServer) -> None:
self.assertRegex(requests[-2]["url"], "modes$")
self.assertRegex(requests[-1]["url"], "hsm$")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch(
"aiohttp.request",
new=create_fake_request({"/hsm": FakeResponse(400, url="/hsm")}),
Expand All @@ -207,6 +216,7 @@ def test_start_no_hsm(self, MockServer) -> None:
self.assertRegex(requests[-2]["url"], "modes$")
self.assertRegex(requests[-1]["url"], "hsm$")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_default_event_url(self, MockServer) -> None:
Expand All @@ -217,6 +227,7 @@ def test_default_event_url(self, MockServer) -> None:
url = unquote(requests[0]["url"])
self.assertRegex(url, r"http://127.0.0.1:81$")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_custom_event_url(self, MockServer) -> None:
Expand All @@ -227,6 +238,7 @@ def test_custom_event_url(self, MockServer) -> None:
url = unquote(requests[0]["url"])
self.assertRegex(url, r"http://foo\.local$")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_custom_event_url_without_port(self, MockServer) -> None:
Expand All @@ -237,6 +249,7 @@ def test_custom_event_url_without_port(self, MockServer) -> None:
url = unquote(requests[0]["url"])
self.assertRegex(url, r"http://foo\.local:420$")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_custom_event_port(self, MockServer) -> None:
Expand All @@ -246,6 +259,7 @@ def test_custom_event_port(self, MockServer) -> None:
wait_for(hub.start())
self.assertEqual(MockServer.call_args[0][2], 420)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_custom_event_port_from_url(self, MockServer) -> None:
Expand All @@ -255,6 +269,7 @@ def test_custom_event_port_from_url(self, MockServer) -> None:
wait_for(hub.start())
self.assertEqual(MockServer.call_args[0][2], 416)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_custom_event_port_and_url(self, MockServer) -> None:
Expand All @@ -264,6 +279,7 @@ def test_custom_event_port_and_url(self, MockServer) -> None:
wait_for(hub.start())
self.assertEqual(MockServer.call_args[0][2], 420)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_stop_server(self, MockServer) -> None:
Expand All @@ -274,6 +290,7 @@ def test_stop_server(self, MockServer) -> None:
hub.stop()
self.assertTrue(MockServer.return_value.stop.called)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_devices_loaded(self, MockServer) -> None:
Expand All @@ -282,6 +299,7 @@ def test_devices_loaded(self, MockServer) -> None:
wait_for(hub.start())
self.assertEqual(len(hub.devices), 9)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_event(self, MockServer) -> None:
Expand All @@ -297,6 +315,7 @@ def test_process_event(self, MockServer) -> None:
attr = device.attributes["switch"]
self.assertEqual(attr.value, "on")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_mode_event(self, MockServer) -> None:
Expand All @@ -317,6 +336,7 @@ def listener(_: Any):
hub._process_event(events["mode"])
self.assertTrue(handler_called)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_hsm_event(self, MockServer) -> None:
Expand All @@ -337,6 +357,7 @@ def listener(_: Any):
hub._process_event(events["hsm"])
self.assertTrue(handler_called)

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_other_event(self, MockServer) -> None:
Expand All @@ -352,6 +373,7 @@ def test_process_other_event(self, MockServer) -> None:
attr = device.attributes["switch"]
self.assertEqual(attr.value, "off")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_set_hsm(self, MockServer) -> None:
Expand All @@ -365,6 +387,7 @@ def test_process_set_hsm(self, MockServer) -> None:
hub._process_event(events["hsm"])
self.assertEqual(hub.hsm_status, "armedAway")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_process_set_mode(self, MockServer) -> None:
Expand All @@ -378,6 +401,7 @@ def test_process_set_mode(self, MockServer) -> None:
hub._process_event(events["mode"])
self.assertEqual(hub.mode, "Evening")

@patch("getmac.get_mac_address", new=fake_get_mac_address)
@patch("aiohttp.request", new=create_fake_request())
@patch("hubitatmaker.server.Server")
def test_set_event_url(self, MockServer) -> None:
Expand Down
14 changes: 13 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ include = ["LICENSE"]
[tool.poetry.dependencies]
aiohttp = "^3.6.2"
python = "^3.7.1"
getmac = "^0.8.2"

[tool.poetry.dev-dependencies]
flake8 = "^3.8.3"
Expand Down

0 comments on commit 5da6a4a

Please sign in to comment.