Skip to content

Commit

Permalink
Code refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
elad-bar committed Nov 18, 2022
1 parent fbf2f1c commit 01ebcec
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 121 deletions.
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ repos:
args:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2.0.23

- Add test file to run locally (requires environment variables)
- Cleaner code to resolve URLs
- Remove unused constants
- Core feature: BaseAPI to handle session initialization and termination
- Core fix: wrongfully reported logs of entities getting updated when no update perform

## 2.0.22

- Fix issue with new Select options
Expand Down
93 changes: 41 additions & 52 deletions custom_components/edgeos/component/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
import sys
from typing import Awaitable, Callable

from aiohttp import ClientSession, CookieJar
from aiohttp import CookieJar

from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_create_clientsession

from ...configuration.models.config_data import ConfigData
from ...core.api.base_api import BaseAPI
Expand All @@ -25,11 +24,10 @@
class IntegrationAPI(BaseAPI):
"""The Class for handling the data retrieval."""

_session: ClientSession | None
_config_data: ConfigData | None

def __init__(self,
hass: HomeAssistant,
hass: HomeAssistant | None,
async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
):
Expand All @@ -38,7 +36,6 @@ def __init__(self,

try:
self._config_data = None
self._session = None
self._cookies = {}
self._last_valid = None

Expand Down Expand Up @@ -74,9 +71,6 @@ def csrf_token(self):
def cookies_data(self):
return self._cookies

async def terminate(self):
await self.set_status(ConnectivityStatus.Disconnected)

async def initialize(self, config_data: ConfigData):
_LOGGER.info("Initializing API")

Expand All @@ -85,14 +79,7 @@ async def initialize(self, config_data: ConfigData):

cookie_jar = CookieJar(unsafe=True)

if self.hass is None:
self._session = ClientSession(cookie_jar=cookie_jar)
else:
self._session = async_create_clientsession(
hass=self.hass, cookies=self._cookies, cookie_jar=cookie_jar,
)

await self._login()
await self.initialize_session(cookies=self._cookies, cookie_jar=cookie_jar)

except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
Expand All @@ -115,8 +102,8 @@ def _get_cookie_data(self, cookie_key):

return cookie_data

async def _login(self):
await self.set_status(ConnectivityStatus.Connecting)
async def login(self):
await super().login()

try:
username = self._config_data.username
Expand All @@ -126,11 +113,11 @@ async def _login(self):

url = self._config_data.url

if self._session.closed:
if self.session.closed:
raise SessionTerminatedException()

async with self._session.post(url, data=credentials, ssl=False) as response:
all_cookies = self._session.cookie_jar.filter_cookies(response.url)
async with self.session.post(url, data=credentials, ssl=False) as response:
all_cookies = self.session.cookie_jar.filter_cookies(response.url)

for key, cookie in all_cookies.items():
self._cookies[cookie.key] = cookie.value
Expand Down Expand Up @@ -177,11 +164,19 @@ async def _login(self):

await self.set_status(ConnectivityStatus.NotFound)

async def _async_get(self, url):
async def _async_get(self,
endpoint,
timestamp: str | None = None,
action: str | None = None,
subset: str | None = None
):

result = None
message = None
status = 404

url = self._build_endpoint(endpoint, timestamp, action, subset)

retry_attempt = 0
while retry_attempt < MAXIMUM_RECONNECT:
if retry_attempt > 0:
Expand All @@ -190,8 +185,8 @@ async def _async_get(self, url):
retry_attempt = retry_attempt + 1

try:
if self._session is not None:
async with self._session.get(url, ssl=False) as response:
if self.session is not None:
async with self.session.get(url, ssl=False) as response:
status = response.status

message = (
Expand All @@ -202,7 +197,7 @@ async def _async_get(self, url):
result = await response.json()
break
elif status == 403:
self._session = None
self.session = None
self._cookies = {}

break
Expand All @@ -225,8 +220,8 @@ async def _async_get(self, url):

def _get_post_headers(self):
headers = {}
for header_key in self._session.headers:
header = self._session.headers.get(header_key)
for header_key in self.session.headers:
header = self.session.headers.get(header_key)

if header is not None:
headers[header_key] = header
Expand All @@ -235,15 +230,17 @@ def _get_post_headers(self):

return headers

async def _async_post(self, url, data):
async def _async_post(self, endpoint, data):
result = None

try:
if self._session is not None:
url = self._build_endpoint(endpoint)

if self.session is not None:
headers = self._get_post_headers()
data_json = json.dumps(data)

async with self._session.post(url, headers=headers, data=data_json, ssl=False) as response:
async with self.session.post(url, headers=headers, data=data_json, ssl=False) as response:
response.raise_for_status()

result = await response.json()
Expand All @@ -252,7 +249,7 @@ async def _async_post(self, url, data):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno

message = f"URL: {url}, Error: {ex}, Line: {line_number}"
message = f"Endpoint: {endpoint}, Error: {ex}, Line: {line_number}"
_LOGGER.warning(f"Request failed, {message}")

return result
Expand All @@ -267,14 +264,7 @@ async def async_send_heartbeat(self, max_age=HEARTBEAT_MAX_AGE):
if current_invocation > timedelta(seconds=max_age):
current_ts = str(int(ts.timestamp()))

heartbeat_req_url = self._get_edge_os_api_endpoint(
API_HEARTBEAT
)
heartbeat_req_full_url = API_URL_HEARTBEAT_TEMPLATE.format(
heartbeat_req_url, current_ts
)

response = await self._async_get(heartbeat_req_full_url)
response = await self._async_get(API_URL_HEARTBEAT, timestamp=current_ts)

if response is not None:
_LOGGER.debug(f"Heartbeat response: {response}")
Expand Down Expand Up @@ -314,9 +304,7 @@ async def async_update(self):
async def _load_system_data(self):
try:
if self.status == ConnectivityStatus.Connected:
get_req_url = self._get_edge_os_api_endpoint(API_GET)

result_json = await self._async_get(get_req_url)
result_json = await self._async_get(API_URL_DATA, action=API_GET)

if result_json is not None:
if RESPONSE_SUCCESS_KEY in result_json:
Expand Down Expand Up @@ -348,12 +336,8 @@ async def _load_general_data(self, key):
_LOGGER.debug(f"Loading {key} data")

clean_item = key.replace(STRING_DASH, STRING_UNDERSCORE)
data_req_url = self._get_edge_os_api_endpoint(API_DATA)
data_req_full_url = API_URL_DATA_TEMPLATE.format(
data_req_url, clean_item
)

data = await self._async_get(data_req_full_url)
data = await self._async_get(API_URL_DATA_SUBSET, action=API_DATA, subset=clean_item)

if data is not None:
if RESPONSE_SUCCESS_KEY in data:
Expand Down Expand Up @@ -388,9 +372,7 @@ async def set_interface_state(self, interface: EdgeOSInterfaceData, is_enabled:
}
}

get_req_url = self._get_edge_os_api_endpoint(endpoint)

result_json = await self._async_post(get_req_url, data)
result_json = await self._async_post(endpoint, data)

if result_json is not None:
set_response = result_json.get(API_DATA_SAVE.upper(), {})
Expand All @@ -405,7 +387,14 @@ async def set_interface_state(self, interface: EdgeOSInterfaceData, is_enabled:
if not modified:
_LOGGER.error(f"Failed to set state of interface {interface.name} to {is_enabled}")

def _get_edge_os_api_endpoint(self, endpoint):
url = API_URL.format(self._config_data.url, endpoint)
def _build_endpoint(self, endpoint, timestamp: str | None = None, action: str | None = None, subset: str | None = None):
data = {
API_URL_PARAMETER_BASE_URL: self._config_data.url,
API_URL_PARAMETER_TIMESTAMP: timestamp,
API_URL_PARAMETER_ACTION: action,
API_URL_PARAMETER_SUBSET: subset
}

url = endpoint.format(**data)

return url
2 changes: 1 addition & 1 deletion custom_components/edgeos/component/api/storage_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class StorageAPI(BaseAPI):
_data: dict

def __init__(self,
hass: HomeAssistant,
hass: HomeAssistant | None,
async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
):
Expand Down
32 changes: 12 additions & 20 deletions custom_components/edgeos/component/api/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
from typing import Awaitable, Callable
from urllib.parse import urlparse

from aiohttp import ClientSession

from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_create_clientsession

from ...component.helpers.const import *
from ...configuration.models.config_data import ConfigData
Expand All @@ -25,23 +22,21 @@


class IntegrationWS(BaseAPI):
_session: ClientSession | None
_config_data: ConfigData | None
_api_data: dict
_can_log_messages: bool
_previous_message: dict | None
_ws_handlers: dict

def __init__(self,
hass: HomeAssistant,
hass: HomeAssistant | None,
async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
):

super().__init__(hass, async_on_data_changed, async_on_status_changed)

self._config_data = None
self._session = None
self._ws = None
self._api_data = {}
self._remove_async_track_time = None
Expand Down Expand Up @@ -88,16 +83,9 @@ async def initialize(self, config_data: ConfigData | None = None):
WS_INTERFACES_KEY: {},
}

await self.set_status(ConnectivityStatus.Connecting)

if self.hass is None:
self._session = ClientSession(cookies=self._api_cookies)
else:
self._session = async_create_clientsession(
hass=self.hass, cookies=self._api_cookies
)
await self.initialize_session(cookies=self._api_cookies)

async with self._session.ws_connect(
async with self.session.ws_connect(
self._ws_url,
ssl=False,
autoclose=True,
Expand All @@ -115,7 +103,7 @@ async def initialize(self, config_data: ConfigData | None = None):
await self.set_status(ConnectivityStatus.NotConnected)

except Exception as ex:
if self._session is not None and self._session.closed:
if self.session is not None and self.session.closed:
_LOGGER.info(f"WS Session closed")

await self.terminate()
Expand All @@ -133,16 +121,20 @@ async def initialize(self, config_data: ConfigData | None = None):
await self.set_status(ConnectivityStatus.Failed)

async def terminate(self):
await super().terminate()

if self._remove_async_track_time is not None:
self._remove_async_track_time()
self._remove_async_track_time = None

if self.status != ConnectivityStatus.Disconnected:
await self.set_status(ConnectivityStatus.Disconnected)
if self._ws is not None:
await self._ws.close()

self._ws = None

async def async_send_heartbeat(self):
_LOGGER.debug(f"Keep alive message sent")
if self._session is None or self._session.closed:
if self.session is None or self.session.closed:
await self.set_status(ConnectivityStatus.NotConnected)

return
Expand Down Expand Up @@ -182,7 +174,7 @@ async def _listen(self):
is_closing_type = msg.type in WS_CLOSING_MESSAGE
is_error = msg.type == aiohttp.WSMsgType.ERROR
is_closing_data = False if is_closing_type or is_error else msg.data == "close"
session_is_closed = self._session is None or self._session.closed
session_is_closed = self.session is None or self.session.closed

if is_closing_type or is_error or is_closing_data or session_is_closed or not is_connected:
_LOGGER.warning(
Expand Down
14 changes: 9 additions & 5 deletions custom_components/edgeos/component/helpers/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,19 @@

API_DATA_SAVE = "SAVE"

API_URL_DATA_TEMPLATE = "{}?data={}"
API_URL_HEARTBEAT_TEMPLATE = "{}?_={}"

API_URL = "{}/api/edge/{}.json"
API_GET = "get"
API_SET = "set"
API_DELETE = "delete"
API_DATA = "data"
API_HEARTBEAT = "heartbeat"

API_URL_PARAMETER_BASE_URL = "base_url"
API_URL_PARAMETER_TIMESTAMP = "timestamp"
API_URL_PARAMETER_ACTION = "action"
API_URL_PARAMETER_SUBSET = "subset"

API_URL_HEARTBEAT = "{base_url}?_={timestamp}"
API_URL_DATA = "{base_url}/api/edge/{action}.json"
API_URL_DATA_SUBSET = f"{API_URL_DATA}?data={{subset}}"

TRUE_STR = "true"
FALSE_STR = "false"
Expand Down
Loading

0 comments on commit 01ebcec

Please sign in to comment.