Skip to content

Commit

Permalink
Merge 30880ed into a5aa4ea
Browse files Browse the repository at this point in the history
  • Loading branch information
broox committed Jan 19, 2023
2 parents a5aa4ea + 30880ed commit 80af485
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 59 deletions.
36 changes: 32 additions & 4 deletions nuheat/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import requests
import nuheat.config as config
from nuheat import config
from nuheat.thermostat import NuHeatThermostat

_LOGGER = logging.getLogger(__name__)
Expand All @@ -9,21 +9,45 @@

class NuHeat(object):

def __init__(self, username, password, session_id=None):
def __init__(self, username, password, session_id=None, brand=config.NUHEAT):
"""
Initialize a NuHeat API session
:param username: NuHeat username
:param username: NuHeat password
:param session_id: A Session ID token to re-use to avoid re-authenticating
:param brand: Manages which API is used, can be NUHEAT or MAPEHEAT
"""
self.username = username
self.password = password
self._session_id = session_id
self._brand = brand if brand in config.BRANDS else config.BRANDS[0]

def __repr__(self):
return "<NuHeat username='{}'>".format(self.username)

@property
def _hostname(self):
return config.HOSTNAMES.get(self._brand)

@property
def _api_url(self):
return f"https://{self._hostname}/api"

@property
def _auth_url(self):
return f"{self._api_url}/authenticate/user"

@property
def _request_headers(self):
return {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
"HOST": self._hostname,
"DNT": "1",
"Origin": self._api_url,
}

def authenticate(self):
"""
Authenticate against the NuHeat API
Expand All @@ -38,7 +62,11 @@ def authenticate(self):
"Password": self.password,
"application": "0"
}
data = self.request(config.AUTH_URL, method="POST", data=post_data)
data = self.request(
url=self._auth_url,
method="POST",
data=post_data,
)
session_id = data.get("SessionId")
if not session_id:
raise Exception("Authentication error")
Expand All @@ -63,7 +91,7 @@ def request(self, url, method="GET", data=None, params=None, retry=True):
:param params: Querystring parameters
:param retry: Attempt to re-authenticate and retry request if necessary
"""
headers = config.REQUEST_HEADERS
headers = self._request_headers

if params and self._session_id:
params['sessionid'] = self._session_id
Expand Down
17 changes: 6 additions & 11 deletions nuheat/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
API_URL = "https://www.mynuheat.com/api"

AUTH_URL = API_URL + "/authenticate/user"
THERMOSTAT_URL = API_URL + "/thermostat"

REQUEST_HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
"Host": "mynuheat.com",
"DNT": "1",
"Origin": "https://mynuheat.com/api"
NUHEAT = "NUHEAT"
MAPEHEAT = "MAPEHEAT"
BRANDS = (NUHEAT, MAPEHEAT)
HOSTNAMES = {
NUHEAT: "mynuheat.com",
MAPEHEAT: "mymapeheat.com",
}

# NuHeat Schedule Modes
Expand Down
23 changes: 21 additions & 2 deletions nuheat/thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ def __repr__(self):
self.target_celsius
)

@classmethod
def get_url(cls, api_url):
"""
A helper method to solve a circular dependency when testing
"""
return f"{api_url}/thermostat"

@property
def _url(self):
return self.get_url(self._session._api_url)

@property
def fahrenheit(self):
"""
Expand Down Expand Up @@ -138,7 +149,10 @@ def get_data(self):
params = {
"serialnumber": self.serial_number
}
data = self._session.request(config.THERMOSTAT_URL, params=params)
data = self._session.request(
url=self._url,
params=params,
)

self._data = data

Expand Down Expand Up @@ -338,4 +352,9 @@ def set_data(self, post_data):
params = {
"serialnumber": self.serial_number
}
self._session.request(config.THERMOSTAT_URL, method="POST", data=post_data, params=params)
self._session.request(
url=self._url,
method="POST",
data=post_data,
params=params,
)
32 changes: 23 additions & 9 deletions tests/test_nuheat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from mock import patch
from urllib.parse import urlencode

from nuheat import NuHeat, NuHeatThermostat, config
from nuheat import NuHeat, NuHeatThermostat
from . import NuTestCase, load_fixture


Expand All @@ -24,32 +24,34 @@ def test_repr(self):

@responses.activate
def test_successful_authentication(self):
api = NuHeat("test@example.com", "secure-password")

response_data = load_fixture("auth_success.json")
responses.add(
responses.POST,
config.AUTH_URL,
api._auth_url,
status=200,
body=json.dumps(response_data),
content_type="application/json"
)

api = NuHeat("test@example.com", "secure-password")
self.assertIsNone(api._session_id)
api.authenticate()
self.assertEqual(api._session_id, response_data.get("SessionId"))

@responses.activate
def test_authentication_error(self):
api = NuHeat("test@example.com", "secure-password")

response_data = load_fixture("auth_error.json")
responses.add(
responses.POST,
config.AUTH_URL,
api._auth_url,
status=200,
body=json.dumps(response_data),
content_type="application/json"
)

api = NuHeat("test@example.com", "secure-password")
with self.assertRaises(Exception) as _:
api.authenticate()
self.assertIsNone(api._session_id)
Expand Down Expand Up @@ -81,8 +83,14 @@ def test_get_request(self):
self.assertEqual(response.status_code, 200)
self.assertUrlsEqual(response.request.url, "{}?{}".format(url, urlencode(params)))
request_headers = response.request.headers
self.assertEqual(request_headers["Origin"], config.REQUEST_HEADERS["Origin"])
self.assertEqual(request_headers["Content-Type"], config.REQUEST_HEADERS["Content-Type"])
self.assertEqual(
request_headers["Origin"],
api._request_headers["Origin"],
)
self.assertEqual(
request_headers["Content-Type"],
api._request_headers["Content-Type"],
)

@responses.activate
def test_post_request(self):
Expand All @@ -102,5 +110,11 @@ def test_post_request(self):
self.assertUrlsEqual(response.request.url, "{}?{}".format(url, urlencode(params)))
self.assertEqual(response.request.body, urlencode(data))
request_headers = response.request.headers
self.assertEqual(request_headers["Origin"], config.REQUEST_HEADERS["Origin"])
self.assertEqual(request_headers["Content-Type"], config.REQUEST_HEADERS["Content-Type"])
self.assertEqual(
request_headers["Origin"],
api._request_headers["Origin"],
)
self.assertEqual(
request_headers["Content-Type"],
api._request_headers["Content-Type"],
)

0 comments on commit 80af485

Please sign in to comment.