From 3bdced09f6ce3c82b2997fa4aefd62c05d33b1dc Mon Sep 17 00:00:00 2001 From: Matthieu Bourgain Date: Sun, 11 Apr 2021 20:29:30 +0200 Subject: [PATCH] use async requests --- .gitignore | 3 +- CHANGELOG.md | 4 ++ README.md | 34 +++++---- pyecodevices/__init__.py | 145 +++++++++++++++++++++++++++------------ setup.py | 2 +- 5 files changed, 128 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index b4e6727..d427c54 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build/ pyecodevices.egg-info __pycache__ publish -.vscode/ \ No newline at end of file +.vscode/ +test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ea2d5..031db28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.0 + +- Make async requests + ## 1.1.0 - Use XML API of the Eco-Devices to get more information diff --git a/README.md b/README.md index 78912bc..ee8ac43 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Get information from GCE Eco-Devices - `port`: (default: 80) - `username`: if authentication enabled on Eco-Devices - `password`: if authentication enabled on Eco-Devices -- `timeout`: (default: 3) +- `request_timeout`: (default: 3) ## Properties @@ -30,17 +30,23 @@ Get information from GCE Eco-Devices ```python from pyecodevices import EcoDevices -ecodevices = EcoDevices('192.168.1.239','80',"username","password") - -print("# ping") -print(ecodevices.ping()) -print("# firmware version") -print(ecodevices.firmware) -print("# all values") -print(ecodevices.global_get()) -print("# inputs values") -print(ecodevices.get_t1()) -print(ecodevices.get_t2()) -print(ecodevices.get_c1()) -print(ecodevices.get_c2()) +import asyncio + + +async def main(): + async with EcoDevices('192.168.1.239', '80', "username", "password") as ecodevices: + ping = await ecodevices.ping() + print("ping:", ping) + version = await ecodevices.firmware + print("firmware version: ", version) + data = await ecodevices.global_get() + print("all values: ", data) + data = await ecodevices.get_t1() + print("teleinfo 1: ", data["current"]) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + ``` diff --git a/pyecodevices/__init__.py b/pyecodevices/__init__.py index e1a265b..be25c07 100644 --- a/pyecodevices/__init__.py +++ b/pyecodevices/__init__.py @@ -1,73 +1,109 @@ """Get information from GCE Eco-Devices.""" -import requests +import asyncio +import socket + +import aiohttp +import async_timeout import xmltodict class EcoDevices: """Class representing the Eco-Devices and its XML API.""" - def __init__(self, host, port=80, username=None, password=None, timeout=3): + def __init__( + self, + host: str, + port: int = 80, + username: str = None, + password: str = None, + request_timeout: int = 10, + session: aiohttp.client.ClientSession = None + ) -> None: """Init a EcoDevice API.""" self._host = host self._port = port self._username = username self._password = password - self._timeout = timeout + self._request_timeout = request_timeout self._api_url = f"http://{host}:{port}/status.xml" - def _request(self): - if self._username is not None and self._password is not None: - r = requests.get( - self._api_url, - params={}, - auth=(self._username, self._password), - timeout=self._timeout, - ) - else: - r = requests.get(self._api_url, params={}, timeout=self._timeout) - r.raise_for_status() - xml_content = xmltodict.parse(r.text) - response = xml_content.get("response", None) - if response: - return response - else: - raise Exception( - "Eco-Devices XML request error, url: %s`r%s", - r.request.url, - response, + self._session = session + self._close_session = False + + async def _request(self) -> dict: + """Make a request to get Eco-Devices data.""" + auth = None + if self._username and self._password: + auth = aiohttp.BasicAuth(self._username, self._password) + + if self._session is None: + self._session = aiohttp.ClientSession() + self._close_session = True + + try: + with async_timeout.timeout(self._request_timeout): + response = await self._session.request( + "GET", + self._api_url, + auth=auth, + data=None, + json=None, + params=None, + headers={}, + ssl=False, + ) + except asyncio.TimeoutError as exception: + raise CannotConnectError( + "Timeout occurred while connecting to Eco-Devices." + ) from exception + except (aiohttp.ClientError, socket.gaierror) as exception: + raise CannotConnectError( + "Error occurred while communicating with Eco-Devices." + ) from exception + if response.status == 401: + raise InvalidAuthError( + "Authentication failed with Eco-Devices." ) + if response.status: + contents = await response.text() + response.close() + xml_content = xmltodict.parse(contents) + data = xml_content.get("response", None) + if data: + return data + raise CannotConnectError("Eco-Devices XML request error:", data) + @property - def host(self): + def host(self) -> str: """Return the hostname.""" return self._host @property - def mac_address(self): + async def mac_address(self) -> str: """Return the mac address.""" - return self._request().get("config_mac") + data = await self._request() + return data["config_mac"] @property - def firmware(self): - """Return the firmware.""" - return self._request().get("version") + async def firmware(self) -> str: + """Return the mac address.""" + data = await self._request() + return data["version"] - def ping(self) -> bool: + async def ping(self) -> bool: """Return true if Eco-Devices answer to API request.""" - try: - self._request() + if await self._request(): return True - except Exception: - pass return False - def global_get(self): + async def global_get(self) -> dict: """Return all values from API.""" - return self._request() + return await self._request() - def get_t1(self): + async def get_t1(self) -> dict: """Get values from teleinformation 1 input.""" - data = self._request() + data = await self._request() return { "current": data.get("T1_PAPP"), "type_heures": data.get("T1_PTEC"), @@ -75,9 +111,9 @@ def get_t1(self): "intensite_max": data.get("T1_IMAX"), } - def get_t2(self): + async def get_t2(self) -> dict: """Get values from teleinformation 1 input.""" - data = self._request() + data = await self._request() return { "current": data.get("T2_PAPP"), "type_heures": data.get("T2_PTEC"), @@ -85,20 +121,41 @@ def get_t2(self): "intensite_max": data.get("T2_IMAX"), } - def get_c1(self): + async def get_c1(self) -> dict: """Get values from meter 1 input.""" - data = self._request() + data = await self._request() return { "daily": data.get("c0day"), "total": data.get("count0"), "fuel": data.get("c0_fuel"), } - def get_c2(self): + async def get_c2(self) -> dict: """Get values from meter 2 input.""" - data = self._request() + data = await self._request() return { "daily": data.get("c1day"), "total": data.get("count1"), "fuel": data.get("c1_fuel"), } + + async def close(self) -> None: + """Close open client session.""" + if self._session and self._close_session: + await self._session.close() + + async def __aenter__(self): + """Async enter.""" + return self + + async def __aexit__(self, *_exc_info) -> None: + """Async exit.""" + await self.close() + + +class CannotConnectError(Exception): + """Exception to indicate an error in connection.""" + + +class InvalidAuthError(Exception): + """Exception to indicate an error in authentication.""" diff --git a/setup.py b/setup.py index c0ad693..be7653b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pyecodevices", - version="1.1.0", + version="1.2.0", author="Aohzan", author_email="aohzan@gmail.com", description="Get information from GCE Eco-Devices.",