From 374c799806cdbebc0734eddc29f435f8753e549b Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 8 Nov 2023 18:14:13 +0100 Subject: [PATCH 01/38] few exceptions handling config_flow + enhancement --- custom_components/mawaqit/config_flow.py | 183 ++++++++++-------- custom_components/mawaqit/mawaqit.py | 23 +-- custom_components/mawaqit/strings.json | 14 +- .../mawaqit/translations/en.json | 67 ++++--- .../mawaqit/translations/fr.json | 83 ++++---- 5 files changed, 199 insertions(+), 171 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 9128e57..d17b1ae 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -9,6 +9,7 @@ from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN +import logging import asyncio import aiohttp import os @@ -19,6 +20,8 @@ from homeassistant.data_entry_flow import FlowResult +_LOGGER = logging.getLogger(__name__) + class MawaqitPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for mawaqit.""" @@ -32,70 +35,85 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" + self._errors = {} + lat = self.hass.config.latitude longi = self.hass.config.longitude - if user_input is not None: - valid = await self._test_credentials( - user_input[CONF_USERNAME], user_input[CONF_PASSWORD] - ) - #create data folder if does not exist - username = user_input[CONF_USERNAME] - password = user_input[CONF_PASSWORD] + # create data folder if does not exist + current_dir = os.path.dirname(os.path.realpath(__file__)) + if not os.path.exists('{}/data'.format(current_dir)): + os.makedirs('{}/data'.format(current_dir)) + + if user_input is None: + return await self._show_config_form(user_input=None) + + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + # check if user credentials are correct + try: + valid = await self._test_credentials(username, password) + except aiohttp.client_exceptions.ClientConnectorError: + self._errors["base"] = "cannot_connect_to_server" + return await self._show_config_form(user_input) + + if valid: + de = await self.apimawaqit( + username, password + ) + + da = await self.neighborhood( + lat, longi, username, password, de + + ) + if len(da)==0: + return self.async_abort(reason="no_mosque") + + + # create data folder if does not exist current_dir = os.path.dirname(os.path.realpath(__file__)) if not os.path.exists('{}/data'.format(current_dir)): os.makedirs('{}/data'.format(current_dir)) - - # check if user credentials are correct - if valid: - de = await self.apimawaqit( - user_input[CONF_USERNAME], user_input[CONF_PASSWORD] - ) - da = await self.neighborhood( - lat, longi, user_input[CONF_USERNAME], user_input[CONF_PASSWORD], de + text_file = open('{}/data/api.txt'.format(current_dir), "w") + text_file.write(de) + text_file.close() + + text_file = open('{}/data/all_mosquee_NN.txt'.format(current_dir), "w") + json.dump(da, text_file) + text_file.close() + + # creation of the list of mosques to be displayed in the options + name_servers = [] + uuid_servers = [] + CALC_METHODS = [] - ) - if len(da)==0: - return self.async_abort(reason="no_mosque") - - - text_file = open('{}/data/api.txt'.format(current_dir), "w") - text_file.write(de) - text_file.close() - - text_file = open('{}/data/all_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da, text_file) - text_file.close() - - # creation of the list of mosques to be displayed in the options - name_servers = [] - uuid_servers = [] - CALC_METHODS = [] - - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) - - text_file = open('{}/mosq_list.py'.format(current_dir), "w") - n = text_file.write("CALC_METHODS = " + str(CALC_METHODS)) - text_file.close() + with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + distros_dict = json.load(f) + for distro in distros_dict: + name_servers.extend([distro["label"]]) + uuid_servers.extend([distro["uuid"]]) + CALC_METHODS.extend([distro["label"]]) + + text_file = open('{}/mosq_list.py'.format(current_dir), "w") + n = text_file.write("CALC_METHODS = " + str(CALC_METHODS)) + text_file.close() - return await self.async_step_mosques() + return await self.async_step_mosques() - else : # (if not valid) - self._errors["base"] = "wrong_credential" - return await self._show_config_form(user_input) + else : # (if not valid) + self._errors["base"] = "wrong_credential" - return await self._show_config_form(user_input) + return await self._show_config_form(user_input) async def async_step_mosques(self, user_input=None) -> FlowResult: """Handle mosques step.""" + + self._errors = {} + lat = self.hass.config.latitude longi = self.hass.config.longitude current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -118,39 +136,38 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: CALC_METHODS.extend([distro["label"]]) mosquee = user_input[CONF_UUID] - indice = name_servers.index(mosquee) - mosque_id = uuid_servers[indice] + index = name_servers.index(mosquee) + mosque_id = uuid_servers[index] text_file = open('{}/data/my_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da[indice], text_file) - text_file.close() + json.dump(da[index], text_file) + text_file.close() # the mosque chosen by user - db = await self.fetch_prayer_times( - lat, longi, mosque_id, '', '', api - ) + db = await self.fetch_prayer_times(lat, longi, mosque_id, '', '', api) - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name="" ), "w") + text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") json.dump(db, text_file) text_file.close() + + title = 'MAWAQIT' + ' - ' + da[index]["name"] + data = {CONF_API_KEY: api, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} - return self.async_create_entry( - title='Mawaqit', data={CONF_API_KEY: api, CONF_UUID: user_input[CONF_UUID],CONF_LATITUDE: lat,CONF_LONGITUDE: longi} - ) + return self.async_create_entry(title=title, data=data) - return await self._show_config_form2(user_input) + return await self._show_config_form2() async def _show_config_form(self, user_input): # pylint: disable=unused-argument if user_input is None: user_input = {} - user_input[CONF_USERNAME] = "login@mawaqit.net" - user_input[CONF_PASSWORD] = "password" + user_input[CONF_USERNAME] = "" + user_input[CONF_PASSWORD] = "" schema = vol.Schema( - {vol.Required(CONF_USERNAME, description={"suggested_value":user_input[CONF_USERNAME]}): str, - vol.Required(CONF_PASSWORD, description={"suggested_value":user_input[CONF_PASSWORD]}): str} + {vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str, + vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str} ) """Show the configuration form to edit location data.""" @@ -161,7 +178,7 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen ) - async def _show_config_form2(self, user_input): # pylint: disable=unused-argument + async def _show_config_form2(self): # pylint: disable=unused-argument """Show the configuration form to edit location data.""" lat = self.hass.config.latitude longi = self.hass.config.longitude @@ -183,7 +200,6 @@ async def _show_config_form2(self, user_input): # pylint: disable=unused-argume uuid_servers.extend([distro["uuid"]]) CALC_METHODS.extend([distro["label"]]) - return self.async_show_form( step_id="mosques", data_schema=vol.Schema( @@ -193,11 +209,7 @@ async def _show_config_form2(self, user_input): # pylint: disable=unused-argume ), errors=self._errors, ) - - - - def async_get_options_flow(config_entry): """Get the options flow for this handler.""" @@ -208,16 +220,12 @@ def async_get_options_flow(config_entry): async def _test_credentials(self, username, password): """Return true if credentials is valid.""" try: - client = MawaqitClient('','','',username,password,'') - await client.login() + client = MawaqitClient('', '', '', username, password, '') + await client.login() await client.close() return True except BadCredentialsException: # pylint: disable=broad-except - - pass - - - return False + return False async def neighborhood(self, lat, long, username, password, api): @@ -288,11 +296,11 @@ async def async_step_init(self, user_input=None): CALC_METHODS.extend([distro["label"]]) mosquee = user_input['calculation_method'] - indice = name_servers.index(mosquee) - mosque_id = uuid_servers[indice] + index = name_servers.index(mosquee) + mosque_id = uuid_servers[index] text_file = open('{}/data/my_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da[indice], text_file) + json.dump(da[index], text_file) text_file.close() # the mosque chosen by user @@ -300,10 +308,21 @@ async def async_step_init(self, user_input=None): lat, longi, mosque_id, '', '', api ) - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name="" ), "w") + text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") json.dump(db, text_file) text_file.close() - return self.async_create_entry(title="", data=user_input) + + title = 'MAWAQIT' + ' - ' + da[index]["name"] + + data = {CONF_API_KEY: api, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} + + self.hass.config_entries.async_update_entry( + self.config_entry, + title=title, + data=data + ) + + return self.async_create_entry(title=None, data=None) lat = self.hass.config.latitude longi = self.hass.config.longitude diff --git a/custom_components/mawaqit/mawaqit.py b/custom_components/mawaqit/mawaqit.py index 621de93..9c6c6ab 100755 --- a/custom_components/mawaqit/mawaqit.py +++ b/custom_components/mawaqit/mawaqit.py @@ -1,4 +1,3 @@ - """ Python wrapper for the mawaqit API """ from __future__ import annotations import json @@ -27,7 +26,7 @@ class NotAuthenticatedException(Exception): class BadCredentialsException(Exception): pass -class NoMosqueArround(Exception): +class NoMosqueAround(Exception): pass @@ -59,8 +58,6 @@ def __init__( self.token = token self.session = session if session else ClientSession() - - async def __aenter__(self) -> MawaqitClient: return self @@ -76,8 +73,6 @@ async def close(self) -> None: """Close the session.""" await self.session.close() - - @backoff.on_exception( backoff.expo, NotAuthenticatedException, @@ -85,10 +80,6 @@ async def close(self) -> None: on_backoff=relogin, ) - - - - async def apimawaqit(self) -> str: if self.token == '': auth=aiohttp.BasicAuth(self.username, self.password) @@ -101,9 +92,6 @@ async def apimawaqit(self) -> str: else: return self.token - - - async def all_mosques_neighborhood(self): if self.token == '': api = await self.apimawaqit() @@ -124,12 +112,12 @@ async def all_mosques_neighborhood(self): if response.status != 200: raise NotAuthenticatedException #if len(response.text()) == 0: - # raise NoMosqueArround + # raise NoMosqueAround data = await response.json() #if len(data) == 0: - # raise NoMosqueArround + # raise NoMosqueAround return data #json.loads(data) async def fetch_prayer_times(self) -> dict: @@ -142,7 +130,6 @@ async def fetch_prayer_times(self) -> dict: if self.token == '': api_token = await self.apimawaqit() else: api_token = self.token - headers = {'Content-Type': 'application/json', 'Api-Access-Token': format(api_token)} @@ -153,10 +140,8 @@ async def fetch_prayer_times(self) -> dict: if response.status != 200: raise NotAuthenticatedException data = await response.json() - return data - - + return data async def login(self) -> None: """Log into the mawaqit website.""" diff --git a/custom_components/mawaqit/strings.json b/custom_components/mawaqit/strings.json index 7163d79..4896b73 100755 --- a/custom_components/mawaqit/strings.json +++ b/custom_components/mawaqit/strings.json @@ -1,10 +1,10 @@ { - "title": "Mawaqit Prayer Times", + "title": "MAWAQIT", "config": { "step": { "user": { - "title": "Mawaqit", - "description": "Register on https://mawaqit.net/ and connect below. The nearest mosque to your house will be chosen but you can change it later on in the add-on parameters.", + "title": "MAWAQIT", + "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", "data": { "username": "E-mail", "password": "Password", @@ -19,7 +19,6 @@ } }, - "select_server": { "title": "Select your favourite mosque", "description": "Multiple mosques available, select one :", @@ -32,12 +31,13 @@ }, "error": { - "wrong_credential": "Wrong login or password, please retry." + "wrong_credential": "Wrong login or password, please retry.", + "cannot_connect_to_server": "Cannot connect to the server, please retry later." }, "abort": { "one_instance_allowed": "Only a single instance is necessary.", - "no_mosque": "Sorry, there's no Mawaqit mosque in your neighborhood" + "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." } }, @@ -45,7 +45,7 @@ "step": { "init": { "data": { - "calculation_method": "Change your favourite mosque" + "calculation_method": "Change your mosque :" } } } diff --git a/custom_components/mawaqit/translations/en.json b/custom_components/mawaqit/translations/en.json index 08ae3d8..2755052 100755 --- a/custom_components/mawaqit/translations/en.json +++ b/custom_components/mawaqit/translations/en.json @@ -1,42 +1,53 @@ { + "title": "MAWAQIT", "config": { - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "no_mosque": "Sorry, there's no MAWAQIT mosque in the neighborhood." + "step": { + "user": { + "title": "MAWAQIT", + "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", + "data": { + "username": "E-mail", + "password": "Password", + "server": "Server" + } }, - "step": { - "user": { - "description": "Register on https://mawaqit.net/ and connect below. The nearest mosque to your house will be chosen but you can change it later on in the add-on parameters.", - "data": { - "username": "E-mail", - "password": "Password", - "server": "Server" - }, - "title": "Set-up MAWAQIT Prayer Times" - }, + "mosques": { - "title": "Select your favourite mosque", + "title": "Your favourite mosque", "data": { "mosques": "Multiple mosques are available around you, select one :" } - } - - }, - - "error": { - "wrong_credential": "Wrong login or password, please retry." + + "select_server": { + "title": "Select your favourite mosque", + "description": "Multiple mosques available, select one :", + "data": { + "username": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]", + "server": "Server" + } } + }, + + "error": { + "wrong_credential": "Wrong login or password, please retry.", + "cannot_connect_to_server": "Cannot connect to the server, please retry later." + }, + + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." + } }, "options": { - "step": { - "init": { - "data": { - "calculation_method": "Change your favourite mosque" - } - } + "step": { + "init": { + "data": { + "calculation_method": "Change your mosque :" + } } - }, - "title": "MAWAQIT" + } + } } diff --git a/custom_components/mawaqit/translations/fr.json b/custom_components/mawaqit/translations/fr.json index a64f4fb..d46a3e6 100755 --- a/custom_components/mawaqit/translations/fr.json +++ b/custom_components/mawaqit/translations/fr.json @@ -1,42 +1,55 @@ { + "title": "Heures de prières MAWAQIT", "config": { - "abort": { - "one_instance_allowed": "Une seule instance de Mawaqit est possible.", - "no_mosque": "D\u00e9sol\u00e9, pas de mosqu\u00e9e \u00e9quip\u00e9e par Mawaqit preès de chez vous.", - "wrong_credential": "Login ou mot de passe incorrect." - }, - "step": { - "user": { - "description": "Inscrivez-vous sur https://mawaqit.net/ puis connectez-vous ci-dessous. La mosqu\u00e9e la plus proche sera choisie mais vous pouvez la changer ult\u00e9rieurement dans les param\u00e8tres du plugin Mawaqit :", - "data": { - "username": "E-mail", - "password": "Mot de passe", - "server": "Server" - }, - "title": "Installation de Mawaqit" - }, - "mosques": { - "title": "Choisir votre mosqu\u00e9e", - "data": { - "mosques": "Diff\u00e9rentes mosqu\u00e9es sont disponibles autour de vous, s\u00e9lectionnez une:" - } - } - + "step": { + "user": { + "title": "MAWAQIT", + "description": "Inscrivez-vous sur https://mawaqit.net/ puis connectez-vous ci-dessous. La mosqu\u00e9e la plus proche sera choisie mais vous pouvez la changer ult\u00e9rieurement dans les param\u00e8tres du plugin Mawaqit :", + "data": { + "username": "E-mail", + "password": "Mot de passe", + "server": "Serveur" + } }, - - "error": { - "wrong_credential": "Mauvais login ou mot de passe, veuillez réessayer." + + "mosques": { + "title": "Votre mosqu\u00e9e favorite", + "data": { + "mosques": "Diff\u00e9rentes mosqu\u00e9es sont disponibles autour de vous, s\u00e9lectionnez-en une :" + } + }, + + + "select_server": { + "title": "Choisissiez votre mosqu\u00e9e favorite", + "description": "Plusieurs mosqu\u00e9es sont disponibles autour de vous, s\u00e9lectionnez-en une :", + "data": { + "username": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]", + "server": "Serveur" } - + } + }, + + "error": { + "wrong_credential": "Mauvais login ou mot de passe, veuillez réessayer.", + "cannot_connect_to_server": "Impossible de se connecter au serveur, veuillez réessayer." + }, + + "abort": { + "one_instance_allowed": "Une seule instance de MAWAQIT est possible.", + "no_mosque": "D\u00e9sol\u00e9, aucune mosqu\u00e9e MAWAQIT n'est disponible près de chez vous." + } + }, "options": { - "step": { - "init": { - "data": { - "calculation_method": "Changer votre mosqu\u00e9e " - } - } + "step": { + "init": { + "data": { + "calculation_method": "Changer votre mosqu\u00e9e :" + } } - }, - "title": "Mawaqit" -} + } + } + } + \ No newline at end of file From e9cdb326dc935c4ac12cdf0f629ad84331d03a2f Mon Sep 17 00:00:00 2001 From: mohaThr <98562658+mohaThr@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:16:12 +0100 Subject: [PATCH 02/38] Update .gitignore Get rid of useless python files (test.py/mosq_list.py) --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 009ccb7..3bba512 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ - -custom_components/.DS_Store +custom_components/mawaqit/data +custom_components/mawaqit/test.py +custom_components/mawaqit/mosq_list.py custom_components/.DS_Store .DS_Store -custom_components/mawaqit/data From 28c0e472704af41434f2541cc449f67e64d1b642 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 15 Nov 2023 01:06:04 +0100 Subject: [PATCH 03/38] first commit, need lot of testing --- custom_components/mawaqit/config_flow.py | 184 +++++++++++---------- custom_components/mawaqit/const.py | 2 +- custom_components/mawaqit/mawaqit.py | 195 +++++++++++++---------- custom_components/mawaqit/sensor.py | 34 +--- custom_components/mawaqit/test.py | 25 --- 5 files changed, 213 insertions(+), 227 deletions(-) delete mode 100755 custom_components/mawaqit/test.py diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index d17b1ae..7a71cea 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -3,8 +3,7 @@ import voluptuous as vol from .const import CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME, CONF_SERVER, USERNAME, PASSWORD, CONF_UUID -from .mosq_list import CALC_METHODS -from .mawaqit import MawaqitClient, BadCredentialsException +from .mawaqit import MawaqitClient, BadCredentialsException, NoMosqueAround from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN @@ -26,7 +25,7 @@ class MawaqitPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for mawaqit.""" - VERSION = 1 + VERSION = 2.1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): @@ -52,25 +51,16 @@ async def async_step_user(self, user_input=None): username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] - # check if user credentials are correct + # check if the user credentials are correct (valid = True) : try: valid = await self._test_credentials(username, password) + # if we have an error connecting to the server : except aiohttp.client_exceptions.ClientConnectorError: self._errors["base"] = "cannot_connect_to_server" return await self._show_config_form(user_input) if valid: - de = await self.apimawaqit( - username, password - ) - - da = await self.neighborhood( - lat, longi, username, password, de - - ) - if len(da)==0: - return self.async_abort(reason="no_mosque") - + mawaqit_token = await self.get_mawaqit_api_token(username, password) # create data folder if does not exist current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -78,11 +68,16 @@ async def async_step_user(self, user_input=None): os.makedirs('{}/data'.format(current_dir)) text_file = open('{}/data/api.txt'.format(current_dir), "w") - text_file.write(de) + text_file.write(mawaqit_token) text_file.close() + + try: + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, username, password, mawaqit_token) + except NoMosqueAround: + return self.async_abort(reason="no_mosque") - text_file = open('{}/data/all_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da, text_file) + text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques, text_file) text_file.close() # creation of the list of mosques to be displayed in the options @@ -90,7 +85,7 @@ async def async_step_user(self, user_input=None): uuid_servers = [] CALC_METHODS = [] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) @@ -99,7 +94,7 @@ async def async_step_user(self, user_input=None): text_file = open('{}/mosq_list.py'.format(current_dir), "w") n = text_file.write("CALC_METHODS = " + str(CALC_METHODS)) - text_file.close() + text_file.close() return await self.async_step_mosques() @@ -116,41 +111,46 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: lat = self.hass.config.latitude longi = self.hass.config.longitude + current_dir = os.path.dirname(os.path.realpath(__file__)) f = open('{}/data/api.txt'.format(current_dir)) - api = f.read() + mawaqit_token = f.read() f.close() if user_input is not None: - da = await self.neighborhood(lat, longi, '', '', api) + nearest_mosques = await self.all_mosques_neighborhood(lat=lat, + longi=longi, + username=user_input[CONF_USERNAME], + password=user_input[CONF_PASSWORD], + token=mawaqit_token) + + text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques[index], text_file) + text_file.close() name_servers=[] uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) uuid_servers.extend([distro["uuid"]]) CALC_METHODS.extend([distro["label"]]) - mosquee = user_input[CONF_UUID] - index = name_servers.index(mosquee) - mosque_id = uuid_servers[index] - - text_file = open('{}/data/my_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da[index], text_file) - text_file.close() + mosque = user_input[CONF_UUID] + index = name_servers.index(mosque) + mosque_id = uuid_servers[index] # the mosque chosen by user - db = await self.fetch_prayer_times(lat, longi, mosque_id, '', '', api) + dict_calendar = await self.fetch_prayer_times(lat=lat, longi=longi, api=api, mosque_id=mosque_id) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") - json.dump(db, text_file) + json.dump(dict_calendar, text_file) text_file.close() - title = 'MAWAQIT' + ' - ' + da[index]["name"] + title = 'MAWAQIT' + ' - ' + nearest_mosques[index]["name"] data = {CONF_API_KEY: api, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} return self.async_create_entry(title=title, data=data) @@ -182,18 +182,23 @@ async def _show_config_form2(self): # pylint: disable=unused-argument """Show the configuration form to edit location data.""" lat = self.hass.config.latitude longi = self.hass.config.longitude + current_dir = os.path.dirname(os.path.realpath(__file__)) f = open('{}/data/api.txt'.format(current_dir)) - api = f.read() + mawaqit_token = f.read() f.close() - da = await self.neighborhood(lat, longi, '', '', api) + nearest_mosques = await self.all_mosques_neighborhood(lat=lat, longi=longi, token=mawaqit_token) + + text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques, text_file) + text_file.close() name_servers=[] uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) @@ -220,49 +225,48 @@ def async_get_options_flow(config_entry): async def _test_credentials(self, username, password): """Return true if credentials is valid.""" try: - client = MawaqitClient('', '', '', username, password, '') + client = MawaqitClient(username=username, password=password) await client.login() await client.close() return True - except BadCredentialsException: # pylint: disable=broad-except + except BadCredentialsException: return False - async def neighborhood(self, lat, long, username, password, api): + async def all_mosques_neighborhood(self, lat, long, username, password, token): """Return mosques in the neighborhood if any.""" try: - client = MawaqitClient(lat,long,'',username,password, api, '') - da = await client.all_mosques_neighborhood() + client = MawaqitClient(lat=lat, long=long, username=username, password=password, token=token) + nearest_mosques = await client.all_mosques_neighborhood() await client.close() - return da - except BadCredentialsException: # pylint: disable=broad-except + except BadCredentialsException: pass - return da + return nearest_mosques - async def apimawaqit(self, username, password): - """Return mosques in the neighborhood if any.""" + + async def get_mawaqit_api_token(self, username, password): + """Return the MAWAQIT API Token.""" try: - client = MawaqitClient('','','',username,password,'') - da = await client.apimawaqit() + client = MawaqitClient(username=username, password=password) + token = await client.get_api_token() await client.close() - return da - except BadCredentialsException: # pylint: disable=broad-except + except BadCredentialsException: pass - return da + + return token - async def fetch_prayer_times(self, lat, long, mosquee, username, password, api): - """fetch prayer time""" + async def fetch_prayer_times(self, lat, long, mosque, username, password, token): + """Get prayer times from the MAWAQIT API. Returns a dict.""" try: - client = MawaqitClient(lat,long,mosquee,username,password,api,'') - db = await client.fetch_prayer_times() + client = MawaqitClient(lat=lat, long=long, mosque=mosque, username=username, password=password, token=token) + dict_calendar = await client.fetch_prayer_times() await client.close() - return db - except BadCredentialsException: # pylint: disable=broad-except + except BadCredentialsException: pass - return db + return dict_calendar class MawaqitPrayerOptionsFlowHandler(config_entries.OptionsFlow): @@ -280,41 +284,44 @@ async def async_step_init(self, user_input=None): current_dir = os.path.dirname(os.path.realpath(__file__)) f = open('{}/data/api.txt'.format(current_dir)) - api = f.read() + mawaqit_token = f.read() f.close() - da = await self.neighborhood(lat, longi, '', '', api) + nearest_mosques = await self.neighborhood(lat=lat, long=longi, token=mawaqit_token) name_servers=[] uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) uuid_servers.extend([distro["uuid"]]) CALC_METHODS.extend([distro["label"]]) - mosquee = user_input['calculation_method'] - index = name_servers.index(mosquee) + mosque = user_input['calculation_method'] + index = name_servers.index(mosque) mosque_id = uuid_servers[index] - text_file = open('{}/data/my_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da[index], text_file) - text_file.close() + text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques[index], text_file) + text_file.close() # the mosque chosen by user - db = await self.fetch_prayer_times( - lat, longi, mosque_id, '', '', api - ) + dict_calendar = await self.fetch_prayer_times(lat=lat, + long=longi, + mosque=mosque_id, + username=user_input[CONF_USERNAME], + password=user_input[CONF_PASSWORD], + token=mawaqit_token) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") - json.dump(db, text_file) + json.dump(dict_calendar, text_file) text_file.close() title = 'MAWAQIT' + ' - ' + da[index]["name"] - data = {CONF_API_KEY: api, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} + data = {CONF_API_KEY: mawaqit_token, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} self.hass.config_entries.async_update_entry( self.config_entry, @@ -326,21 +333,23 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude + current_dir = os.path.dirname(os.path.realpath(__file__)) f = open('{}/data/api.txt'.format(current_dir)) - api = f.read() + mawaqit_token = f.read() f.close() - da = await self.neighborhood(lat, longi, '', '', api) + + nearest_mosques = await self.neighborhood(lat=lat, long=longi, token=mawaqit_token) - text_file = open('{}/data/all_mosquee_NN.txt'.format(current_dir), "w") - json.dump(da, text_file) + text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques, text_file) text_file.close() name_servers=[] uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosque_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) @@ -360,24 +369,23 @@ async def async_step_init(self, user_input=None): async def neighborhood(self, lat, long, username, password, api): - """Return mosques in the neighborhood if any.""" + """Return mosques in the neighborhood if any. Returns a list of dicts.""" try: client = MawaqitClient(lat,long,'',username,password, api, '') - da = await client.all_mosques_neighborhood() + nearest_mosques = await client.all_mosques_neighborhood() await client.close() - return da - except BadCredentialsException: # pylint: disable=broad-except + except BadCredentialsException: pass - return da + return nearest_mosques - async def fetch_prayer_times(self, lat, long, mosquee, username, password, api): - """fetch prayer time""" + async def fetch_prayer_times(self, lat, long, mosque, username, password, api): + """Get prayer times from the MAWAQIT API. Returns a dict.""" try: - client = MawaqitClient(lat,long,mosquee,username,password,api,'') - db = await client.fetch_prayer_times() + client = MawaqitClient(lat, long, mosque, username, password, api, '') + dict_calendar = await client.fetch_prayer_times() await client.close() - return db - except BadCredentialsException: # pylint: disable=broad-except + return dict_calendar + except BadCredentialsException: pass - return db + return dict_calendar diff --git a/custom_components/mawaqit/const.py b/custom_components/mawaqit/const.py index 00c97a3..a4015df 100755 --- a/custom_components/mawaqit/const.py +++ b/custom_components/mawaqit/const.py @@ -44,4 +44,4 @@ PASSWORD = "password" API = "api" -CONF_UUID ="uuid" +CONF_UUID = "uuid" diff --git a/custom_components/mawaqit/mawaqit.py b/custom_components/mawaqit/mawaqit.py index 9c6c6ab..ad565f1 100755 --- a/custom_components/mawaqit/mawaqit.py +++ b/custom_components/mawaqit/mawaqit.py @@ -1,4 +1,5 @@ -""" Python wrapper for the mawaqit API """ +"""Python Wrapper to access the MAWAQIT API.""" + from __future__ import annotations import json from datetime import date, datetime, timedelta @@ -6,17 +7,22 @@ from typing import Any, Dict, List, Union import backoff -from aiohttp import ClientSession + import aiohttp +from aiohttp import ClientSession JSON = Union[Dict[str, Any], List[Dict[str, Any]]] +API_URL_BASE = "https://mawaqit.net/api/2.0" +LOGIN_URL = f"{API_URL_BASE}/me" +SEARCH_MOSQUES_URL = f"{API_URL_BASE}/mosque/search" -LOGIN_URL = f"https://mawaqit.net/api/2.0/me" +def prayer_times_url(mosque_id: int) -> str: + return f"{API_URL_BASE}/mosques/{mosque_id}/prayer-times" -async def relogin(invocation: dict[str, Any]) -> None: - await invocation["args"][0].login() + +MAX_LOGIN_RETRIES = 20 class NotAuthenticatedException(Exception): @@ -26,46 +32,49 @@ class NotAuthenticatedException(Exception): class BadCredentialsException(Exception): pass + class NoMosqueAround(Exception): pass -class MawaqitClient: - """Interface class for the mawaqit unofficial API""" +class MissingCredentials(Exception): + pass + + +async def relogin(invocation: dict[str, Any]) -> None: + await invocation["args"][0].login() + +class MawaqitClient: + """Interface class for the MAWAQIT official API.""" def __init__( - self, - latitude: float, - longitude: float, - mosquee: str, - username: str, - password: str, - token: str, - session: ClientSession = None, + self, + latitude: float = None, + longitude: float = None, + mosque: str = None, + username: str = None, + password: str = None, + token: str = None, + session: ClientSession = None, ) -> None: - """ - Constructor - :param username: the username for eau-services.com - :param password: the password for eau-services.com - :param session: optional ClientSession - """ + """Initialize the client.""" self.username = username self.password = password self.latitude = latitude self.longitude = longitude - self.mosquee = mosquee - self.token = token + self.mosque = mosque + self.token = token self.session = session if session else ClientSession() async def __aenter__(self) -> MawaqitClient: return self async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: await self.close() @@ -76,78 +85,90 @@ async def close(self) -> None: @backoff.on_exception( backoff.expo, NotAuthenticatedException, - max_tries=20, - on_backoff=relogin, + max_tries=MAX_LOGIN_RETRIES, + on_backoff=relogin ) + async def get_api_token(self) -> str: + """Get the MAWAQIT API token.""" - async def apimawaqit(self) -> str: - if self.token == '': - auth=aiohttp.BasicAuth(self.username, self.password) - async with self.session.get(LOGIN_URL, auth=auth) as response: - if response.status != 200: - raise NotAuthenticatedException - data = await response.text() + if self.token is None: + await self.login() - return json.loads(data)["apiAccessToken"] - else: - return self.token + return self.token async def all_mosques_neighborhood(self): - if self.token == '': - api = await self.apimawaqit() - else: api = self.token - #api = await self.apimawaqit() - if len(str(api))>1: - payload = { - "lat": self.latitude, - "lon": self.longitude - } - headers = { - 'Authorization': api, - 'Content-Type': 'application/json', - } - api_url0 = 'https://mawaqit.net/api/2.0/mosque/search' - - async with self.session.get(api_url0, params=payload, data=None, headers=headers) as response: - if response.status != 200: - raise NotAuthenticatedException - #if len(response.text()) == 0: - # raise NoMosqueAround - - data = await response.json() - - #if len(data) == 0: - # raise NoMosqueAround - return data #json.loads(data) + """Get the five nearest mosques from the Client coordinates. + Returns a list of dicts with info on the mosques.""" + if self.token is None: + token = await self.get_api_token() + else: + token = self.token + + if (self.latitude is None) or (self.longitude is None): + raise MissingCredentials("Please provide a latitude and a longitude in your MawaqitClient object.") + + payload = { + "lat": self.latitude, + "lon": self.longitude + } + headers = { + 'Authorization': token, + 'Content-Type': 'application/json', + } + + endpoint_url = SEARCH_MOSQUES_URL + + async with self.session.get(endpoint_url, params=payload, data=None, headers=headers) as response: + if response.status != 200: + raise NotAuthenticatedException("Authentication failed. Please check your MAWAQIT credentials.") + data = await response.json() + + if len(data) == 0: + raise NoMosqueAround("No mosque found around your location. Please check your coordinates.") + + return data async def fetch_prayer_times(self) -> dict: - if self.mosquee == '': # and len(self.all_mosques_neighborhood())>0: - mosque_id = await (self.all_mosques_neighborhood()) + """Fetch the prayer times calendar for self.mosque, + Returns a dict with info on the mosque and the year-calendar prayer times.""" + + if self.mosque is None: + mosque_id = await self.all_mosques_neighborhood() mosque_id = mosque_id[0]["uuid"] - else: - mosque_id= self.mosquee + else: + mosque_id = self.mosque - if self.token == '': - api_token = await self.apimawaqit() - else: api_token = self.token + if self.token is None: + api_token = await self.get_api_token() + else: + api_token = self.token - headers = {'Content-Type': 'application/json', - 'Api-Access-Token': format(api_token)} - api_url_base = 'https://mawaqit.net/api/2.0/' - api_url = api_url_base + 'mosque/' + mosque_id + '/prayer-times' - - async with self.session.get(api_url, data=None, headers=headers) as response: - if response.status != 200: - raise NotAuthenticatedException - data = await response.json() + headers = {'Content-Type': 'application/json', + 'Api-Access-Token': format(api_token)} - return data + endpoint_url = prayer_times_url(mosque_id) + + async with self.session.get(endpoint_url, data=None, headers=headers) as response: + if response.status != 200: + raise NotAuthenticatedException("Authentication failed. Please check your MAWAQIT credentials.") + data = await response.json() + return data async def login(self) -> None: - """Log into the mawaqit website.""" + """Log into the MAWAQIT website.""" + + if (self.username is None) or (self.password is None): + raise MissingCredentials("Please provide a MAWAQIT login and password.") + auth = aiohttp.BasicAuth(self.username, self.password) - async with await self.session.post( - LOGIN_URL, auth = auth - ) as response: + + endpoint_url = LOGIN_URL + + async with await self.session.post(endpoint_url, auth=auth) as response: if response.status != 200: - raise BadCredentialsException + raise BadCredentialsException("Authentication failed. Please check your MAWAQIT credentials.") + data = await response.text() + + self.token = json.loads(data)["apiAccessToken"] + + diff --git a/custom_components/mawaqit/sensor.py b/custom_components/mawaqit/sensor.py index 8094d92..f531b0f 100755 --- a/custom_components/mawaqit/sensor.py +++ b/custom_components/mawaqit/sensor.py @@ -11,27 +11,23 @@ import os import logging + _LOGGER = logging.getLogger(__name__) -def is_date_parsing(date_str): - try: - return bool(date_parser.parse(date_str)) - except ValueError: - return False async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Mawaqit prayer times sensor platform.""" client = hass.data[DOMAIN] if not client: - _LOGGER.error("Error retrieving client object") + _LOGGER.error("Error retrieving client object.") entities = [] for sensor_type in SENSOR_TYPES: if sensor_type in ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha", "Jumua", "Jumua 2", #"Aid", "Aid 2", "Fajr Iqama", "Shurouq Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama", - "Next Salat Name", "Next Salat Time"]: #add "Next Salat Preparation" to the list in case a sensor is needed 15 min before salat time + "Next Salat Name", "Next Salat Time", "Next Salat Preparation"]: sensor = MawaqitPrayerTimeSensor(sensor_type, client) entities.append(sensor) async_add_entities(entities, True) @@ -41,10 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensor1, True) - - - - class MawaqitPrayerTimeSensor(SensorEntity): """Representation of an Mawaqit prayer time sensor.""" @@ -109,8 +101,8 @@ def device_class(self): "Fajr Iqama", "Shurouq Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama", "Next Salat Time", "Next Salat Preparation"]: return DEVICE_CLASS_TIMESTAMP - #else: - # return str + else: + return None async def async_added_to_hass(self): """Handle entity which will be added.""" @@ -119,9 +111,6 @@ async def async_added_to_hass(self): ) - - - class MyMosqueSensor(SensorEntity): """Representation of a mosque sensor.""" @@ -137,22 +126,17 @@ def __init__(self, name, hass): self._longitude = longitude - - #@Throttle(TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data from the Mawaqit API.""" current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/my_mosquee_NN.txt'.format(current_dir)) + f = open('{}/data/my_mosque_NN.txt'.format(current_dir)) data = json.load(f) f.close() for (k, v) in data.items(): if str(k) != "uuid" and str(k) != "id" and str(k) != "slug": self._attributes[k] = str(v) - print("Key: " + k) - print("Value: " + str(v)) - @property @@ -160,22 +144,20 @@ def name(self): """Return the name of the sensor.""" return self._name + @property def state(self): """Return the state of the sensor.""" return self._attributes['name'] + @property def icon(self): """Return the icon of the sensor.""" return 'mdi:mosque' - @property def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes - - - diff --git a/custom_components/mawaqit/test.py b/custom_components/mawaqit/test.py deleted file mode 100755 index b2faa38..0000000 --- a/custom_components/mawaqit/test.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -os.system('pip3 install mawaqit_times_calculator') -from mawaqit_times_calculator import MawaqitTimesCalculator - - - -mawaqit_login = "login here" -mawaqit_password = "password here" -mawaqit_latitude = "45.764043" -mawaqit_longitude = "4.835659" - - - - -calc = MawaqitTimesCalculator( - mawaqit_latitude, - mawaqit_longitude, - '', - mawaqit_login, - mawaqit_password, - '' - ) - -print(calc.apimawaqit()) -print(calc.fetch_prayer_times()) From b516f1a229699f759a753e8f0f2780c14dc0465f Mon Sep 17 00:00:00 2001 From: mohaThr <98562658+mohaThr@users.noreply.github.com> Date: Wed, 15 Nov 2023 01:08:00 +0100 Subject: [PATCH 04/38] Update .gitignore Not sure if I can gitignore the mosq_list --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3bba512..2811992 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ custom_components/mawaqit/data custom_components/mawaqit/test.py -custom_components/mawaqit/mosq_list.py custom_components/.DS_Store .DS_Store From d9d7a08b4e41a839351c21ce585b211eed1e0d27 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 15 Nov 2023 02:53:38 +0100 Subject: [PATCH 05/38] fix issues with async and stuff --- custom_components/mawaqit/config_flow.py | 100 +++++++++++------------ custom_components/mawaqit/mawaqit.py | 28 +++---- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 7a71cea..070800a 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -117,17 +117,7 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: mawaqit_token = f.read() f.close() - if user_input is not None: - nearest_mosques = await self.all_mosques_neighborhood(lat=lat, - longi=longi, - username=user_input[CONF_USERNAME], - password=user_input[CONF_PASSWORD], - token=mawaqit_token) - - text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques[index], text_file) - text_file.close() name_servers=[] uuid_servers=[] @@ -141,24 +131,30 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: mosque = user_input[CONF_UUID] index = name_servers.index(mosque) - mosque_id = uuid_servers[index] + mosque_id = uuid_servers[index] + + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + + text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") + json.dump(nearest_mosques[index], text_file) + text_file.close() # the mosque chosen by user - dict_calendar = await self.fetch_prayer_times(lat=lat, longi=longi, api=api, mosque_id=mosque_id) + dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") json.dump(dict_calendar, text_file) text_file.close() title = 'MAWAQIT' + ' - ' + nearest_mosques[index]["name"] - data = {CONF_API_KEY: api, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} + data = {CONF_API_KEY: mawaqit_token, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} return self.async_create_entry(title=title, data=data) return await self._show_config_form2() - async def _show_config_form(self, user_input): # pylint: disable=unused-argument + async def _show_config_form(self, user_input): if user_input is None: user_input = {} @@ -178,7 +174,7 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen ) - async def _show_config_form2(self): # pylint: disable=unused-argument + async def _show_config_form2(self): """Show the configuration form to edit location data.""" lat = self.hass.config.latitude longi = self.hass.config.longitude @@ -188,7 +184,7 @@ async def _show_config_form2(self): # pylint: disable=unused-argument mawaqit_token = f.read() f.close() - nearest_mosques = await self.all_mosques_neighborhood(lat=lat, longi=longi, token=mawaqit_token) + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") json.dump(nearest_mosques, text_file) @@ -223,7 +219,7 @@ def async_get_options_flow(config_entry): async def _test_credentials(self, username, password): - """Return true if credentials is valid.""" + """Return True if the MAWAQIT credentials is valid.""" try: client = MawaqitClient(username=username, password=password) await client.login() @@ -231,37 +227,38 @@ async def _test_credentials(self, username, password): return True except BadCredentialsException: return False + - - async def all_mosques_neighborhood(self, lat, long, username, password, token): - """Return mosques in the neighborhood if any.""" + async def get_mawaqit_api_token(self, username, password): + """Return the MAWAQIT API token.""" try: - client = MawaqitClient(lat=lat, long=long, username=username, password=password, token=token) - nearest_mosques = await client.all_mosques_neighborhood() + client = MawaqitClient(username=username, password=password) + token = await client.get_api_token() await client.close() except BadCredentialsException: pass - return nearest_mosques + return token - async def get_mawaqit_api_token(self, username, password): - """Return the MAWAQIT API Token.""" + async def all_mosques_neighborhood(self, lat, long, username = None, password = None, token = None): + """Return mosques in the neighborhood if any.""" try: - client = MawaqitClient(username=username, password=password) - token = await client.get_api_token() + client = MawaqitClient(latitude=lat, longitude=long, username=username, password=password, token=token) + await client.get_api_token() + nearest_mosques = await client.all_mosques_neighborhood() await client.close() except BadCredentialsException: pass - return token - + return nearest_mosques - async def fetch_prayer_times(self, lat, long, mosque, username, password, token): + async def fetch_prayer_times(self, lat = None, long = None, mosque = None, username = None, password = None, token = None): """Get prayer times from the MAWAQIT API. Returns a dict.""" try: - client = MawaqitClient(lat=lat, long=long, mosque=mosque, username=username, password=password, token=token) + client = MawaqitClient(latitude=lat, longitude=long, mosque=mosque, username=username, password=password, token=token) + await client.get_api_token() dict_calendar = await client.fetch_prayer_times() await client.close() except BadCredentialsException: @@ -281,12 +278,8 @@ async def async_step_init(self, user_input=None): if user_input is not None: lat = self.hass.config.latitude longi = self.hass.config.longitude - current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/api.txt'.format(current_dir)) - mawaqit_token = f.read() - f.close() - nearest_mosques = await self.neighborhood(lat=lat, long=longi, token=mawaqit_token) + current_dir = os.path.dirname(os.path.realpath(__file__)) name_servers=[] uuid_servers=[] @@ -302,24 +295,29 @@ async def async_step_init(self, user_input=None): mosque = user_input['calculation_method'] index = name_servers.index(mosque) mosque_id = uuid_servers[index] - + + f = open('{}/data/api.txt'.format(current_dir)) + mawaqit_token = f.read() + f.close() + + try: + nearest_mosques = await self.neighborhood(latitude=lat, longitude=longi, token=mawaqit_token) + except NoMosqueAround: + # TODO + raise NoMosqueAround("No mosque around.") + text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") json.dump(nearest_mosques[index], text_file) text_file.close() # the mosque chosen by user - dict_calendar = await self.fetch_prayer_times(lat=lat, - long=longi, - mosque=mosque_id, - username=user_input[CONF_USERNAME], - password=user_input[CONF_PASSWORD], - token=mawaqit_token) + dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") json.dump(dict_calendar, text_file) text_file.close() - title = 'MAWAQIT' + ' - ' + da[index]["name"] + title = 'MAWAQIT' + ' - ' + nearest_mosques[index]["name"] data = {CONF_API_KEY: mawaqit_token, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} @@ -339,7 +337,7 @@ async def async_step_init(self, user_input=None): mawaqit_token = f.read() f.close() - nearest_mosques = await self.neighborhood(lat=lat, long=longi, token=mawaqit_token) + nearest_mosques = await self.neighborhood(lat, longi, token=mawaqit_token) text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") json.dump(nearest_mosques, text_file) @@ -349,7 +347,7 @@ async def async_step_init(self, user_input=None): uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosque_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: name_servers.extend([distro["label"]]) @@ -368,10 +366,11 @@ async def async_step_init(self, user_input=None): return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) - async def neighborhood(self, lat, long, username, password, api): + async def neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): """Return mosques in the neighborhood if any. Returns a list of dicts.""" try: - client = MawaqitClient(lat,long,'',username,password, api, '') + client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + await client.get_api_token() nearest_mosques = await client.all_mosques_neighborhood() await client.close() except BadCredentialsException: @@ -379,10 +378,11 @@ async def neighborhood(self, lat, long, username, password, api): return nearest_mosques - async def fetch_prayer_times(self, lat, long, mosque, username, password, api): + async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): """Get prayer times from the MAWAQIT API. Returns a dict.""" try: - client = MawaqitClient(lat, long, mosque, username, password, api, '') + client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + await client.get_api_token() dict_calendar = await client.fetch_prayer_times() await client.close() return dict_calendar diff --git a/custom_components/mawaqit/mawaqit.py b/custom_components/mawaqit/mawaqit.py index ad565f1..fc54941 100755 --- a/custom_components/mawaqit/mawaqit.py +++ b/custom_components/mawaqit/mawaqit.py @@ -19,7 +19,7 @@ def prayer_times_url(mosque_id: int) -> str: - return f"{API_URL_BASE}/mosques/{mosque_id}/prayer-times" + return f"{API_URL_BASE}/mosque/{mosque_id}/prayer-times" MAX_LOGIN_RETRIES = 20 @@ -57,8 +57,7 @@ def __init__( token: str = None, session: ClientSession = None, ) -> None: - """Initialize the client.""" - + self.username = username self.password = password self.latitude = latitude @@ -99,11 +98,7 @@ async def get_api_token(self) -> str: async def all_mosques_neighborhood(self): """Get the five nearest mosques from the Client coordinates. Returns a list of dicts with info on the mosques.""" - if self.token is None: - token = await self.get_api_token() - else: - token = self.token - + if (self.latitude is None) or (self.longitude is None): raise MissingCredentials("Please provide a latitude and a longitude in your MawaqitClient object.") @@ -112,7 +107,7 @@ async def all_mosques_neighborhood(self): "lon": self.longitude } headers = { - 'Authorization': token, + 'Authorization': self.token, 'Content-Type': 'application/json', } @@ -138,20 +133,16 @@ async def fetch_prayer_times(self) -> dict: else: mosque_id = self.mosque - if self.token is None: - api_token = await self.get_api_token() - else: - api_token = self.token - headers = {'Content-Type': 'application/json', - 'Api-Access-Token': format(api_token)} + 'Api-Access-Token': format(self.token)} endpoint_url = prayer_times_url(mosque_id) async with self.session.get(endpoint_url, data=None, headers=headers) as response: if response.status != 200: - raise NotAuthenticatedException("Authentication failed. Please check your MAWAQIT credentials.") + raise NotAuthenticatedException("Authentication failed. Please retry. Response.status : " + str(response.status)) data = await response.json() + return data async def login(self) -> None: @@ -165,8 +156,11 @@ async def login(self) -> None: endpoint_url = LOGIN_URL async with await self.session.post(endpoint_url, auth=auth) as response: - if response.status != 200: + if response.status == 401: raise BadCredentialsException("Authentication failed. Please check your MAWAQIT credentials.") + elif response.status != 200: + raise NotAuthenticatedException("Authentication failed. Please retry.") + data = await response.text() self.token = json.loads(data)["apiAccessToken"] From 62e769ded33dba3a0c71c5a583ea9180c556d27c Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 15 Nov 2023 04:49:42 +0100 Subject: [PATCH 06/38] fix lots of issues --- custom_components/mawaqit/__init__.py | 18 +- custom_components/mawaqit/config_flow.py | 210 ++++++++---------- custom_components/mawaqit/mawaqit.py | 6 +- custom_components/mawaqit/strings.json | 2 +- .../mawaqit/translations/en.json | 2 +- .../mawaqit/translations/fr.json | 2 +- 6 files changed, 105 insertions(+), 135 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index b59cc73..6f240f7 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -95,6 +95,16 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload Mawaqit Prayer entry from config_entry.""" + + if hass.data[DOMAIN].event_unsub: + hass.data[DOMAIN].event_unsub() + hass.data.pop(DOMAIN) + + return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + +async def async_remove_entry(hass, config_entry): + """Remove Mawaqit Prayer entry from config_entry.""" + current_dir = os.path.dirname(os.path.realpath(__file__)) dir_path = '{}/data'.format(current_dir) try: @@ -108,11 +118,7 @@ async def async_unload_entry(hass, config_entry): except OSError as e: print("Error: %s : %s" % (dir_path, e.strerror)) - if hass.data[DOMAIN].event_unsub: - hass.data[DOMAIN].event_unsub() - hass.data.pop(DOMAIN) - - return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + return await hass.config_entries.async_remove(config_entry) class MawaqitPrayerClient: @@ -148,7 +154,7 @@ def get_new_prayer_times(self): uuid_servers=[] CALC_METHODS=[] - with open('{}/data/all_mosquee_NN.txt'.format(current_dir), "r") as f: + with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: distros_dict = json.load(f) for distro in distros_dict: diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 070800a..ac2bb73 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -12,6 +12,7 @@ import asyncio import aiohttp import os +import shutil import json import time import homeassistant.helpers.config_validation as cv @@ -21,11 +22,13 @@ _LOGGER = logging.getLogger(__name__) +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) + class MawaqitPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Config flow for mawaqit.""" + """Config flow for MAWAQIT.""" - VERSION = 2.1 + VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): @@ -41,9 +44,12 @@ async def async_step_user(self, user_input=None): longi = self.hass.config.longitude # create data folder if does not exist - current_dir = os.path.dirname(os.path.realpath(__file__)) - if not os.path.exists('{}/data'.format(current_dir)): - os.makedirs('{}/data'.format(current_dir)) + create_data_folder() + + # if the data folder is empty, we can continue the configuration + # otherwise, we abort the configuration because that means that the user has already configured an entry. + if not is_data_folder_empty(): + return self.async_abort(reason="one_instance_allowed") if user_input is None: return await self._show_config_form(user_input=None) @@ -63,36 +69,23 @@ async def async_step_user(self, user_input=None): mawaqit_token = await self.get_mawaqit_api_token(username, password) # create data folder if does not exist - current_dir = os.path.dirname(os.path.realpath(__file__)) - if not os.path.exists('{}/data'.format(current_dir)): - os.makedirs('{}/data'.format(current_dir)) - - text_file = open('{}/data/api.txt'.format(current_dir), "w") + create_data_folder() + + text_file = open('{}/data/api.txt'.format(CURRENT_DIR), "w") text_file.write(mawaqit_token) text_file.close() try: - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, username, password, mawaqit_token) + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) except NoMosqueAround: return self.async_abort(reason="no_mosque") - text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques, text_file) - text_file.close() + write_all_mosques_NN_file(nearest_mosques) # creation of the list of mosques to be displayed in the options - name_servers = [] - uuid_servers = [] - CALC_METHODS = [] - - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - text_file = open('{}/mosq_list.py'.format(current_dir), "w") + text_file = open('{}/mosq_list.py'.format(CURRENT_DIR), "w") n = text_file.write("CALC_METHODS = " + str(CALC_METHODS)) text_file.close() @@ -112,22 +105,11 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: lat = self.hass.config.latitude longi = self.hass.config.longitude - current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/api.txt'.format(current_dir)) - mawaqit_token = f.read() - f.close() + mawaqit_token = get_mawaqit_token_from_file() if user_input is not None: - name_servers=[] - uuid_servers=[] - CALC_METHODS=[] - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() mosque = user_input[CONF_UUID] index = name_servers.index(mosque) @@ -135,14 +117,12 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) - text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques[index], text_file) - text_file.close() + write_my_mosque_NN_file(nearest_mosques[index]) # the mosque chosen by user dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") + text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") json.dump(dict_calendar, text_file) text_file.close() @@ -179,27 +159,14 @@ async def _show_config_form2(self): lat = self.hass.config.latitude longi = self.hass.config.longitude - current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/api.txt'.format(current_dir)) - mawaqit_token = f.read() - f.close() + mawaqit_token = get_mawaqit_token_from_file() - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) - text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques, text_file) - text_file.close() + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) - name_servers=[] - uuid_servers=[] - CALC_METHODS=[] + write_all_mosques_NN_file(nearest_mosques) - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() return self.async_show_form( step_id="mosques", @@ -241,28 +208,30 @@ async def get_mawaqit_api_token(self, username, password): return token - async def all_mosques_neighborhood(self, lat, long, username = None, password = None, token = None): - """Return mosques in the neighborhood if any.""" + async def all_mosques_neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): + """Return mosques in the neighborhood if any. Returns a list of dicts.""" try: - client = MawaqitClient(latitude=lat, longitude=long, username=username, password=password, token=token) + client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) await client.get_api_token() nearest_mosques = await client.all_mosques_neighborhood() await client.close() - except BadCredentialsException: - pass + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving mosques: %s", e) return nearest_mosques - async def fetch_prayer_times(self, lat = None, long = None, mosque = None, username = None, password = None, token = None): + async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): """Get prayer times from the MAWAQIT API. Returns a dict.""" + try: - client = MawaqitClient(latitude=lat, longitude=long, mosque=mosque, username=username, password=password, token=token) + client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) await client.get_api_token() dict_calendar = await client.fetch_prayer_times() await client.close() except BadCredentialsException: pass + return dict_calendar @@ -279,41 +248,26 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude - current_dir = os.path.dirname(os.path.realpath(__file__)) - - name_servers=[] - uuid_servers=[] - CALC_METHODS=[] - - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() mosque = user_input['calculation_method'] index = name_servers.index(mosque) mosque_id = uuid_servers[index] - f = open('{}/data/api.txt'.format(current_dir)) - mawaqit_token = f.read() - f.close() + mawaqit_token = get_mawaqit_token_from_file() try: - nearest_mosques = await self.neighborhood(latitude=lat, longitude=longi, token=mawaqit_token) + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) except NoMosqueAround: # TODO raise NoMosqueAround("No mosque around.") - text_file = open('{}/data/my_mosque_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques[index], text_file) - text_file.close() + write_my_mosque_NN_file(nearest_mosques[index]) # the mosque chosen by user dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=current_dir, name=""), "w") + text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") json.dump(dict_calendar, text_file) text_file.close() @@ -332,27 +286,13 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude - current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/api.txt'.format(current_dir)) - mawaqit_token = f.read() - f.close() + mawaqit_token = get_mawaqit_token_from_file() - nearest_mosques = await self.neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) - text_file = open('{}/data/all_mosques_NN.txt'.format(current_dir), "w") - json.dump(nearest_mosques, text_file) - text_file.close() + write_all_mosques_NN_file(nearest_mosques) - name_servers=[] - uuid_servers=[] - CALC_METHODS=[] - - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() options = { vol.Optional( @@ -366,26 +306,52 @@ async def async_step_init(self, user_input=None): return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) - async def neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): + async def all_mosques_neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): """Return mosques in the neighborhood if any. Returns a list of dicts.""" - try: - client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) - await client.get_api_token() - nearest_mosques = await client.all_mosques_neighborhood() - await client.close() - except BadCredentialsException: - pass - return nearest_mosques + return await MawaqitPrayerFlowHandler.all_mosques_neighborhood(self, latitude, longitude, mosque, username, password, token) async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): """Get prayer times from the MAWAQIT API. Returns a dict.""" - try: - client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) - await client.get_api_token() - dict_calendar = await client.fetch_prayer_times() - await client.close() - return dict_calendar - except BadCredentialsException: - pass - return dict_calendar + return await MawaqitPrayerFlowHandler.fetch_prayer_times(self, latitude, longitude, mosque, username, password, token) + + +def read_all_mosques_NN_file(): + name_servers = [] + uuid_servers = [] + CALC_METHODS = [] + + with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "r") as f: + distros_dict = json.load(f) + for distro in distros_dict: + name_servers.extend([distro["label"]]) + uuid_servers.extend([distro["uuid"]]) + CALC_METHODS.extend([distro["label"]]) + + return name_servers, uuid_servers, CALC_METHODS + +def write_all_mosques_NN_file(mosques): + with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "w") as f: + json.dump(mosques, f) + +def write_my_mosque_NN_file(mosque): + text_file = open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), "w") + json.dump(mosque, text_file) + text_file.close() + +def create_data_folder(): + if not os.path.exists('{}/data'.format(CURRENT_DIR)): + os.makedirs('{}/data'.format(CURRENT_DIR)) + +def get_mawaqit_token_from_file(): + f = open('{}/data/api.txt'.format(CURRENT_DIR)) + mawaqit_token = f.read() + f.close() + return mawaqit_token + +def delete_data_folder(): + if os.path.exists('{}/data'.format(CURRENT_DIR)): + shutil.rmtree('{}/data'.format(CURRENT_DIR)) + +def is_data_folder_empty(): + return not os.listdir('{}/data'.format(CURRENT_DIR)) diff --git a/custom_components/mawaqit/mawaqit.py b/custom_components/mawaqit/mawaqit.py index fc54941..90032d6 100755 --- a/custom_components/mawaqit/mawaqit.py +++ b/custom_components/mawaqit/mawaqit.py @@ -157,12 +157,10 @@ async def login(self) -> None: async with await self.session.post(endpoint_url, auth=auth) as response: if response.status == 401: - raise BadCredentialsException("Authentication failed. Please check your MAWAQIT credentials.") + raise BadCredentialsException("Authentication failed. Please check your MAWAQIT credentials. Response.status : " + str(response.status)) elif response.status != 200: - raise NotAuthenticatedException("Authentication failed. Please retry.") + raise NotAuthenticatedException("Authentication failed. Please retry. Response.status : " + str(response.status)) data = await response.text() self.token = json.loads(data)["apiAccessToken"] - - diff --git a/custom_components/mawaqit/strings.json b/custom_components/mawaqit/strings.json index 4896b73..d6df1bd 100755 --- a/custom_components/mawaqit/strings.json +++ b/custom_components/mawaqit/strings.json @@ -36,7 +36,7 @@ }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "one_instance_allowed": "Une seule instance (entrée) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosquée, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante.", "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." } diff --git a/custom_components/mawaqit/translations/en.json b/custom_components/mawaqit/translations/en.json index 2755052..3636796 100755 --- a/custom_components/mawaqit/translations/en.json +++ b/custom_components/mawaqit/translations/en.json @@ -36,7 +36,7 @@ }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "one_instance_allowed": "One MAWAQIT entry is enough. If you want to change your mosque, please click on the \"Configure\" button of the existing entry.", "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." } diff --git a/custom_components/mawaqit/translations/fr.json b/custom_components/mawaqit/translations/fr.json index d46a3e6..ac4abb9 100755 --- a/custom_components/mawaqit/translations/fr.json +++ b/custom_components/mawaqit/translations/fr.json @@ -37,7 +37,7 @@ }, "abort": { - "one_instance_allowed": "Une seule instance de MAWAQIT est possible.", + "one_instance_allowed": "Une seule instance (entrée) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosquée, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante.", "no_mosque": "D\u00e9sol\u00e9, aucune mosqu\u00e9e MAWAQIT n'est disponible près de chez vous." } From efef4c49af7f6bfa363ac8be910b72b6a098576a Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 15 Nov 2023 05:19:24 +0100 Subject: [PATCH 07/38] fix some issues --- custom_components/mawaqit/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 6f240f7..fa85c7d 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -118,8 +118,6 @@ async def async_remove_entry(hass, config_entry): except OSError as e: print("Error: %s : %s" % (dir_path, e.strerror)) - return await hass.config_entries.async_remove(config_entry) - class MawaqitPrayerClient: """Mawaqit Prayer Client Object.""" From 90dc522a555d0782dc6d12615368e09a78d88994 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Wed, 15 Nov 2023 06:00:57 +0100 Subject: [PATCH 08/38] enhancement distance --- custom_components/mawaqit/config_flow.py | 33 ++++++++++-------------- custom_components/mawaqit/mawaqit.py | 4 ++- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index ac2bb73..6f54d81 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -1,22 +1,18 @@ """Adds config flow for Mawaqit.""" -from homeassistant import config_entries +import logging +import aiohttp +import os +import json +from typing import Any import voluptuous as vol from .const import CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME, CONF_SERVER, USERNAME, PASSWORD, CONF_UUID from .mawaqit import MawaqitClient, BadCredentialsException, NoMosqueAround +from homeassistant import config_entries from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN - -import logging -import asyncio -import aiohttp -import os -import shutil -import json -import time import homeassistant.helpers.config_validation as cv -from typing import Any from homeassistant.data_entry_flow import FlowResult @@ -322,11 +318,14 @@ def read_all_mosques_NN_file(): CALC_METHODS = [] with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "r") as f: - distros_dict = json.load(f) - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) + dict_mosques = json.load(f) + for mosque in dict_mosques: + distance = mosque["proximity"] + distance = distance/1000 + distance = round(distance, 2) + name_servers.extend([mosque["label"] + ' (' + str(distance) + 'km)']) + uuid_servers.extend([mosque["uuid"]]) + CALC_METHODS.extend([mosque["label"]]) return name_servers, uuid_servers, CALC_METHODS @@ -349,9 +348,5 @@ def get_mawaqit_token_from_file(): f.close() return mawaqit_token -def delete_data_folder(): - if os.path.exists('{}/data'.format(CURRENT_DIR)): - shutil.rmtree('{}/data'.format(CURRENT_DIR)) - def is_data_folder_empty(): return not os.listdir('{}/data'.format(CURRENT_DIR)) diff --git a/custom_components/mawaqit/mawaqit.py b/custom_components/mawaqit/mawaqit.py index 90032d6..e3065d0 100755 --- a/custom_components/mawaqit/mawaqit.py +++ b/custom_components/mawaqit/mawaqit.py @@ -11,6 +11,8 @@ import aiohttp from aiohttp import ClientSession +import asyncio + JSON = Union[Dict[str, Any], List[Dict[str, Any]]] API_URL_BASE = "https://mawaqit.net/api/2.0" @@ -163,4 +165,4 @@ async def login(self) -> None: data = await response.text() - self.token = json.loads(data)["apiAccessToken"] + self.token = json.loads(data)["apiAccessToken"] \ No newline at end of file From 5e8036a7dc4815329fc0f3992a51f5884a252baa Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Fri, 17 Nov 2023 18:26:01 +0100 Subject: [PATCH 09/38] fix issue #49 --- custom_components/mawaqit/__init__.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index fa85c7d..e825ad8 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -243,26 +243,27 @@ def get_new_prayer_times(self): res['Mosque_localisation']=data["localisation"] res['Mosque_url']=data["url"] res['Mosque_image']=data["image"] - - # The Iqama countdown from Adhan is stored in pray_time.txt as well. - iqamaCalendar = data["iqamaCalendar"] - iqamas = iqamaCalendar[index_month][str(index_day)] # Today's iqama times. - try: - # The iqama countdown is stored as a string with a + sign. - # So, we need to remove the + and convert the countdown to an int. - iqamas = [int(countdown.replace("+", "")) for countdown in iqamas] - except ValueError: - iqamas = [0, 0, 0, 0, 0] # We store the prayer times of the day in HH:MM format. prayers = [datetime.strptime(prayer, '%H:%M') for prayer in day_times] del prayers[1] # Because there's no iqama for shurouq. + # The Iqama countdown from Adhan is stored in pray_time.txt as well. + iqamaCalendar = data["iqamaCalendar"] + iqamas = iqamaCalendar[index_month][str(index_day)] # Today's iqama times. + # We store the iqama times of the day in HH:MM format. iqama_times = [] for (prayer, iqama) in zip(prayers, iqamas): - iqama_times.append((prayer + timedelta(minutes=iqama)).strftime("%H:%M")) + # The iqama can be either stored as a minutes countdown starting by a '+', or as a fixed time (HH:MM). + if '+' in iqama: + iqama_times.append((prayer + timedelta(minutes=iqama)).strftime("%H:%M")) + elif ':' in iqama: + iqama_times.append(iqama) + else: + # if there's a bug, we just append the prayer time for now. + iqama.append(prayer) iqama_names = ["Fajr Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama"] From 37ec15f48a504096816392311c7f58263f24a9fc Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Thu, 30 Nov 2023 21:48:16 +0100 Subject: [PATCH 10/38] fix bug --- custom_components/mawaqit/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index e825ad8..6feb749 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -258,6 +258,7 @@ def get_new_prayer_times(self): for (prayer, iqama) in zip(prayers, iqamas): # The iqama can be either stored as a minutes countdown starting by a '+', or as a fixed time (HH:MM). if '+' in iqama: + iqama = int(iqama.replace('+', '')) iqama_times.append((prayer + timedelta(minutes=iqama)).strftime("%H:%M")) elif ':' in iqama: iqama_times.append(iqama) @@ -275,6 +276,14 @@ def get_new_prayer_times(self): async def async_update_next_salat_sensor(self, *_): + # DEBUG + with open("logs.txt", "a") as f: + f.write(f"async_update_next_salat_sensor launched : {datetime.now().strftime('%d/%m at %H:%M:%S')}\n") + f.write(f"self.prayer_times_info['Next Salat Name'] : {self.prayer_times_info['Next Salat Name']}\n") + f.write(f"self.prayer_times_info['Next Salat Time'] : {self.prayer_times_info['Next Salat Time']}\n") + # END DEBUG + + salat_before_update = self.prayer_times_info['Next Salat Name'] prayers = ["Fajr", "Dhuhr", "Asr", "Maghrib", "Isha"] if salat_before_update != "Isha": # We just retrieve the next salat of the day. From 6856a6edec3850679c0e33519b641439b80275c0 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Thu, 30 Nov 2023 22:34:05 +0100 Subject: [PATCH 11/38] VERSION WITH DEBUGS --- custom_components/mawaqit/__init__.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 6feb749..4c7cb6e 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -130,13 +130,11 @@ def __init__(self, hass, config_entry): self.available = True self.event_unsub = None - @property def calc_method(self): """Return the calculation method.""" return self.config_entry.options[CONF_CALC_METHOD] - def get_new_prayer_times(self): """Fetch prayer times for today.""" mawaqit_login = self.config_entry.data.get("username") @@ -182,7 +180,6 @@ def get_new_prayer_times(self): day_times = month_times[str(index_day)] # Today's times prayer_names = ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha"] - res = {prayer_names[i]: day_times[i] for i in range(len(prayer_names))} try: day_times_tomorrow = month_times[str(index_day + 1)] @@ -199,15 +196,20 @@ def get_new_prayer_times(self): today = datetime.today().strftime("%Y-%m-%d") tomorrow = (datetime.today() + timedelta(days=1)).strftime("%Y-%m-%d") - + + prayer_names = ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha"] prayers = [] + res = {} + for j in range(len(prayer_names)): if prayer_names[j] == "Shurouq": - pray = tomorrow + " " + "23:59:00" # We never take in account shurouq in the calculation of next_salat + pray = tomorrow + " " + "23:59:00" # We never take Shurouq in account in the calculation of next_salat else: if datetime.strptime(day_times[j], '%H:%M') < datetime.strptime(now, '%H:%M'): + res[prayer_names[j]] = day_times_tomorrow[j] pray = tomorrow + " " + day_times_tomorrow[j] + ":00" else: + res[prayer_names[j]] = day_times[j] pray = today + " " + day_times[j] + ":00" prayers.append(pray) @@ -274,10 +276,10 @@ def get_new_prayer_times(self): return res2 - async def async_update_next_salat_sensor(self, *_): # DEBUG with open("logs.txt", "a") as f: + f.write("\n\nBEFORE :\n") f.write(f"async_update_next_salat_sensor launched : {datetime.now().strftime('%d/%m at %H:%M:%S')}\n") f.write(f"self.prayer_times_info['Next Salat Name'] : {self.prayer_times_info['Next Salat Name']}\n") f.write(f"self.prayer_times_info['Next Salat Time'] : {self.prayer_times_info['Next Salat Time']}\n") @@ -330,6 +332,13 @@ async def async_update_next_salat_sensor(self, *_): self.prayer_times_info['Next Salat Name'] = "Fajr" self.prayer_times_info['Next Salat Time'] = dt_util.parse_datetime(f"{today.year}-{today.month}-{index_day} {fajr_hour}:00") + # DEBUG + with open("logs.txt", "a") as f: + f.write("AFTER :\n") + f.write(f"self.prayer_times_info['Next Salat Name'] : {self.prayer_times_info['Next Salat Name']}\n") + f.write(f"self.prayer_times_info['Next Salat Time'] : {self.prayer_times_info['Next Salat Time']}\n") + # END DEBUG + countdown_next_prayer = 15 self.prayer_times_info['Next Salat Preparation'] = self.prayer_times_info['Next Salat Time'] - timedelta(minutes=countdown_next_prayer) @@ -369,6 +378,7 @@ async def async_update(self, *_): pray_date += timedelta(days=(4 - pray_date.weekday() + 7) % 7) # We convert the date to string to be able to put it in the dictionary. pray = pray_date.strftime("%Y-%m-%d") + self.prayer_times_info[prayer] = dt_util.parse_datetime( f"{pray} {time}" ) @@ -381,6 +391,7 @@ async def async_update(self, *_): for prayer in prayer_times: next_update_at = prayer + timedelta(minutes=1) + _LOGGER.error("Next update at: %s (prayer number %s)", next_update_at, prayer_times.index(prayer) + 1) async_track_point_in_time( self.hass, self.async_update_next_salat_sensor, next_update_at ) From 624ec618a6f8ec2a0a0f41ebd17889485afa61e2 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Fri, 1 Dec 2023 19:54:59 +0100 Subject: [PATCH 12/38] impotant bugs + trad --- custom_components/mawaqit/__init__.py | 23 ++++++++----------- .../mawaqit/translations/fr.json | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 4c7cb6e..4f25183 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -178,8 +178,6 @@ def get_new_prayer_times(self): index_day = today.day day_times = month_times[str(index_day)] # Today's times - - prayer_names = ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha"] try: day_times_tomorrow = month_times[str(index_day + 1)] @@ -201,18 +199,15 @@ def get_new_prayer_times(self): prayers = [] res = {} - for j in range(len(prayer_names)): - if prayer_names[j] == "Shurouq": - pray = tomorrow + " " + "23:59:00" # We never take Shurouq in account in the calculation of next_salat - else: - if datetime.strptime(day_times[j], '%H:%M') < datetime.strptime(now, '%H:%M'): - res[prayer_names[j]] = day_times_tomorrow[j] - pray = tomorrow + " " + day_times_tomorrow[j] + ":00" - else: - res[prayer_names[j]] = day_times[j] - pray = today + " " + day_times[j] + ":00" - - prayers.append(pray) + for i in range(len(prayer_names)): + if datetime.strptime(day_times[i], '%H:%M') < datetime.strptime(now, '%H:%M'): + res[prayer_names[i]] = day_times_tomorrow[i] + pray = tomorrow + " " + day_times_tomorrow[i] + ":00" + else: + res[prayer_names[i]] = day_times[i] + pray = today + " " + day_times[i] + ":00" + if prayer_names[i] != "Shurouq": + prayers.append(pray) # Then the next prayer is the nearest prayer time, so the min of the prayers list next_prayer = min(prayers) diff --git a/custom_components/mawaqit/translations/fr.json b/custom_components/mawaqit/translations/fr.json index ac4abb9..3c440b9 100755 --- a/custom_components/mawaqit/translations/fr.json +++ b/custom_components/mawaqit/translations/fr.json @@ -1,5 +1,5 @@ { - "title": "Heures de prières MAWAQIT", + "title": "MAWAQIT", "config": { "step": { "user": { From 79fc7020c491e96778246b4efcd1210843449a9c Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Thu, 18 Jan 2024 00:06:43 +0100 Subject: [PATCH 13/38] Fixes #51 + other small changes --- custom_components/mawaqit/__init__.py | 44 ++++++------------------ custom_components/mawaqit/config_flow.py | 3 -- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 4f25183..9a2517b 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -149,21 +149,6 @@ def get_new_prayer_times(self): name_servers=[] uuid_servers=[] CALC_METHODS=[] - - with open('{}/data/all_mosques_NN.txt'.format(current_dir), "r") as f: - distros_dict = json.load(f) - - for distro in distros_dict: - name_servers.extend([distro["label"]]) - uuid_servers.extend([distro["uuid"]]) - CALC_METHODS.extend([distro["label"]]) - - if mosque == "nearest" or mosque == "no mosque in neighborhood": - indice = 0 - else: - indice = name_servers.index(mosque) - - mosque_id = uuid_servers[indice] # We get the prayer times of the year from pray_time.txt f = open('{dir}/data/pray_time.txt'.format(dir=current_dir), "r") @@ -272,13 +257,6 @@ def get_new_prayer_times(self): return res2 async def async_update_next_salat_sensor(self, *_): - # DEBUG - with open("logs.txt", "a") as f: - f.write("\n\nBEFORE :\n") - f.write(f"async_update_next_salat_sensor launched : {datetime.now().strftime('%d/%m at %H:%M:%S')}\n") - f.write(f"self.prayer_times_info['Next Salat Name'] : {self.prayer_times_info['Next Salat Name']}\n") - f.write(f"self.prayer_times_info['Next Salat Time'] : {self.prayer_times_info['Next Salat Time']}\n") - # END DEBUG salat_before_update = self.prayer_times_info['Next Salat Name'] @@ -327,13 +305,6 @@ async def async_update_next_salat_sensor(self, *_): self.prayer_times_info['Next Salat Name'] = "Fajr" self.prayer_times_info['Next Salat Time'] = dt_util.parse_datetime(f"{today.year}-{today.month}-{index_day} {fajr_hour}:00") - # DEBUG - with open("logs.txt", "a") as f: - f.write("AFTER :\n") - f.write(f"self.prayer_times_info['Next Salat Name'] : {self.prayer_times_info['Next Salat Name']}\n") - f.write(f"self.prayer_times_info['Next Salat Time'] : {self.prayer_times_info['Next Salat Time']}\n") - # END DEBUG - countdown_next_prayer = 15 self.prayer_times_info['Next Salat Preparation'] = self.prayer_times_info['Next Salat Time'] - timedelta(minutes=countdown_next_prayer) @@ -341,6 +312,9 @@ async def async_update_next_salat_sensor(self, *_): async_dispatcher_send(self.hass, DATA_UPDATED) async def async_update(self, *_): + # TODO : Reload pray_time.txt so we avoid bugs if prayer_times changes (for example if the mosque decides to change the iqama delay of a prayer) + # get ID from my_mosque.txt, then create MawaqitClient and generate the dict with the prayer times. + """Update sensors with new prayer times.""" try: prayer_times = await self.hass.async_add_executor_job( @@ -384,12 +358,16 @@ async def async_update(self, *_): prayers = ["Fajr", "Dhuhr", "Asr", "Maghrib", "Isha"] prayer_times = [self.prayer_times_info[prayer] for prayer in prayers] + # We cancel the previous scheduled updates (if there is any) to avoid multiple updates for the same prayer. + if self.cancel_events_next_salat: + for cancel_event in self.cancel_events_next_salat: + cancel_event() + self.cancel_events_next_salat = [] + for prayer in prayer_times: next_update_at = prayer + timedelta(minutes=1) - _LOGGER.error("Next update at: %s (prayer number %s)", next_update_at, prayer_times.index(prayer) + 1) - async_track_point_in_time( - self.hass, self.async_update_next_salat_sensor, next_update_at - ) + cancel_event = async_track_point_in_time(self.hass, self.async_update_next_salat_sensor, next_update_at) + self.cancel_events_next_salat.append(cancel_event) _LOGGER.debug("New prayer times retrieved. Updating sensors.") async_dispatcher_send(self.hass, DATA_UPDATED) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 6f54d81..2c63280 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -64,9 +64,6 @@ async def async_step_user(self, user_input=None): if valid: mawaqit_token = await self.get_mawaqit_api_token(username, password) - # create data folder if does not exist - create_data_folder() - text_file = open('{}/data/api.txt'.format(CURRENT_DIR), "w") text_file.write(mawaqit_token) text_file.close() From 3e0dfee29493c8d7475eb0c4bc7cd439bd59b330 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri Date: Thu, 18 Jan 2024 01:09:53 +0100 Subject: [PATCH 14/38] Fixes #51 + Change Mosque Bug --- custom_components/mawaqit/__init__.py | 6 ++++-- custom_components/mawaqit/config_flow.py | 25 +++++++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 9a2517b..6020c1a 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -314,7 +314,7 @@ async def async_update_next_salat_sensor(self, *_): async def async_update(self, *_): # TODO : Reload pray_time.txt so we avoid bugs if prayer_times changes (for example if the mosque decides to change the iqama delay of a prayer) # get ID from my_mosque.txt, then create MawaqitClient and generate the dict with the prayer times. - + """Update sensors with new prayer times.""" try: prayer_times = await self.hass.async_add_executor_job( @@ -359,9 +359,11 @@ async def async_update(self, *_): prayer_times = [self.prayer_times_info[prayer] for prayer in prayers] # We cancel the previous scheduled updates (if there is any) to avoid multiple updates for the same prayer. - if self.cancel_events_next_salat: + try: for cancel_event in self.cancel_events_next_salat: cancel_event() + except AttributeError: + pass self.cancel_events_next_salat = [] for prayer in prayer_times: diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 2c63280..6dc1074 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -242,8 +242,8 @@ async def async_step_init(self, user_input=None): longi = self.hass.config.longitude name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - - mosque = user_input['calculation_method'] + + mosque = user_input[CONF_CALC_METHOD] index = name_servers.index(mosque) mosque_id = uuid_servers[index] @@ -286,13 +286,19 @@ async def async_step_init(self, user_input=None): write_all_mosques_NN_file(nearest_mosques) name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - + + current_mosque = read_my_mosque_NN_file()["uuid"] + + try: + index = uuid_servers.index(current_mosque) + default_name = name_servers[index] + except ValueError: + default_name = "None" + options = { - vol.Optional( + vol.Required( CONF_CALC_METHOD, - default=self.config_entry.options.get( - CONF_CALC_METHOD, DEFAULT_CALC_METHOD - ), + default=default_name, ): vol.In(name_servers) } @@ -330,6 +336,11 @@ def write_all_mosques_NN_file(mosques): with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "w") as f: json.dump(mosques, f) +def read_my_mosque_NN_file(): + with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), "r") as f: + mosque = json.load(f) + return mosque + def write_my_mosque_NN_file(mosque): text_file = open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), "w") json.dump(mosque, text_file) From ab65bd50ad08e5a22364f1e1cfee3dd9cdb74ce5 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+mohaThr@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:52:07 +0100 Subject: [PATCH 15/38] New version working with PyPI library --- custom_components/mawaqit/__init__.py | 14 +++++++++----- custom_components/mawaqit/config_flow.py | 21 +++++++++++---------- custom_components/mawaqit/manifest.json | 5 +++-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 6020c1a..224cbc0 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta from dateutil import parser as date_parser -from .mawaqit import BadCredentialsException #, MawaqitClient +from mawaqit.consts import BadCredentialsException from requests.exceptions import ConnectionError as ConnError @@ -191,8 +191,12 @@ def get_new_prayer_times(self): else: res[prayer_names[i]] = day_times[i] pray = today + " " + day_times[i] + ":00" - if prayer_names[i] != "Shurouq": - prayers.append(pray) + + # # We never take in account shurouq in the calculation of next_salat + if prayer_names[i] == "Shurouq": + pray = tomorrow + " " + "23:59:59" + + prayers.append(pray) # Then the next prayer is the nearest prayer time, so the min of the prayers list next_prayer = min(prayers) @@ -258,9 +262,9 @@ def get_new_prayer_times(self): async def async_update_next_salat_sensor(self, *_): - salat_before_update = self.prayer_times_info['Next Salat Name'] prayers = ["Fajr", "Dhuhr", "Asr", "Maghrib", "Isha"] + if salat_before_update != "Isha": # We just retrieve the next salat of the day. index = prayers.index(salat_before_update) + 1 self.prayer_times_info['Next Salat Name'] = prayers[index] @@ -313,7 +317,7 @@ async def async_update_next_salat_sensor(self, *_): async def async_update(self, *_): # TODO : Reload pray_time.txt so we avoid bugs if prayer_times changes (for example if the mosque decides to change the iqama delay of a prayer) - # get ID from my_mosque.txt, then create MawaqitClient and generate the dict with the prayer times. + # get ID from my_mosque.txt, then create AsyncMawaqitClient and generate the dict with the prayer times. """Update sensors with new prayer times.""" try: diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 6dc1074..ddc32c5 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -7,7 +7,9 @@ import voluptuous as vol from .const import CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME, CONF_SERVER, USERNAME, PASSWORD, CONF_UUID -from .mawaqit import MawaqitClient, BadCredentialsException, NoMosqueAround + +from mawaqit import AsyncMawaqitClient +from mawaqit.consts import BadCredentialsException, NoMosqueAround from homeassistant import config_entries @@ -181,7 +183,7 @@ def async_get_options_flow(config_entry): async def _test_credentials(self, username, password): """Return True if the MAWAQIT credentials is valid.""" try: - client = MawaqitClient(username=username, password=password) + client = AsyncMawaqitClient(username=username, password=password) await client.login() await client.close() return True @@ -192,11 +194,11 @@ async def _test_credentials(self, username, password): async def get_mawaqit_api_token(self, username, password): """Return the MAWAQIT API token.""" try: - client = MawaqitClient(username=username, password=password) + client = AsyncMawaqitClient(username=username, password=password) token = await client.get_api_token() await client.close() - except BadCredentialsException: - pass + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving API Token: %s", e) return token @@ -204,7 +206,7 @@ async def get_mawaqit_api_token(self, username, password): async def all_mosques_neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): """Return mosques in the neighborhood if any. Returns a list of dicts.""" try: - client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) await client.get_api_token() nearest_mosques = await client.all_mosques_neighborhood() await client.close() @@ -218,12 +220,12 @@ async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = N """Get prayer times from the MAWAQIT API. Returns a dict.""" try: - client = MawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) await client.get_api_token() dict_calendar = await client.fetch_prayer_times() await client.close() - except BadCredentialsException: - pass + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving prayer times: %s", e) return dict_calendar @@ -252,7 +254,6 @@ async def async_step_init(self, user_input=None): try: nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) except NoMosqueAround: - # TODO raise NoMosqueAround("No mosque around.") write_my_mosque_NN_file(nearest_mosques[index]) diff --git a/custom_components/mawaqit/manifest.json b/custom_components/mawaqit/manifest.json index 7b20947..e1b5b94 100755 --- a/custom_components/mawaqit/manifest.json +++ b/custom_components/mawaqit/manifest.json @@ -2,8 +2,9 @@ "domain": "mawaqit", "name": "MAWAQIT", "documentation": "https://github.com/mawaqit/home-assistant", - "requirements": [], - "codeowners": ["@MAWAQIT"], + "requirements": ["mawaqit==0.0.1"], + "integration_type": "hub", + "codeowners": ["@MAWAQIT", "@mohaThr"], "config_flow": true, "iot_class": "cloud_polling", "version": "2.0.0" From 83de12636dcbc4d751a056778b3122b7b8fa3747 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:43:47 -0800 Subject: [PATCH 16/38] added tests to a part of MawaqitPrayerFlowHandler Class --- .gitignore | 1 + custom_components/tests/mawaqit/__init__.py | 1 + .../tests/mawaqit/test_config_flow.py | 89 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 custom_components/tests/mawaqit/__init__.py create mode 100644 custom_components/tests/mawaqit/test_config_flow.py diff --git a/.gitignore b/.gitignore index 2811992..80ab4a0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ custom_components/mawaqit/data custom_components/mawaqit/test.py custom_components/.DS_Store .DS_Store +__pycache__/ \ No newline at end of file diff --git a/custom_components/tests/mawaqit/__init__.py b/custom_components/tests/mawaqit/__init__.py new file mode 100644 index 0000000..acbc5fe --- /dev/null +++ b/custom_components/tests/mawaqit/__init__.py @@ -0,0 +1 @@ +"""Tests for the Mawaqit component.""" diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py new file mode 100644 index 0000000..e82390e --- /dev/null +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -0,0 +1,89 @@ +# from unittest.async_case import AsyncTestCase +from unittest.mock import patch, Mock, MagicMock +import pytest +from homeassistant import config_entries, data_entry_flow +from homeassistant.core import HomeAssistant +from homeassistant.components.mawaqit import DOMAIN +from homeassistant.components.mawaqit import config_flow +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from mawaqit.consts import NoMosqueAround +# from tests.common import MockConfigEntry + +# pytestmark = pytest.mark.usefixtures("mock_setup_entry") +# pytestmark = pytest.mark.usefixtures("mock_setup_entry") + +@pytest.mark.asyncio +async def test_show_form(hass: HomeAssistant): + """Test that the form is served with no input.""" + # Initialize the flow handler with the HomeAssistant instance + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + with patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ): + # Invoke the initial step of the flow without user input + result = await flow.async_step_user(user_input=None) + + # Validate that the form is returned to the user + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + + +@pytest.mark.asyncio +async def test_async_step_user_no_neighborhood(hass: HomeAssistant): + """Test the user step when no mosque is found in the neighborhood.""" + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + # Patching the methods used in the flow to simulate external interactions + with patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + return_value=True, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", + return_value="MAWAQIT_API_TOKEN", + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + side_effect=NoMosqueAround, + ), patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ): + # Simulate user input to trigger the flow's logic + result = await flow.async_step_user( + {CONF_USERNAME: "testuser", CONF_PASSWORD: "testpass"} + ) + + # Check that the flow is aborted due to the lack of mosques nearby + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_mosque" + + +@pytest.mark.asyncio +async def test_async_step_user_invalid_credentials(hass: HomeAssistant): + """Test the user step with invalid credentials.""" + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + # Patch the credentials test to simulate a login failure + with patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + return_value=False, + ), patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ): + # Simulate user input with incorrect credentials + result = await flow.async_step_user( + {CONF_USERNAME: "wronguser", CONF_PASSWORD: "wrongpass"} + ) + + # Validate that the error is correctly handled and reported + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + #data_entry_flow.RESULT_TYPE_ABORT + # data_entry_flow.RESULT_TYPE_FORM + print(result) + assert "base" in result["errors"] + assert result["errors"]["base"] == "wrong_credential"# From 852389b733bddf87dd3b17af483941e7e64b203e Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 13 Mar 2024 03:32:35 -0700 Subject: [PATCH 17/38] added a test for the mosque step of config_flow --- .../tests/mawaqit/test_config_flow.py | 103 ++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index e82390e..a04021e 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -5,15 +5,16 @@ from homeassistant.core import HomeAssistant from homeassistant.components.mawaqit import DOMAIN from homeassistant.components.mawaqit import config_flow -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_UUID from mawaqit.consts import NoMosqueAround # from tests.common import MockConfigEntry # pytestmark = pytest.mark.usefixtures("mock_setup_entry") # pytestmark = pytest.mark.usefixtures("mock_setup_entry") + @pytest.mark.asyncio -async def test_show_form(hass: HomeAssistant): +async def test_show_form_user(hass: HomeAssistant): """Test that the form is served with no input.""" # Initialize the flow handler with the HomeAssistant instance flow = config_flow.MawaqitPrayerFlowHandler() @@ -81,9 +82,97 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): ) # Validate that the error is correctly handled and reported - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - #data_entry_flow.RESULT_TYPE_ABORT - # data_entry_flow.RESULT_TYPE_FORM - print(result) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert "base" in result["errors"] - assert result["errors"]["base"] == "wrong_credential"# + assert result["errors"]["base"] == "wrong_credential" + + +@pytest.mark.asyncio +async def test_async_step_mosques(hass): + mock_mosques = [ + { + "uuid": "aaaaa-bbbbb-cccccc-0000", + "name": "Mosque1", + "type": "MOSQUE", + "slug": "1-mosque", + "latitude": 48, + "longitude": 1, + "jumua": None, + "proximity": 1744, + "label": "Mosque1", + "localisation": "aaaaa bbbbb cccccc", + }, + { + "uuid": "bbbbb-cccccc-ddddd-0000", + "name": "Mosque2", + "type": "MOSQUE", + "slug": "2-mosque", + "latitude": 47, + "longitude": 1, + "jumua": None, + "proximity": 20000, + "label": "Mosque1", + "localisation": "bbbbb cccccc ddddd", + }, + { + "uuid": "bbbbb-cccccc-ddddd-0001", + "name": "Mosque3", + "type": "MOSQUE", + "slug": "2-mosque", + "latitude": 47, + "longitude": 1, + "jumua": None, + "proximity": 20000, + "label": "Mosque1", + "localisation": "bbbbb cccccc ddddd", + }, + ] + + mocked_mosques_data = ( + ["Mosque1 (1.74km)", "Mosque2 (20.0km)", "Mosque2 (20.0km)"], # name_servers + [ + "aaaaa-bbbbb-cccccc-0000", + "bbbbb-cccccc-ddddd-0000", + "bbbbb-cccccc-ddddd-0001", + ], # uuid_servers + ["Mosque1", "Mosque2", "Mosque3"], # CALC_METHODS + ) + + # Mock external dependencies + with patch( + "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + return_value="TOKEN", + ), patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + return_value=mock_mosques, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times", + return_value={}, + ): + # Initialize the flow + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + # # Pre-fill the token and mosques list as if the user step has been completed + flow.context = {} + + # Call the mosques step + result = await flow.async_step_mosques() + + # Verify the form is displayed with correct mosques options + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + # print(result["data_schema"].schema) + assert CONF_UUID in result["data_schema"].schema + + # Now simulate the user selecting a mosque and submitting the form + mosque_uuid_label = "Mosque1 (1.74km)" # Assuming the user selects the first mosque + mosque_uuid = "aaaaa-bbbbb-cccccc-0000" # uuid of the first mosque + + result = await flow.async_step_mosques({CONF_UUID: mosque_uuid_label}) + + # Verify the flow processes the selection correctly + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_UUID] == mosque_uuid From 754fd93734b44f40720c4a8cbaa938a42ed72cdf Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:39:53 -0700 Subject: [PATCH 18/38] added tests --- .../tests/mawaqit/test_config_flow.py | 384 ++++++++++++++++-- 1 file changed, 342 insertions(+), 42 deletions(-) diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index a04021e..50fe3f4 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -1,20 +1,31 @@ -# from unittest.async_case import AsyncTestCase -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import patch, Mock, MagicMock, mock_open import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.core import HomeAssistant from homeassistant.components.mawaqit import DOMAIN from homeassistant.components.mawaqit import config_flow +from homeassistant.components.mawaqit.const import CONF_CALC_METHOD from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_UUID from mawaqit.consts import NoMosqueAround -# from tests.common import MockConfigEntry - -# pytestmark = pytest.mark.usefixtures("mock_setup_entry") -# pytestmark = pytest.mark.usefixtures("mock_setup_entry") +import json +import os +import aiohttp +@pytest.mark.asyncio +async def test_step_user_one_instance_allowed(hass: HomeAssistant): + # test if if the data folder is not empty we abort + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + with patch('homeassistant.components.mawaqit.config_flow.is_data_folder_empty', return_value=False): + + result = await flow.async_step_user(None) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "one_instance_allowed" + @pytest.mark.asyncio -async def test_show_form_user(hass: HomeAssistant): +async def test_show_form_user_no_input_reopens_form(hass: HomeAssistant): """Test that the form is served with no input.""" # Initialize the flow handler with the HomeAssistant instance flow = config_flow.MawaqitPrayerFlowHandler() @@ -33,21 +44,21 @@ async def test_show_form_user(hass: HomeAssistant): @pytest.mark.asyncio -async def test_async_step_user_no_neighborhood(hass: HomeAssistant): - """Test the user step when no mosque is found in the neighborhood.""" +async def test_async_step_user_connection_error(hass: HomeAssistant): + """Test the user step handles connection errors correctly.""" flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass + # Create an instance of ClientConnectorError with mock arguments + mock_conn_key = MagicMock() + mock_os_error = MagicMock() + connection_error_instance = aiohttp.client_exceptions.ClientConnectorError(mock_conn_key, mock_os_error) + + # Patching the methods used in the flow to simulate external interactions with patch( "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", - return_value=True, - ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", - return_value="MAWAQIT_API_TOKEN", - ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", - side_effect=NoMosqueAround, + side_effect=connection_error_instance, ), patch( "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", return_value=True, @@ -57,10 +68,11 @@ async def test_async_step_user_no_neighborhood(hass: HomeAssistant): {CONF_USERNAME: "testuser", CONF_PASSWORD: "testpass"} ) - # Check that the flow is aborted due to the lack of mosques nearby - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "no_mosque" - + # Check that the flow returns a form with an error message due to the connection error + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert "base" in result["errors"] + assert result["errors"]["base"] == "cannot_connect_to_server" @pytest.mark.asyncio async def test_async_step_user_invalid_credentials(hass: HomeAssistant): @@ -86,9 +98,66 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): assert "base" in result["errors"] assert result["errors"]["base"] == "wrong_credential" +@pytest.mark.asyncio +async def test_async_step_user_valid_credentials(hass: HomeAssistant): + """Test the user step with invalid credentials.""" + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + # Patch the credentials test to simulate a login failure + with patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + return_value=True, + ), patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", + return_value="MAWAQIT_API_TOKEN", + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + return_value={}, + ): + # Simulate user input with incorrect credentials + result = await flow.async_step_user( + {CONF_USERNAME: "wronguser", CONF_PASSWORD: "wrongpass"} + ) + + # Validate that the error is correctly handled and reported + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mosques" @pytest.mark.asyncio -async def test_async_step_mosques(hass): +async def test_async_step_user_no_neighborhood(hass: HomeAssistant): + """Test the user step when no mosque is found in the neighborhood.""" + flow = config_flow.MawaqitPrayerFlowHandler() + flow.hass = hass + + # Patching the methods used in the flow to simulate external interactions + with patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + return_value=True, + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", + return_value="MAWAQIT_API_TOKEN", + ), patch( + "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + side_effect=NoMosqueAround, + ): + # Simulate user input to trigger the flow's logic + result = await flow.async_step_user( + {CONF_USERNAME: "testuser", CONF_PASSWORD: "testpass"} + ) + + # Check that the flow is aborted due to the lack of mosques nearby + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_mosque" + +@pytest.fixture +async def mock_mosques_test_data(): mock_mosques = [ { "uuid": "aaaaa-bbbbb-cccccc-0000", @@ -99,19 +168,19 @@ async def test_async_step_mosques(hass): "longitude": 1, "jumua": None, "proximity": 1744, - "label": "Mosque1", + "label": "Mosque1-label", "localisation": "aaaaa bbbbb cccccc", }, { "uuid": "bbbbb-cccccc-ddddd-0000", - "name": "Mosque2", + "name": "Mosque2-label", "type": "MOSQUE", "slug": "2-mosque", "latitude": 47, "longitude": 1, "jumua": None, "proximity": 20000, - "label": "Mosque1", + "label": "Mosque2-label", "localisation": "bbbbb cccccc ddddd", }, { @@ -123,35 +192,32 @@ async def test_async_step_mosques(hass): "longitude": 1, "jumua": None, "proximity": 20000, - "label": "Mosque1", + "label": "Mosque3-label", "localisation": "bbbbb cccccc ddddd", }, ] mocked_mosques_data = ( - ["Mosque1 (1.74km)", "Mosque2 (20.0km)", "Mosque2 (20.0km)"], # name_servers + ['Mosque1-label (1.74km)', 'Mosque2-label (20.0km)', 'Mosque3-label (20.0km)'], # name_servers [ "aaaaa-bbbbb-cccccc-0000", "bbbbb-cccccc-ddddd-0000", "bbbbb-cccccc-ddddd-0001", ], # uuid_servers - ["Mosque1", "Mosque2", "Mosque3"], # CALC_METHODS + ['Mosque1-label', 'Mosque2-label', 'Mosque3-label'], # CALC_METHODS ) + return mock_mosques, mocked_mosques_data + +@pytest.mark.asyncio +async def test_async_step_mosques(hass, mock_mosques_test_data): + mock_mosques, mocked_mosques_data = mock_mosques_test_data + # Mock external dependencies - with patch( - "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", - return_value="TOKEN", - ), patch( - "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", - return_value=mocked_mosques_data, - ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", - return_value=mock_mosques, - ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times", - return_value={}, - ): + with patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file",return_value="TOKEN",), \ + patch("homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file",return_value=mocked_mosques_data,), \ + patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood",return_value=mock_mosques,), \ + patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times",return_value={},):#empty data # Initialize the flow flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass @@ -168,11 +234,245 @@ async def test_async_step_mosques(hass): assert CONF_UUID in result["data_schema"].schema # Now simulate the user selecting a mosque and submitting the form - mosque_uuid_label = "Mosque1 (1.74km)" # Assuming the user selects the first mosque - mosque_uuid = "aaaaa-bbbbb-cccccc-0000" # uuid of the first mosque + mosque_uuid_label = mocked_mosques_data[0][0] # Assuming the user selects the first mosque + mosque_uuid = mocked_mosques_data[1][0] # uuid of the first mosque result = await flow.async_step_mosques({CONF_UUID: mosque_uuid_label}) # Verify the flow processes the selection correctly assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_UUID] == mosque_uuid + + +@pytest.fixture +def mock_config_entry_setup(): + """Create a mock ConfigEntry.""" + entry = MagicMock(spec=config_entries.ConfigEntry) + # Set up any necessary properties of the entry here + # For example: entry.entry_id = "test_entry" + return entry + +@pytest.mark.asyncio +async def test_async_get_options_flow(mock_config_entry_setup): + """Test the options flow is correctly instantiated with the config entry.""" + + options_flow = config_flow.MawaqitPrayerFlowHandler.async_get_options_flow(mock_config_entry_setup) + + # Verify that the result is an instance of the expected options flow handler + assert isinstance(options_flow, config_flow.MawaqitPrayerOptionsFlowHandler) + # check that the config entry is correctly passed to the handler + assert options_flow.config_entry == mock_config_entry_setup + +@pytest.fixture +async def config_entry_setup(hass: HomeAssistant): + """Create a mock config entry for tests.""" + entry = config_entries.ConfigEntry( + version=1, + minor_version=1, + domain=DOMAIN, + title='MAWAQIT - Mosque1', + data={'api_key': 'TOKEN', 'uuid': 'aaaaa-bbbbb-cccccc-0000', 'latitude': 32.87336, 'longitude': -117.22743}, + source=config_entries.SOURCE_USER, + ) + + return entry + +@pytest.mark.asyncio +async def test_options_flow_valid_input(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): + + mock_mosques, mocked_mosques_data = mock_mosques_test_data + + """Test the options flow.""" + with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ + patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ + patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value=mock_mosques), \ + patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times", return_value={}):# empty data + + # Initialize the options flow + flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) + flow.hass = hass # Assign HomeAssistant instance + + + # Simulate user input in the options flow + mosque_uuid_label = mocked_mosques_data[0][1] # Assuming the user selects the first mosque + mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque + + #TODO chage this if we remove the create_entry line 278 + result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY # Assert that an entry is created/updated + # assert result["data"][CONF_UUID] == mosque_uuid + +@pytest.mark.asyncio +async def test_options_flow_error_no_mosques_around(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): + + _ , mocked_mosques_data = mock_mosques_test_data + + """Test the options flow.""" + with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ + patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ + patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', side_effect=NoMosqueAround): + + + # Initialize the options flow + flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) + flow.hass = hass # Assign HomeAssistant instance + + + # Simulate user input in the options flow + mosque_uuid_label = mocked_mosques_data[0][1] # Assuming the user selects the first mosque + mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque + + # Since we expect an exception, let's catch it to assert it happens + with pytest.raises(NoMosqueAround): + result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) + #Same tests as in test_options_flow_valid_input : + #TODO chage this if we remove the create_entry line 278 + result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY # Assert that an entry is created/updated + # assert result["data"][CONF_UUID] == mosque_uuid + +@pytest.mark.asyncio +async def test_options_flow_no_input_reopens_form(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): + + # MawaqitPrayerOptionsFlowHandler. + mock_mosques , mocked_mosques_data = mock_mosques_test_data + + """Test the options flow.""" + with patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value={}), \ + patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ + patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value=mock_mosques[0]): + + # Initialize the options flow + flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) + flow.hass = hass # Assign HomeAssistant instance + + # Simulate the init step + result = await flow.async_step_init(user_input=None) + assert result["type"] == data_entry_flow.FlowResultType.FORM # Assert that a form is shown + assert result["step_id"] == "init" + +@pytest.mark.asyncio +async def test_options_flow_no_input_error_reopens_form(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): + + # MawaqitPrayerOptionsFlowHandler. + _ , mocked_mosques_data = mock_mosques_test_data + + """Test the options flow.""" + with patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value={}), \ + patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ + patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value={"uuid": "non_existent_uuid"}):#, \ + + # Initialize the options flow + flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) + flow.hass = hass # Assign HomeAssistant instance + + # Simulate the init step + result = await flow.async_step_init(user_input=None) + # Same tests as test_options_flow_no_input_reopens_form : + assert result["type"] == data_entry_flow.FlowResultType.FORM # Assert that a form is shown + assert result["step_id"] == "init" + +@pytest.fixture +async def mock_data_folder(): + # Utility fixture to mock os.path.exists and os.makedirs + with patch("os.path.exists") as mock_exists: + with patch("os.makedirs") as mock_makedirs: + yield mock_exists, mock_makedirs + + +@pytest.fixture +async def mock_file_io(): + # Utility fixture for mocking open + with patch("builtins.open", mock_open()) as mocked_file: + yield mocked_file + + +@pytest.mark.asyncio +async def test_read_all_mosques_NN_file(mock_mosques_test_data): + sample_data, expected_output = mock_mosques_test_data + with patch("builtins.open", mock_open(read_data=json.dumps(sample_data))): + assert config_flow.read_all_mosques_NN_file() == expected_output + +@pytest.fixture(scope="function") +def test_folder_setup(): + # Define the folder name + folder_name = './test_dir' + # Create the folder + os.makedirs(folder_name, exist_ok=True) + # Pass the folder name to the test + yield folder_name + # No deletion here, handled by another fixture + +@pytest.fixture(scope="function", autouse=True) +def test_folder_cleanup(request, test_folder_setup): + # This fixture does not need to do anything before the test, + #so it yields control immediately + yield + # Teardown: Delete the folder after the test runs + folder_path = test_folder_setup # Corrected variable name + def cleanup(): + if os.path.exists(folder_path): + os.rmdir(folder_path) # Make sure the folder is empty before calling rmdir + request.addfinalizer(cleanup) + +@pytest.mark.asyncio +async def test_write_all_mosques_NN_file(mock_file_io,test_folder_setup): + # test for write_all_mosques_NN_file + with patch('homeassistant.components.mawaqit.config_flow.CURRENT_DIR', new='./test_dir'): + mosques = [{"label": "Mosque A", "uuid": "uuid1"}] + config_flow.write_all_mosques_NN_file(mosques) + mock_file_io.assert_called_with(f"{test_folder_setup}/data/all_mosques_NN.txt", "w") + assert mock_file_io().write.called, "The file's write method was not called." + +@pytest.mark.asyncio +async def test_read_my_mosque_NN_file(): + # test for read_my_mosque_NN_file + sample_mosque = {"label": "My Mosque", "uuid": "myuuid"} + with patch("builtins.open", mock_open(read_data=json.dumps(sample_mosque))): + assert config_flow.read_my_mosque_NN_file() == sample_mosque + +@pytest.mark.asyncio +# @patch('path.to.your.config_flow_module.CURRENT_DIR', './test_dir') +async def test_write_my_mosque_NN_file(mock_file_io,test_folder_setup): + # test for write_my_mosque_NN_file + with patch('homeassistant.components.mawaqit.config_flow.CURRENT_DIR', new='./test_dir'): + mosque = {"label": "My Mosque", "uuid": "myuuid"} + config_flow.write_my_mosque_NN_file(mosque) + mock_file_io.assert_called_with(f"{test_folder_setup}/data/my_mosque_NN.txt", "w") + assert mock_file_io().write.called, "The file's write method was not called." + +@pytest.mark.asyncio +async def test_create_data_folder_already_exists(mock_data_folder): + # test for create_data_folder + mock_exists, mock_makedirs = mock_data_folder + mock_exists.return_value = True + config_flow.create_data_folder() + mock_makedirs.assert_not_called() + +@pytest.mark.asyncio +async def test_create_data_folder_does_not_exist(mock_data_folder): + mock_exists, mock_makedirs = mock_data_folder + mock_exists.return_value = False + config_flow.create_data_folder() + mock_makedirs.assert_called_once() + +@pytest.mark.asyncio +async def test_get_mawaqit_token_from_file(): + # test for get_mawaqit_token_from_file + token = "some-token" + with patch("builtins.open", mock_open(read_data=token)): + assert config_flow.get_mawaqit_token_from_file() == token + +@pytest.mark.asyncio +async def test_is_data_folder_empty_true(): + # test for is_data_folder_empty + with patch("os.listdir", return_value=[]): + assert config_flow.is_data_folder_empty() == True + +@pytest.mark.asyncio +async def test_is_data_folder_empty_false(): + with patch("os.listdir", return_value=["some_file.txt"]): + assert config_flow.is_data_folder_empty() == False + + + \ No newline at end of file From 3ad86cd68c6819571d8016876e1e7a2b75e50ed1 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:37:35 -0700 Subject: [PATCH 19/38] refactoring the way the api calls were made --- custom_components/mawaqit/config_flow.py | 84 +++---------------- custom_components/mawaqit/mawaqit_wrapper.py | 55 ++++++++++++ .../tests/mawaqit/test_config_flow.py | 30 +++---- 3 files changed, 81 insertions(+), 88 deletions(-) create mode 100644 custom_components/mawaqit/mawaqit_wrapper.py diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index ddc32c5..171ea56 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -8,10 +8,10 @@ from .const import CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME, CONF_SERVER, USERNAME, PASSWORD, CONF_UUID -from mawaqit import AsyncMawaqitClient from mawaqit.consts import BadCredentialsException, NoMosqueAround from homeassistant import config_entries +from . import mawaqit_wrapper from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN import homeassistant.helpers.config_validation as cv @@ -57,21 +57,21 @@ async def async_step_user(self, user_input=None): # check if the user credentials are correct (valid = True) : try: - valid = await self._test_credentials(username, password) + valid = await mawaqit_wrapper._test_credentials(username, password) # if we have an error connecting to the server : except aiohttp.client_exceptions.ClientConnectorError: self._errors["base"] = "cannot_connect_to_server" return await self._show_config_form(user_input) if valid: - mawaqit_token = await self.get_mawaqit_api_token(username, password) + mawaqit_token = await mawaqit_wrapper.get_mawaqit_api_token(username, password) text_file = open('{}/data/api.txt'.format(CURRENT_DIR), "w") text_file.write(mawaqit_token) text_file.close() try: - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) except NoMosqueAround: return self.async_abort(reason="no_mosque") @@ -110,12 +110,12 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: index = name_servers.index(mosque) mosque_id = uuid_servers[index] - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) write_my_mosque_NN_file(nearest_mosques[index]) # the mosque chosen by user - dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) + dict_calendar = await mawaqit_wrapper.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") json.dump(dict_calendar, text_file) @@ -157,7 +157,7 @@ async def _show_config_form2(self): mawaqit_token = get_mawaqit_token_from_file() - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) write_all_mosques_NN_file(nearest_mosques) @@ -179,57 +179,6 @@ def async_get_options_flow(config_entry): return MawaqitPrayerOptionsFlowHandler(config_entry) - - async def _test_credentials(self, username, password): - """Return True if the MAWAQIT credentials is valid.""" - try: - client = AsyncMawaqitClient(username=username, password=password) - await client.login() - await client.close() - return True - except BadCredentialsException: - return False - - - async def get_mawaqit_api_token(self, username, password): - """Return the MAWAQIT API token.""" - try: - client = AsyncMawaqitClient(username=username, password=password) - token = await client.get_api_token() - await client.close() - except BadCredentialsException as e: - _LOGGER.error("Error on retrieving API Token: %s", e) - - return token - - - async def all_mosques_neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): - """Return mosques in the neighborhood if any. Returns a list of dicts.""" - try: - client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) - await client.get_api_token() - nearest_mosques = await client.all_mosques_neighborhood() - await client.close() - except BadCredentialsException as e: - _LOGGER.error("Error on retrieving mosques: %s", e) - - return nearest_mosques - - - async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): - """Get prayer times from the MAWAQIT API. Returns a dict.""" - - try: - client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) - await client.get_api_token() - dict_calendar = await client.fetch_prayer_times() - await client.close() - except BadCredentialsException as e: - _LOGGER.error("Error on retrieving prayer times: %s", e) - - return dict_calendar - - class MawaqitPrayerOptionsFlowHandler(config_entries.OptionsFlow): """Handle Mawaqit Prayer client options.""" @@ -252,14 +201,14 @@ async def async_step_init(self, user_input=None): mawaqit_token = get_mawaqit_token_from_file() try: - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) except NoMosqueAround: raise NoMosqueAround("No mosque around.") write_my_mosque_NN_file(nearest_mosques[index]) # the mosque chosen by user - dict_calendar = await self.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) + dict_calendar = await mawaqit_wrapper.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") json.dump(dict_calendar, text_file) @@ -282,7 +231,7 @@ async def async_step_init(self, user_input=None): mawaqit_token = get_mawaqit_token_from_file() - nearest_mosques = await self.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) write_all_mosques_NN_file(nearest_mosques) @@ -303,18 +252,7 @@ async def async_step_init(self, user_input=None): ): vol.In(name_servers) } - return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) - - - async def all_mosques_neighborhood(self, latitude, longitude, mosque = None, username = None, password = None, token = None): - """Return mosques in the neighborhood if any. Returns a list of dicts.""" - return await MawaqitPrayerFlowHandler.all_mosques_neighborhood(self, latitude, longitude, mosque, username, password, token) - - - async def fetch_prayer_times(self, latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): - """Get prayer times from the MAWAQIT API. Returns a dict.""" - return await MawaqitPrayerFlowHandler.fetch_prayer_times(self, latitude, longitude, mosque, username, password, token) - + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) def read_all_mosques_NN_file(): name_servers = [] diff --git a/custom_components/mawaqit/mawaqit_wrapper.py b/custom_components/mawaqit/mawaqit_wrapper.py new file mode 100644 index 0000000..b7f72ec --- /dev/null +++ b/custom_components/mawaqit/mawaqit_wrapper.py @@ -0,0 +1,55 @@ +from mawaqit import AsyncMawaqitClient +from mawaqit.consts import BadCredentialsException, NoMosqueAround +import logging + +_LOGGER = logging.getLogger(__name__) + + +async def _test_credentials(username, password): + """Return True if the MAWAQIT credentials is valid.""" + try: + client = AsyncMawaqitClient(username=username, password=password) + await client.login() + await client.close() + return True + except BadCredentialsException: + return False + + +async def get_mawaqit_api_token( username, password): + """Return the MAWAQIT API token.""" + try: + client = AsyncMawaqitClient(username=username, password=password) + token = await client.get_api_token() + await client.close() + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving API Token: %s", e) + + return token + + +async def all_mosques_neighborhood( latitude, longitude, mosque = None, username = None, password = None, token = None): + """Return mosques in the neighborhood if any. Returns a list of dicts.""" + try: + client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + await client.get_api_token() + nearest_mosques = await client.all_mosques_neighborhood() + await client.close() + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving mosques: %s", e) + + return nearest_mosques + + +async def fetch_prayer_times(latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): + """Get prayer times from the MAWAQIT API. Returns a dict.""" + + try: + client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + await client.get_api_token() + dict_calendar = await client.fetch_prayer_times() + await client.close() + except BadCredentialsException as e: + _LOGGER.error("Error on retrieving prayer times: %s", e) + + return dict_calendar \ No newline at end of file diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index 50fe3f4..0eed5e6 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -57,7 +57,7 @@ async def test_async_step_user_connection_error(hass: HomeAssistant): # Patching the methods used in the flow to simulate external interactions with patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", side_effect=connection_error_instance, ), patch( "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", @@ -82,7 +82,7 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): # Patch the credentials test to simulate a login failure with patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", return_value=False, ), patch( "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", @@ -106,16 +106,16 @@ async def test_async_step_user_valid_credentials(hass: HomeAssistant): # Patch the credentials test to simulate a login failure with patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", return_value=True, ), patch( "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", return_value=True, ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", return_value="MAWAQIT_API_TOKEN", ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", return_value={}, ): # Simulate user input with incorrect credentials @@ -138,13 +138,13 @@ async def test_async_step_user_no_neighborhood(hass: HomeAssistant): "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", return_value=True, ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler._test_credentials", + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", return_value=True, ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.get_mawaqit_api_token", + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", return_value="MAWAQIT_API_TOKEN", ), patch( - "homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood", + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", side_effect=NoMosqueAround, ): # Simulate user input to trigger the flow's logic @@ -216,8 +216,8 @@ async def test_async_step_mosques(hass, mock_mosques_test_data): # Mock external dependencies with patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file",return_value="TOKEN",), \ patch("homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file",return_value=mocked_mosques_data,), \ - patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.all_mosques_neighborhood",return_value=mock_mosques,), \ - patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times",return_value={},):#empty data + patch("homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood",return_value=mock_mosques,), \ + patch("homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times",return_value={},):#empty data # Initialize the flow flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass @@ -285,8 +285,8 @@ async def test_options_flow_valid_input(hass: HomeAssistant, config_entry_setup, """Test the options flow.""" with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ - patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value=mock_mosques), \ - patch("homeassistant.components.mawaqit.config_flow.MawaqitPrayerFlowHandler.fetch_prayer_times", return_value={}):# empty data + patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value=mock_mosques), \ + patch("homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", return_value={}):# empty data # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) @@ -310,7 +310,7 @@ async def test_options_flow_error_no_mosques_around(hass: HomeAssistant, config_ """Test the options flow.""" with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ - patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', side_effect=NoMosqueAround): + patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', side_effect=NoMosqueAround): # Initialize the options flow @@ -338,7 +338,7 @@ async def test_options_flow_no_input_reopens_form(hass: HomeAssistant, config_en mock_mosques , mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" - with patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value={}), \ + with patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value={}), \ patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value=mock_mosques[0]): @@ -358,7 +358,7 @@ async def test_options_flow_no_input_error_reopens_form(hass: HomeAssistant, con _ , mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" - with patch('homeassistant.components.mawaqit.config_flow.MawaqitPrayerOptionsFlowHandler.all_mosques_neighborhood', return_value={}), \ + with patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value={}), \ patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value={"uuid": "non_existent_uuid"}):#, \ From f32517adb76a6027a54b11ea2c3420b90ab410b5 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:10:02 -0700 Subject: [PATCH 20/38] drop usage of mosq_list.py --- custom_components/mawaqit/__init__.py | 18 +++++++++++++++--- custom_components/mawaqit/config_flow.py | 6 +++--- custom_components/mawaqit/mosq_list.py | 1 - 3 files changed, 18 insertions(+), 7 deletions(-) delete mode 100755 custom_components/mawaqit/mosq_list.py diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index 224cbc0..a5e4390 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -35,10 +35,22 @@ CONF_UUID, ) -from .mosq_list import ( - CALC_METHODS, -) +import json + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) +file_path = f"{CURRENT_DIR}/mosq_list_data" +try: + with open(file_path, "r") as text_file: + data = json.load(text_file) + + # Accessing the CALC_METHODS object + CALC_METHODS = data["CALC_METHODS"] +except FileNotFoundError: + #First Run + print(f"The file {file_path} was not found.") + CALC_METHODS = [] + # from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 171ea56..1f21caf 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -80,9 +80,9 @@ async def async_step_user(self, user_input=None): # creation of the list of mosques to be displayed in the options name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - text_file = open('{}/mosq_list.py'.format(CURRENT_DIR), "w") - n = text_file.write("CALC_METHODS = " + str(CALC_METHODS)) - text_file.close() + file_path = f"{CURRENT_DIR}/mosq_list_data" + with open(file_path, "w+") as text_file: + json.dump({"CALC_METHODS": CALC_METHODS}, text_file) return await self.async_step_mosques() diff --git a/custom_components/mawaqit/mosq_list.py b/custom_components/mawaqit/mosq_list.py deleted file mode 100755 index 88cda74..0000000 --- a/custom_components/mawaqit/mosq_list.py +++ /dev/null @@ -1 +0,0 @@ -CALC_METHODS = ['مسجد التوبة - Lyon', 'مسجد الروضة - Lyon', 'El AMINE - Lyon', 'CIMG Vaise - Lyon', 'Mosquée Al Houda مسجد الهدى - Lyon'] \ No newline at end of file From cd20f243aa3bf71bc120b52ed934397c845633d0 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:14:04 -0700 Subject: [PATCH 21/38] changed directory where mosq_list_data is saved --- custom_components/mawaqit/__init__.py | 2 +- custom_components/mawaqit/config_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py index a5e4390..46d238e 100755 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -38,7 +38,7 @@ import json CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -file_path = f"{CURRENT_DIR}/mosq_list_data" +file_path = f"{CURRENT_DIR}/data/mosq_list_data" try: with open(file_path, "r") as text_file: diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 1f21caf..71f79e4 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -80,7 +80,7 @@ async def async_step_user(self, user_input=None): # creation of the list of mosques to be displayed in the options name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - file_path = f"{CURRENT_DIR}/mosq_list_data" + file_path = f"{CURRENT_DIR}/data/mosq_list_data" with open(file_path, "w+") as text_file: json.dump({"CALC_METHODS": CALC_METHODS}, text_file) From f41115cef46dec021bac96b3880b764cdbad0eea Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 28 Apr 2024 04:22:58 -0700 Subject: [PATCH 22/38] removed unecessary return self.async_create_entry(title=None, data=None) --- custom_components/mawaqit/config_flow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 71f79e4..fd01406 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -223,8 +223,7 @@ async def async_step_init(self, user_input=None): title=title, data=data ) - - return self.async_create_entry(title=None, data=None) + return self.config_entry lat = self.hass.config.latitude longi = self.hass.config.longitude From 66a75cb080710c2532042e16734cd6f10deb63e5 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 04:50:49 -0700 Subject: [PATCH 23/38] edit multiple files --- .gitignore | 2 + custom_components/mawaqit/__init__.py | 203 +++++---- custom_components/mawaqit/config_flow.py | 199 ++++++--- custom_components/mawaqit/manifest.json | 16 +- custom_components/mawaqit/mawaqit_wrapper.py | 53 ++- custom_components/mawaqit/strings.json | 13 +- .../mawaqit/translations/en.json | 93 ++-- .../tests/mawaqit/test_config_flow.py | 416 +++++++++++++----- 8 files changed, 637 insertions(+), 358 deletions(-) mode change 100755 => 100644 custom_components/mawaqit/__init__.py diff --git a/.gitignore b/.gitignore index 80ab4a0..88c2c83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ custom_components/mawaqit/data +custom_components/mawaqit/translations/* custom_components/mawaqit/test.py +custom_components/mawaqit/strings.json custom_components/.DS_Store .DS_Store __pycache__/ \ No newline at end of file diff --git a/custom_components/mawaqit/__init__.py b/custom_components/mawaqit/__init__.py old mode 100755 new mode 100644 index 46d238e..e6c8b77 --- a/custom_components/mawaqit/__init__.py +++ b/custom_components/mawaqit/__init__.py @@ -1,42 +1,38 @@ """The mawaqit_prayer_times component.""" + +from datetime import datetime, timedelta +import json import logging import os -import json + # import sys import shutil -from datetime import datetime, timedelta from dateutil import parser as date_parser - from mawaqit.consts import BadCredentialsException - from requests.exceptions import ConnectionError as ConnError - import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.event import async_call_later, async_track_point_in_time, async_track_time_change +from homeassistant.helpers.event import ( + async_call_later, + async_track_point_in_time, + async_track_time_change, +) import homeassistant.util.dt as dt_util -# from homeassistant.helpers import aiohttp_client - +# from homeassistant.helpers import aiohttp_client from .const import ( - API, CONF_CALC_METHOD, DATA_UPDATED, - UPDATE_TIME, DEFAULT_CALC_METHOD, DOMAIN, - USERNAME, - PASSWORD, - CONF_UUID, + UPDATE_TIME, ) -import json - CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) file_path = f"{CURRENT_DIR}/data/mosq_list_data" @@ -47,10 +43,10 @@ # Accessing the CALC_METHODS object CALC_METHODS = data["CALC_METHODS"] except FileNotFoundError: - #First Run + # First Run print(f"The file {file_path} was not found.") CALC_METHODS = [] - + # from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN _LOGGER = logging.getLogger(__name__) @@ -114,17 +110,18 @@ async def async_unload_entry(hass, config_entry): return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + async def async_remove_entry(hass, config_entry): """Remove Mawaqit Prayer entry from config_entry.""" current_dir = os.path.dirname(os.path.realpath(__file__)) - dir_path = '{}/data'.format(current_dir) + dir_path = "{}/data".format(current_dir) try: shutil.rmtree(dir_path) except OSError as e: print("Error: %s : %s" % (dir_path, e.strerror)) - - dir_path = '{}/__pycache__'.format(current_dir) + + dir_path = "{}/__pycache__".format(current_dir) try: shutil.rmtree(dir_path) except OSError as e: @@ -157,24 +154,24 @@ def get_new_prayer_times(self): mosque = self.config_entry.options.get("calculation_method") current_dir = os.path.dirname(os.path.realpath(__file__)) - - name_servers=[] - uuid_servers=[] - CALC_METHODS=[] - + + name_servers = [] + uuid_servers = [] + CALC_METHODS = [] + # We get the prayer times of the year from pray_time.txt - f = open('{dir}/data/pray_time.txt'.format(dir=current_dir), "r") + f = open("{dir}/data/pray_time.txt".format(dir=current_dir), "r") data = json.load(f) calendar = data["calendar"] - + # Then, we get the prayer times of the day into this file today = datetime.today() index_month = today.month - 1 - month_times = calendar[index_month] # Calendar of the month + month_times = calendar[index_month] # Calendar of the month index_day = today.day - day_times = month_times[str(index_day)] # Today's times + day_times = month_times[str(index_day)] # Today's times try: day_times_tomorrow = month_times[str(index_day + 1)] @@ -197,28 +194,38 @@ def get_new_prayer_times(self): res = {} for i in range(len(prayer_names)): - if datetime.strptime(day_times[i], '%H:%M') < datetime.strptime(now, '%H:%M'): + if datetime.strptime(day_times[i], "%H:%M") < datetime.strptime( + now, "%H:%M" + ): res[prayer_names[i]] = day_times_tomorrow[i] pray = tomorrow + " " + day_times_tomorrow[i] + ":00" else: res[prayer_names[i]] = day_times[i] pray = today + " " + day_times[i] + ":00" - + # # We never take in account shurouq in the calculation of next_salat if prayer_names[i] == "Shurouq": pray = tomorrow + " " + "23:59:59" prayers.append(pray) - + # Then the next prayer is the nearest prayer time, so the min of the prayers list next_prayer = min(prayers) - res['Next Salat Time'] = next_prayer.split(" ", 1)[1].rsplit(':', 1)[0] - res['Next Salat Name'] = prayer_names[prayers.index(next_prayer)] + res["Next Salat Time"] = next_prayer.split(" ", 1)[1].rsplit(":", 1)[0] + res["Next Salat Name"] = prayer_names[prayers.index(next_prayer)] countdown_next_prayer = 15 # 15 minutes Before Next Prayer - res['Next Salat Preparation'] = (datetime.strptime(next_prayer, '%Y-%m-%d %H:%M:%S')-timedelta(minutes=countdown_next_prayer)).strftime('%Y-%m-%d %H:%M:%S').split(" ", 1)[1].rsplit(':', 1)[0] - + res["Next Salat Preparation"] = ( + ( + datetime.strptime(next_prayer, "%Y-%m-%d %H:%M:%S") + - timedelta(minutes=countdown_next_prayer) + ) + .strftime("%Y-%m-%d %H:%M:%S") + .split(" ", 1)[1] + .rsplit(":", 1)[0] + ) + # if Jumu'a is set as Dhuhr, then Jumu'a time is the same as Friday's Dhuhr time if data["jumuaAsDuhr"]: # Then, Jumu'a time should be the Dhuhr time of the next Friday @@ -227,64 +234,73 @@ def get_new_prayer_times(self): next_friday = today + timedelta((4 - today.weekday() + 7) % 7) # We get the next Friday's Dhuhr time from the calendar next_friday_dhuhr = calendar[next_friday.month - 1][str(next_friday.day)][2] - res['Jumua'] = next_friday_dhuhr + res["Jumua"] = next_friday_dhuhr # If jumu'a is set as a specific time, then we use that time elif data["jumua"] is not None: - res['Jumua'] = data["jumua"] + res["Jumua"] = data["jumua"] # if mosque has only one jumu'a, then 'Jumua 2' can be None. if data["jumua2"] is not None: - res['Jumua 2'] = data["jumua2"] + res["Jumua 2"] = data["jumua2"] + + res["Mosque_label"] = data["label"] + res["Mosque_localisation"] = data["localisation"] + res["Mosque_url"] = data["url"] + res["Mosque_image"] = data["image"] - res['Mosque_label']=data["label"] - res['Mosque_localisation']=data["localisation"] - res['Mosque_url']=data["url"] - res['Mosque_image']=data["image"] - # We store the prayer times of the day in HH:MM format. - prayers = [datetime.strptime(prayer, '%H:%M') for prayer in day_times] - del prayers[1] # Because there's no iqama for shurouq. + prayers = [datetime.strptime(prayer, "%H:%M") for prayer in day_times] + del prayers[1] # Because there's no iqama for shurouq. # The Iqama countdown from Adhan is stored in pray_time.txt as well. iqamaCalendar = data["iqamaCalendar"] - iqamas = iqamaCalendar[index_month][str(index_day)] # Today's iqama times. + iqamas = iqamaCalendar[index_month][str(index_day)] # Today's iqama times. # We store the iqama times of the day in HH:MM format. iqama_times = [] - for (prayer, iqama) in zip(prayers, iqamas): + for prayer, iqama in zip(prayers, iqamas): # The iqama can be either stored as a minutes countdown starting by a '+', or as a fixed time (HH:MM). - if '+' in iqama: - iqama = int(iqama.replace('+', '')) - iqama_times.append((prayer + timedelta(minutes=iqama)).strftime("%H:%M")) - elif ':' in iqama: + if "+" in iqama: + iqama = int(iqama.replace("+", "")) + iqama_times.append( + (prayer + timedelta(minutes=iqama)).strftime("%H:%M") + ) + elif ":" in iqama: iqama_times.append(iqama) else: # if there's a bug, we just append the prayer time for now. iqama.append(prayer) - iqama_names = ["Fajr Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama"] + iqama_names = [ + "Fajr Iqama", + "Dhuhr Iqama", + "Asr Iqama", + "Maghrib Iqama", + "Isha Iqama", + ] res1 = {iqama_names[i]: iqama_times[i] for i in range(len(iqama_names))} res2 = {**res, **res1} - + return res2 - - async def async_update_next_salat_sensor(self, *_): - salat_before_update = self.prayer_times_info['Next Salat Name'] + async def async_update_next_salat_sensor(self, *_): + salat_before_update = self.prayer_times_info["Next Salat Name"] prayers = ["Fajr", "Dhuhr", "Asr", "Maghrib", "Isha"] - if salat_before_update != "Isha": # We just retrieve the next salat of the day. + if salat_before_update != "Isha": # We just retrieve the next salat of the day. index = prayers.index(salat_before_update) + 1 - self.prayer_times_info['Next Salat Name'] = prayers[index] - self.prayer_times_info['Next Salat Time'] = self.prayer_times_info[prayers[index]] - - else: # We retrieve the next Fajr (more calcualtions). + self.prayer_times_info["Next Salat Name"] = prayers[index] + self.prayer_times_info["Next Salat Time"] = self.prayer_times_info[ + prayers[index] + ] + + else: # We retrieve the next Fajr (more calcualtions). current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{dir}/data/pray_time.txt'.format(dir=current_dir), "r") + f = open("{dir}/data/pray_time.txt".format(dir=current_dir), "r") data = json.load(f) calendar = data["calendar"] @@ -292,11 +308,11 @@ async def async_update_next_salat_sensor(self, *_): index_month = today.month - 1 month_times = calendar[index_month] - maghrib_hour = self.prayer_times_info['Maghrib'] + maghrib_hour = self.prayer_times_info["Maghrib"] maghrib_hour = maghrib_hour.strftime("%H:%M") - + # isha + 1 minute because this function is launched 1 minute after 'Isha, (useful only if 'Isha is at 11:59 PM) - isha_hour = self.prayer_times_info['Isha'] + timedelta(minutes=1) + isha_hour = self.prayer_times_info["Isha"] + timedelta(minutes=1) isha_hour = isha_hour.strftime("%H:%M") # If 'Isha is before 12 AM (Maghrib hour < 'Isha hour), we need to get the next day's Fajr. @@ -305,7 +321,7 @@ async def async_update_next_salat_sensor(self, *_): index_day = today.day + 1 else: index_day = today.day - + try: day_times = month_times[str(index_day)] except KeyError: @@ -318,11 +334,15 @@ async def async_update_next_salat_sensor(self, *_): day_times = calendar[index_next_month]["1"] fajr_hour = day_times[0] - self.prayer_times_info['Next Salat Name'] = "Fajr" - self.prayer_times_info['Next Salat Time'] = dt_util.parse_datetime(f"{today.year}-{today.month}-{index_day} {fajr_hour}:00") + self.prayer_times_info["Next Salat Name"] = "Fajr" + self.prayer_times_info["Next Salat Time"] = dt_util.parse_datetime( + f"{today.year}-{today.month}-{index_day} {fajr_hour}:00" + ) countdown_next_prayer = 15 - self.prayer_times_info['Next Salat Preparation'] = self.prayer_times_info['Next Salat Time'] - timedelta(minutes=countdown_next_prayer) + self.prayer_times_info["Next Salat Preparation"] = self.prayer_times_info[ + "Next Salat Time" + ] - timedelta(minutes=countdown_next_prayer) _LOGGER.debug("Next salat info updated, updating sensors") async_dispatcher_send(self.hass, DATA_UPDATED) @@ -344,29 +364,28 @@ async def async_update(self, *_): return for prayer, time in prayer_times.items(): - tomorrow = (dt_util.now().date() + timedelta(days=1)).strftime("%Y-%m-%d") today = dt_util.now().date().strftime("%Y-%m-%d") now = dt_util.now().time().strftime("%H:%M") if is_date_parsing(time): - if datetime.strptime(time, '%H:%M') < datetime.strptime(now, '%H:%M'): - pray = tomorrow - else: - pray = today - - if prayer == "Jumua" or prayer == "Jumua 2": - # We convert the date to datetime to be able to do calculations on it. - pray_date = datetime.strptime(pray, "%Y-%m-%d") - # The calculation below allows to add the number of days necessary to arrive at the next Friday. - pray_date += timedelta(days=(4 - pray_date.weekday() + 7) % 7) - # We convert the date to string to be able to put it in the dictionary. - pray = pray_date.strftime("%Y-%m-%d") - - self.prayer_times_info[prayer] = dt_util.parse_datetime( - f"{pray} {time}" - ) + if datetime.strptime(time, "%H:%M") < datetime.strptime(now, "%H:%M"): + pray = tomorrow + else: + pray = today + + if prayer == "Jumua" or prayer == "Jumua 2": + # We convert the date to datetime to be able to do calculations on it. + pray_date = datetime.strptime(pray, "%Y-%m-%d") + # The calculation below allows to add the number of days necessary to arrive at the next Friday. + pray_date += timedelta(days=(4 - pray_date.weekday() + 7) % 7) + # We convert the date to string to be able to put it in the dictionary. + pray = pray_date.strftime("%Y-%m-%d") + + self.prayer_times_info[prayer] = dt_util.parse_datetime( + f"{pray} {time}" + ) else: self.prayer_times_info[prayer] = time @@ -380,11 +399,14 @@ async def async_update(self, *_): cancel_event() except AttributeError: pass + self.cancel_events_next_salat = [] for prayer in prayer_times: next_update_at = prayer + timedelta(minutes=1) - cancel_event = async_track_point_in_time(self.hass, self.async_update_next_salat_sensor, next_update_at) + cancel_event = async_track_point_in_time( + self.hass, self.async_update_next_salat_sensor, next_update_at + ) self.cancel_events_next_salat.append(cancel_event) _LOGGER.debug("New prayer times retrieved. Updating sensors.") @@ -405,7 +427,9 @@ async def async_setup(self): # We update time prayers every day. h, m, s = UPDATE_TIME - async_track_time_change(self.hass, self.async_update, hour=h, minute=m, second=s) + async_track_time_change( + self.hass, self.async_update, hour=h, minute=m, second=s + ) return True @@ -425,4 +449,3 @@ async def async_options_updated(hass, entry): if hass.data[DOMAIN].event_unsub: hass.data[DOMAIN].event_unsub() await hass.data[DOMAIN].async_update() - diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index fd01406..91cae25 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Mawaqit.""" + import logging import aiohttp import os @@ -6,14 +7,30 @@ from typing import Any import voluptuous as vol -from .const import CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME, CONF_SERVER, USERNAME, PASSWORD, CONF_UUID +from .const import ( + CONF_CALC_METHOD, + DEFAULT_CALC_METHOD, + DOMAIN, + NAME, + CONF_SERVER, + USERNAME, + PASSWORD, + CONF_UUID, +) from mawaqit.consts import BadCredentialsException, NoMosqueAround from homeassistant import config_entries from . import mawaqit_wrapper -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_PASSWORD, CONF_USERNAME, CONF_API_KEY, CONF_TOKEN +from homeassistant.const import ( + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_PASSWORD, + CONF_USERNAME, + CONF_API_KEY, + CONF_TOKEN, +) import homeassistant.helpers.config_validation as cv from homeassistant.data_entry_flow import FlowResult @@ -46,12 +63,12 @@ async def async_step_user(self, user_input=None): # if the data folder is empty, we can continue the configuration # otherwise, we abort the configuration because that means that the user has already configured an entry. - if not is_data_folder_empty(): + if is_another_instance(): return self.async_abort(reason="one_instance_allowed") if user_input is None: return await self._show_config_form(user_input=None) - + username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] @@ -64,34 +81,37 @@ async def async_step_user(self, user_input=None): return await self._show_config_form(user_input) if valid: - mawaqit_token = await mawaqit_wrapper.get_mawaqit_api_token(username, password) + mawaqit_token = await mawaqit_wrapper.get_mawaqit_api_token( + username, password + ) - text_file = open('{}/data/api.txt'.format(CURRENT_DIR), "w") + text_file = open("{}/data/api.txt".format(CURRENT_DIR), "w") text_file.write(mawaqit_token) text_file.close() try: - nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( + lat, longi, token=mawaqit_token + ) except NoMosqueAround: return self.async_abort(reason="no_mosque") - + write_all_mosques_NN_file(nearest_mosques) - + # creation of the list of mosques to be displayed in the options name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - + file_path = f"{CURRENT_DIR}/data/mosq_list_data" with open(file_path, "w+") as text_file: json.dump({"CALC_METHODS": CALC_METHODS}, text_file) return await self.async_step_mosques() - else : # (if not valid) + else: # (if not valid) self._errors["base"] = "wrong_credential" return await self._show_config_form(user_input) - async def async_step_mosques(self, user_input=None) -> FlowResult: """Handle mosques step.""" @@ -103,43 +123,53 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: mawaqit_token = get_mawaqit_token_from_file() if user_input is not None: - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - + mosque = user_input[CONF_UUID] index = name_servers.index(mosque) mosque_id = uuid_servers[index] - nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( + lat, longi, token=mawaqit_token + ) write_my_mosque_NN_file(nearest_mosques[index]) - + # the mosque chosen by user - dict_calendar = await mawaqit_wrapper.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) - - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") + dict_calendar = await mawaqit_wrapper.fetch_prayer_times( + mosque=mosque_id, token=mawaqit_token + ) + + text_file = open( + "{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w" + ) json.dump(dict_calendar, text_file) text_file.close() - title = 'MAWAQIT' + ' - ' + nearest_mosques[index]["name"] - data = {CONF_API_KEY: mawaqit_token, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} - + title = "MAWAQIT" + " - " + nearest_mosques[index]["name"] + data = { + CONF_API_KEY: mawaqit_token, + CONF_UUID: mosque_id, + CONF_LATITUDE: lat, + CONF_LONGITUDE: longi, + } + return self.async_create_entry(title=title, data=data) - + return await self._show_config_form2() - - - async def _show_config_form(self, user_input): + async def _show_config_form(self, user_input): if user_input is None: user_input = {} user_input[CONF_USERNAME] = "" user_input[CONF_PASSWORD] = "" - + schema = vol.Schema( - {vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str, - vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str} - ) + { + vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str, + vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str, + } + ) """Show the configuration form to edit location data.""" return self.async_show_form( @@ -147,8 +177,7 @@ async def _show_config_form(self, user_input): data_schema=schema, errors=self._errors, ) - - + async def _show_config_form2(self): """Show the configuration form to edit location data.""" lat = self.hass.config.latitude @@ -156,23 +185,23 @@ async def _show_config_form2(self): mawaqit_token = get_mawaqit_token_from_file() - - nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( + lat, longi, token=mawaqit_token + ) write_all_mosques_NN_file(nearest_mosques) name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - - return self.async_show_form( - step_id="mosques", - data_schema=vol.Schema( - { - vol.Required(CONF_UUID): vol.In(name_servers), - } - ), - errors=self._errors, - ) + return self.async_show_form( + step_id="mosques", + data_schema=vol.Schema( + { + vol.Required(CONF_UUID): vol.In(name_servers), + } + ), + errors=self._errors, + ) def async_get_options_flow(config_entry): """Get the options flow for this handler.""" @@ -191,9 +220,9 @@ async def async_step_init(self, user_input=None): if user_input is not None: lat = self.hass.config.latitude longi = self.hass.config.longitude - + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() - + mosque = user_input[CONF_CALC_METHOD] index = name_servers.index(mosque) mosque_id = uuid_servers[index] @@ -201,39 +230,50 @@ async def async_step_init(self, user_input=None): mawaqit_token = get_mawaqit_token_from_file() try: - nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( + lat, longi, token=mawaqit_token + ) except NoMosqueAround: raise NoMosqueAround("No mosque around.") write_my_mosque_NN_file(nearest_mosques[index]) - + # the mosque chosen by user - dict_calendar = await mawaqit_wrapper.fetch_prayer_times(mosque=mosque_id, token=mawaqit_token) - - text_file = open('{dir}/data/pray_time{name}.txt'.format(dir=CURRENT_DIR, name=""), "w") + dict_calendar = await mawaqit_wrapper.fetch_prayer_times( + mosque=mosque_id, token=mawaqit_token + ) + + text_file = open( + "{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w" + ) json.dump(dict_calendar, text_file) text_file.close() - title = 'MAWAQIT' + ' - ' + nearest_mosques[index]["name"] + title = "MAWAQIT" + " - " + nearest_mosques[index]["name"] - data = {CONF_API_KEY: mawaqit_token, CONF_UUID: mosque_id, CONF_LATITUDE: lat, CONF_LONGITUDE: longi} + data = { + CONF_API_KEY: mawaqit_token, + CONF_UUID: mosque_id, + CONF_LATITUDE: lat, + CONF_LONGITUDE: longi, + } self.hass.config_entries.async_update_entry( - self.config_entry, - title=title, - data=data + self.config_entry, title=title, data=data ) return self.config_entry - + lat = self.hass.config.latitude longi = self.hass.config.longitude mawaqit_token = get_mawaqit_token_from_file() - nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood(lat, longi, token=mawaqit_token) - + nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( + lat, longi, token=mawaqit_token + ) + write_all_mosques_NN_file(nearest_mosques) - + name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() current_mosque = read_my_mosque_NN_file()["uuid"] @@ -251,48 +291,65 @@ async def async_step_init(self, user_input=None): ): vol.In(name_servers) } - return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) + def read_all_mosques_NN_file(): name_servers = [] uuid_servers = [] CALC_METHODS = [] - with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "r") as f: + with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR), "r") as f: dict_mosques = json.load(f) for mosque in dict_mosques: distance = mosque["proximity"] - distance = distance/1000 + distance = distance / 1000 distance = round(distance, 2) - name_servers.extend([mosque["label"] + ' (' + str(distance) + 'km)']) + name_servers.extend([mosque["label"] + " (" + str(distance) + "km)"]) uuid_servers.extend([mosque["uuid"]]) CALC_METHODS.extend([mosque["label"]]) return name_servers, uuid_servers, CALC_METHODS + def write_all_mosques_NN_file(mosques): - with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), "w") as f: + with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR), "w") as f: json.dump(mosques, f) + def read_my_mosque_NN_file(): - with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), "r") as f: + with open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR), "r") as f: mosque = json.load(f) return mosque + def write_my_mosque_NN_file(mosque): - text_file = open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), "w") + text_file = open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR), "w") json.dump(mosque, text_file) text_file.close() + def create_data_folder(): - if not os.path.exists('{}/data'.format(CURRENT_DIR)): - os.makedirs('{}/data'.format(CURRENT_DIR)) + if not os.path.exists("{}/data".format(CURRENT_DIR)): + os.makedirs("{}/data".format(CURRENT_DIR)) + def get_mawaqit_token_from_file(): - f = open('{}/data/api.txt'.format(CURRENT_DIR)) + f = open("{}/data/api.txt".format(CURRENT_DIR)) mawaqit_token = f.read() f.close() return mawaqit_token + def is_data_folder_empty(): - return not os.listdir('{}/data'.format(CURRENT_DIR)) + return not os.listdir("{}/data".format(CURRENT_DIR)) + + +def is_another_instance() -> bool: + if is_data_folder_empty(): + return False + files_in_directory = os.listdir(f"{CURRENT_DIR}/data") + # Check if the data folder contains only one file named api.txt and no other files + if len(files_in_directory) == 1 and files_in_directory[0] == "api.txt": + return False + return True diff --git a/custom_components/mawaqit/manifest.json b/custom_components/mawaqit/manifest.json index e1b5b94..f2b488c 100755 --- a/custom_components/mawaqit/manifest.json +++ b/custom_components/mawaqit/manifest.json @@ -1,11 +1,17 @@ { "domain": "mawaqit", "name": "MAWAQIT", + "codeowners": [ + "@MAWAQIT", + "@mohaThr", + "@Yeyvo" + ], + "config_flow": true, "documentation": "https://github.com/mawaqit/home-assistant", - "requirements": ["mawaqit==0.0.1"], "integration_type": "hub", - "codeowners": ["@MAWAQIT", "@mohaThr"], - "config_flow": true, "iot_class": "cloud_polling", - "version": "2.0.0" -} + "requirements": [ + "mawaqit==0.0.1" + ], + "version": "2.1.0" +} \ No newline at end of file diff --git a/custom_components/mawaqit/mawaqit_wrapper.py b/custom_components/mawaqit/mawaqit_wrapper.py index b7f72ec..232edd1 100644 --- a/custom_components/mawaqit/mawaqit_wrapper.py +++ b/custom_components/mawaqit/mawaqit_wrapper.py @@ -1,55 +1,68 @@ -from mawaqit import AsyncMawaqitClient -from mawaqit.consts import BadCredentialsException, NoMosqueAround import logging -_LOGGER = logging.getLogger(__name__) - - +from mawaqit import AsyncMawaqitClient +from mawaqit.consts import BadCredentialsException + +_LOGGER = logging.getLogger(__name__) + + async def _test_credentials(username, password): """Return True if the MAWAQIT credentials is valid.""" try: client = AsyncMawaqitClient(username=username, password=password) await client.login() - await client.close() return True except BadCredentialsException: return False - + finally: + await client.close() -async def get_mawaqit_api_token( username, password): + +async def get_mawaqit_api_token(username, password): """Return the MAWAQIT API token.""" try: client = AsyncMawaqitClient(username=username, password=password) token = await client.get_api_token() - await client.close() except BadCredentialsException as e: - _LOGGER.error("Error on retrieving API Token: %s", e) - + _LOGGER.error("Error on retrieving API Token: %s", e) + finally: + await client.close() return token -async def all_mosques_neighborhood( latitude, longitude, mosque = None, username = None, password = None, token = None): +async def all_mosques_neighborhood( + latitude, longitude, mosque=None, username=None, password=None, token=None +): """Return mosques in the neighborhood if any. Returns a list of dicts.""" try: - client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + client = AsyncMawaqitClient( + latitude, longitude, mosque, username, password, token, session=None + ) await client.get_api_token() nearest_mosques = await client.all_mosques_neighborhood() - await client.close() except BadCredentialsException as e: _LOGGER.error("Error on retrieving mosques: %s", e) + finally: + await client.close() return nearest_mosques - -async def fetch_prayer_times(latitude = None, longitude = None, mosque = None, username = None, password = None, token = None): + +async def fetch_prayer_times( + latitude=None, longitude=None, mosque=None, username=None, password=None, token=None +): """Get prayer times from the MAWAQIT API. Returns a dict.""" - + try: - client = AsyncMawaqitClient(latitude, longitude, mosque, username, password, token, session=None) + client = AsyncMawaqitClient( + latitude, longitude, mosque, username, password, token, session=None + ) await client.get_api_token() dict_calendar = await client.fetch_prayer_times() - await client.close() + except BadCredentialsException as e: _LOGGER.error("Error on retrieving prayer times: %s", e) + finally: + await client.close() - return dict_calendar \ No newline at end of file + return dict_calendar diff --git a/custom_components/mawaqit/strings.json b/custom_components/mawaqit/strings.json index d6df1bd..f65b0df 100755 --- a/custom_components/mawaqit/strings.json +++ b/custom_components/mawaqit/strings.json @@ -1,9 +1,9 @@ { - "title": "MAWAQIT", + "title": "Setup MAWAQIT", "config": { "step": { "user": { - "title": "MAWAQIT", + "title": "login to MAWAQIT", "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", "data": { "username": "E-mail", @@ -11,14 +11,12 @@ "server": "Server" } }, - "mosques": { "title": "Your favourite mosque", "data": { "mosques": "Multiple mosques are available around you, select one :" } - }, - + }, "select_server": { "title": "Select your favourite mosque", "description": "Multiple mosques available, select one :", @@ -29,17 +27,14 @@ } } }, - "error": { "wrong_credential": "Wrong login or password, please retry.", "cannot_connect_to_server": "Cannot connect to the server, please retry later." }, - "abort": { "one_instance_allowed": "Une seule instance (entrée) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosquée, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante.", "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." } - }, "options": { "step": { @@ -50,4 +45,4 @@ } } } -} +} \ No newline at end of file diff --git a/custom_components/mawaqit/translations/en.json b/custom_components/mawaqit/translations/en.json index 3636796..d2db728 100755 --- a/custom_components/mawaqit/translations/en.json +++ b/custom_components/mawaqit/translations/en.json @@ -1,53 +1,48 @@ { - "title": "MAWAQIT", - "config": { - "step": { - "user": { - "title": "MAWAQIT", - "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", - "data": { - "username": "E-mail", - "password": "Password", - "server": "Server" + "config": { + "abort": { + "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood.", + "one_instance_allowed": "Une seule instance (entr\u00e9e) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosqu\u00e9e, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante." + }, + "error": { + "cannot_connect_to_server": "Cannot connect to the server, please retry later.", + "wrong_credential": "Wrong login or password, please retry." + }, + "step": { + "mosques": { + "data": { + "mosques": "Multiple mosques are available around you, select one :" + }, + "title": "Your favourite mosque" + }, + "select_server": { + "data": { + "password": "Password", + "server": "Server", + "username": "Email" + }, + "description": "Multiple mosques available, select one :", + "title": "Select your favourite mosque" + }, + "user": { + "data": { + "password": "Password", + "server": "Server", + "username": "E-mail" + }, + "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", + "title": "login to MAWAQIT" + } } - }, - - "mosques": { - "title": "Your favourite mosque", - "data": { - "mosques": "Multiple mosques are available around you, select one :" - } - }, - - "select_server": { - "title": "Select your favourite mosque", - "description": "Multiple mosques available, select one :", - "data": { - "username": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]", - "server": "Server" - } - } }, - - "error": { - "wrong_credential": "Wrong login or password, please retry.", - "cannot_connect_to_server": "Cannot connect to the server, please retry later." - }, - - "abort": { - "one_instance_allowed": "One MAWAQIT entry is enough. If you want to change your mosque, please click on the \"Configure\" button of the existing entry.", - "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." - } - - }, - "options": { - "step": { - "init": { - "data": { - "calculation_method": "Change your mosque :" + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Change your mosque :" + } + } } - } - } - } -} + }, + "title": "Setup MAWAQIT" +} \ No newline at end of file diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index 0eed5e6..6ee1948 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -10,19 +10,23 @@ import json import os import aiohttp +from tests.common import MockConfigEntry + @pytest.mark.asyncio async def test_step_user_one_instance_allowed(hass: HomeAssistant): - # test if if the data folder is not empty we abort + # test if if the data folder is not empty we abort flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass - with patch('homeassistant.components.mawaqit.config_flow.is_data_folder_empty', return_value=False): - + with patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=False, + ): result = await flow.async_step_user(None) - + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "one_instance_allowed" - + @pytest.mark.asyncio async def test_show_form_user_no_input_reopens_form(hass: HomeAssistant): @@ -52,16 +56,20 @@ async def test_async_step_user_connection_error(hass: HomeAssistant): # Create an instance of ClientConnectorError with mock arguments mock_conn_key = MagicMock() mock_os_error = MagicMock() - connection_error_instance = aiohttp.client_exceptions.ClientConnectorError(mock_conn_key, mock_os_error) - + connection_error_instance = aiohttp.client_exceptions.ClientConnectorError( + mock_conn_key, mock_os_error + ) # Patching the methods used in the flow to simulate external interactions - with patch( - "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", - side_effect=connection_error_instance, - ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + with ( + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", + side_effect=connection_error_instance, + ), + patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), ): # Simulate user input to trigger the flow's logic result = await flow.async_step_user( @@ -74,6 +82,7 @@ async def test_async_step_user_connection_error(hass: HomeAssistant): assert "base" in result["errors"] assert result["errors"]["base"] == "cannot_connect_to_server" + @pytest.mark.asyncio async def test_async_step_user_invalid_credentials(hass: HomeAssistant): """Test the user step with invalid credentials.""" @@ -81,12 +90,15 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): flow.hass = hass # Patch the credentials test to simulate a login failure - with patch( - "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", - return_value=False, - ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + with ( + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", + return_value=False, + ), + patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), ): # Simulate user input with incorrect credentials result = await flow.async_step_user( @@ -98,6 +110,7 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): assert "base" in result["errors"] assert result["errors"]["base"] == "wrong_credential" + @pytest.mark.asyncio async def test_async_step_user_valid_credentials(hass: HomeAssistant): """Test the user step with invalid credentials.""" @@ -105,18 +118,23 @@ async def test_async_step_user_valid_credentials(hass: HomeAssistant): flow.hass = hass # Patch the credentials test to simulate a login failure - with patch( - "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", - return_value=True, - ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, - ), patch( - "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", - return_value="MAWAQIT_API_TOKEN", - ), patch( - "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", - return_value={}, + with ( + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", + return_value=True, + ), + patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", + return_value="MAWAQIT_API_TOKEN", + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + return_value={}, + ), ): # Simulate user input with incorrect credentials result = await flow.async_step_user( @@ -127,6 +145,7 @@ async def test_async_step_user_valid_credentials(hass: HomeAssistant): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mosques" + @pytest.mark.asyncio async def test_async_step_user_no_neighborhood(hass: HomeAssistant): """Test the user step when no mosque is found in the neighborhood.""" @@ -134,18 +153,23 @@ async def test_async_step_user_no_neighborhood(hass: HomeAssistant): flow.hass = hass # Patching the methods used in the flow to simulate external interactions - with patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, - ), patch( - "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", - return_value=True, - ), patch( - "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", - return_value="MAWAQIT_API_TOKEN", - ), patch( - "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", - side_effect=NoMosqueAround, + with ( + patch( + "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", + return_value=True, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", + return_value=True, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", + return_value="MAWAQIT_API_TOKEN", + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + side_effect=NoMosqueAround, + ), ): # Simulate user input to trigger the flow's logic result = await flow.async_step_user( @@ -156,6 +180,7 @@ async def test_async_step_user_no_neighborhood(hass: HomeAssistant): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_mosque" + @pytest.fixture async def mock_mosques_test_data(): mock_mosques = [ @@ -198,26 +223,45 @@ async def mock_mosques_test_data(): ] mocked_mosques_data = ( - ['Mosque1-label (1.74km)', 'Mosque2-label (20.0km)', 'Mosque3-label (20.0km)'], # name_servers + [ + "Mosque1-label (1.74km)", + "Mosque2-label (20.0km)", + "Mosque3-label (20.0km)", + ], # name_servers [ "aaaaa-bbbbb-cccccc-0000", "bbbbb-cccccc-ddddd-0000", "bbbbb-cccccc-ddddd-0001", ], # uuid_servers - ['Mosque1-label', 'Mosque2-label', 'Mosque3-label'], # CALC_METHODS + ["Mosque1-label", "Mosque2-label", "Mosque3-label"], # CALC_METHODS ) return mock_mosques, mocked_mosques_data + @pytest.mark.asyncio async def test_async_step_mosques(hass, mock_mosques_test_data): mock_mosques, mocked_mosques_data = mock_mosques_test_data # Mock external dependencies - with patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file",return_value="TOKEN",), \ - patch("homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file",return_value=mocked_mosques_data,), \ - patch("homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood",return_value=mock_mosques,), \ - patch("homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times",return_value={},):#empty data + with ( + patch( + "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + return_value="TOKEN", + ), + patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + return_value=mock_mosques, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", + return_value={}, + ), + ): # empty data # Initialize the flow flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass @@ -234,9 +278,11 @@ async def test_async_step_mosques(hass, mock_mosques_test_data): assert CONF_UUID in result["data_schema"].schema # Now simulate the user selecting a mosque and submitting the form - mosque_uuid_label = mocked_mosques_data[0][0] # Assuming the user selects the first mosque + mosque_uuid_label = mocked_mosques_data[0][ + 0 + ] # Assuming the user selects the first mosque mosque_uuid = mocked_mosques_data[1][0] # uuid of the first mosque - + result = await flow.async_step_mosques({CONF_UUID: mosque_uuid_label}) # Verify the flow processes the selection correctly @@ -252,126 +298,251 @@ def mock_config_entry_setup(): # For example: entry.entry_id = "test_entry" return entry + @pytest.mark.asyncio async def test_async_get_options_flow(mock_config_entry_setup): """Test the options flow is correctly instantiated with the config entry.""" - - options_flow = config_flow.MawaqitPrayerFlowHandler.async_get_options_flow(mock_config_entry_setup) + + options_flow = config_flow.MawaqitPrayerFlowHandler.async_get_options_flow( + mock_config_entry_setup + ) # Verify that the result is an instance of the expected options flow handler assert isinstance(options_flow, config_flow.MawaqitPrayerOptionsFlowHandler) # check that the config entry is correctly passed to the handler assert options_flow.config_entry == mock_config_entry_setup + @pytest.fixture async def config_entry_setup(hass: HomeAssistant): """Create a mock config entry for tests.""" - entry = config_entries.ConfigEntry( - version=1, + entry = MockConfigEntry( + version=10, minor_version=1, domain=DOMAIN, - title='MAWAQIT - Mosque1', - data={'api_key': 'TOKEN', 'uuid': 'aaaaa-bbbbb-cccccc-0000', 'latitude': 32.87336, 'longitude': -117.22743}, + title="MAWAQIT - Mosque1", + data={ + "api_key": "TOKEN", + "uuid": "aaaaa-bbbbb-cccccc-0000", + "latitude": 32.87336, + "longitude": -117.22743, + }, source=config_entries.SOURCE_USER, ) + entry.add_to_hass(hass) # register the MockConfigEntry to Hass + return entry + @pytest.mark.asyncio -async def test_options_flow_valid_input(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): - +async def test_options_flow_valid_input( + hass: HomeAssistant, config_entry_setup, mock_mosques_test_data +): mock_mosques, mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" - with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ - patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ - patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value=mock_mosques), \ - patch("homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", return_value={}):# empty data - + with ( + patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), + patch( + "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + return_value="TOKEN", + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + return_value=mock_mosques, + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", + return_value={}, + ), + ): # empty data # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) flow.hass = hass # Assign HomeAssistant instance - # Simulate user input in the options flow - mosque_uuid_label = mocked_mosques_data[0][1] # Assuming the user selects the first mosque + mosque_uuid_label = mocked_mosques_data[0][ + 1 + ] # Assuming the user selects the first mosque mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque - #TODO chage this if we remove the create_entry line 278 - result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY # Assert that an entry is created/updated + # TODO chage this if we remove the create_entry line 278 + print("**********************33333333333333333333333333333") + result = await flow.async_step_init( + user_input={CONF_CALC_METHOD: mosque_uuid_label} + ) + print(result) + assert ( + result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + ) # Assert that an entry is created/updated # assert result["data"][CONF_UUID] == mosque_uuid + +# @pytest.mark.asyncio +# async def test_options_flow_valid_input_v2( +# hass: HomeAssistant, config_entry_setup, mock_mosques_test_data +# ): +# mock_mosques, mocked_mosques_data = mock_mosques_test_data + +# """Test the options flow.""" +# with ( +# patch( +# "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", +# return_value=mocked_mosques_data, +# ), +# patch( +# "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", +# return_value="TOKEN", +# ), +# patch( +# "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", +# return_value=mock_mosques, +# ), +# patch( +# "homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", +# return_value={}, +# ), +# ): # empty data +# # Initialize the options flow +# flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) +# flow.hass = hass # Assign HomeAssistant instance + +# # Simulate user input in the options flow +# mosque_uuid_label = mocked_mosques_data[0][ +# 1 +# ] # Assuming the user selects the first mosque +# mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque + +# # TODO chage this if we remove the create_entry line 278 +# result = await flow.async_step_init( +# user_input={CONF_CALC_METHOD: mosque_uuid_label} +# ) +# assert ( +# result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY +# ) # Assert that an entry is created/updated +# # assert result["data"][CONF_UUID] == mosque_uuid + + @pytest.mark.asyncio -async def test_options_flow_error_no_mosques_around(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): - - _ , mocked_mosques_data = mock_mosques_test_data +async def test_options_flow_error_no_mosques_around( + hass: HomeAssistant, config_entry_setup, mock_mosques_test_data +): + _, mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" - with patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ - patch("homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", return_value="TOKEN"), \ - patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', side_effect=NoMosqueAround): - - + with ( + patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), + patch( + "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + return_value="TOKEN", + ), + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + side_effect=NoMosqueAround, + ), + ): # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) flow.hass = hass # Assign HomeAssistant instance - # Simulate user input in the options flow - mosque_uuid_label = mocked_mosques_data[0][1] # Assuming the user selects the first mosque + mosque_uuid_label = mocked_mosques_data[0][ + 1 + ] # Assuming the user selects the first mosque mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque # Since we expect an exception, let's catch it to assert it happens with pytest.raises(NoMosqueAround): - result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) - #Same tests as in test_options_flow_valid_input : - #TODO chage this if we remove the create_entry line 278 - result = await flow.async_step_init(user_input={CONF_CALC_METHOD: mosque_uuid_label }) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY # Assert that an entry is created/updated + result = await flow.async_step_init( + user_input={CONF_CALC_METHOD: mosque_uuid_label} + ) + # Same tests as in test_options_flow_valid_input : + # TODO chage this if we remove the create_entry line 278 + result = await flow.async_step_init( + user_input={CONF_CALC_METHOD: mosque_uuid_label} + ) + assert ( + result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + ) # Assert that an entry is created/updated # assert result["data"][CONF_UUID] == mosque_uuid -@pytest.mark.asyncio -async def test_options_flow_no_input_reopens_form(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): +@pytest.mark.asyncio +async def test_options_flow_no_input_reopens_form( + hass: HomeAssistant, config_entry_setup, mock_mosques_test_data +): # MawaqitPrayerOptionsFlowHandler. - mock_mosques , mocked_mosques_data = mock_mosques_test_data + mock_mosques, mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" - with patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value={}), \ - patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ - patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value=mock_mosques[0]): - + with ( + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + return_value={}, + ), + patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), + patch( + "homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file", + return_value=mock_mosques[0], + ), + ): # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) flow.hass = hass # Assign HomeAssistant instance # Simulate the init step result = await flow.async_step_init(user_input=None) - assert result["type"] == data_entry_flow.FlowResultType.FORM # Assert that a form is shown + assert ( + result["type"] == data_entry_flow.FlowResultType.FORM + ) # Assert that a form is shown assert result["step_id"] == "init" -@pytest.mark.asyncio -async def test_options_flow_no_input_error_reopens_form(hass: HomeAssistant, config_entry_setup, mock_mosques_test_data): +@pytest.mark.asyncio +async def test_options_flow_no_input_error_reopens_form( + hass: HomeAssistant, config_entry_setup, mock_mosques_test_data +): # MawaqitPrayerOptionsFlowHandler. - _ , mocked_mosques_data = mock_mosques_test_data - + _, mocked_mosques_data = mock_mosques_test_data + """Test the options flow.""" - with patch('homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood', return_value={}), \ - patch('homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file', return_value=mocked_mosques_data), \ - patch('homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file', return_value={"uuid": "non_existent_uuid"}):#, \ - + with ( + patch( + "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", + return_value={}, + ), + patch( + "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", + return_value=mocked_mosques_data, + ), + patch( + "homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file", + return_value={"uuid": "non_existent_uuid"}, + ), + ): # , \ # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) flow.hass = hass # Assign HomeAssistant instance - + # Simulate the init step result = await flow.async_step_init(user_input=None) - # Same tests as test_options_flow_no_input_reopens_form : - assert result["type"] == data_entry_flow.FlowResultType.FORM # Assert that a form is shown + # Same tests as test_options_flow_no_input_reopens_form : + assert ( + result["type"] == data_entry_flow.FlowResultType.FORM + ) # Assert that a form is shown assert result["step_id"] == "init" + @pytest.fixture async def mock_data_folder(): # Utility fixture to mock os.path.exists and os.makedirs @@ -393,37 +564,47 @@ async def test_read_all_mosques_NN_file(mock_mosques_test_data): with patch("builtins.open", mock_open(read_data=json.dumps(sample_data))): assert config_flow.read_all_mosques_NN_file() == expected_output + @pytest.fixture(scope="function") def test_folder_setup(): # Define the folder name - folder_name = './test_dir' + folder_name = "./test_dir" # Create the folder os.makedirs(folder_name, exist_ok=True) # Pass the folder name to the test yield folder_name # No deletion here, handled by another fixture + @pytest.fixture(scope="function", autouse=True) def test_folder_cleanup(request, test_folder_setup): # This fixture does not need to do anything before the test, - #so it yields control immediately + # so it yields control immediately yield # Teardown: Delete the folder after the test runs folder_path = test_folder_setup # Corrected variable name + def cleanup(): if os.path.exists(folder_path): os.rmdir(folder_path) # Make sure the folder is empty before calling rmdir + request.addfinalizer(cleanup) + @pytest.mark.asyncio -async def test_write_all_mosques_NN_file(mock_file_io,test_folder_setup): +async def test_write_all_mosques_NN_file(mock_file_io, test_folder_setup): # test for write_all_mosques_NN_file - with patch('homeassistant.components.mawaqit.config_flow.CURRENT_DIR', new='./test_dir'): + with patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" + ): mosques = [{"label": "Mosque A", "uuid": "uuid1"}] config_flow.write_all_mosques_NN_file(mosques) - mock_file_io.assert_called_with(f"{test_folder_setup}/data/all_mosques_NN.txt", "w") + mock_file_io.assert_called_with( + f"{test_folder_setup}/data/all_mosques_NN.txt", "w" + ) assert mock_file_io().write.called, "The file's write method was not called." + @pytest.mark.asyncio async def test_read_my_mosque_NN_file(): # test for read_my_mosque_NN_file @@ -431,16 +612,22 @@ async def test_read_my_mosque_NN_file(): with patch("builtins.open", mock_open(read_data=json.dumps(sample_mosque))): assert config_flow.read_my_mosque_NN_file() == sample_mosque + @pytest.mark.asyncio # @patch('path.to.your.config_flow_module.CURRENT_DIR', './test_dir') -async def test_write_my_mosque_NN_file(mock_file_io,test_folder_setup): +async def test_write_my_mosque_NN_file(mock_file_io, test_folder_setup): # test for write_my_mosque_NN_file - with patch('homeassistant.components.mawaqit.config_flow.CURRENT_DIR', new='./test_dir'): + with patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" + ): mosque = {"label": "My Mosque", "uuid": "myuuid"} config_flow.write_my_mosque_NN_file(mosque) - mock_file_io.assert_called_with(f"{test_folder_setup}/data/my_mosque_NN.txt", "w") + mock_file_io.assert_called_with( + f"{test_folder_setup}/data/my_mosque_NN.txt", "w" + ) assert mock_file_io().write.called, "The file's write method was not called." + @pytest.mark.asyncio async def test_create_data_folder_already_exists(mock_data_folder): # test for create_data_folder @@ -449,6 +636,7 @@ async def test_create_data_folder_already_exists(mock_data_folder): config_flow.create_data_folder() mock_makedirs.assert_not_called() + @pytest.mark.asyncio async def test_create_data_folder_does_not_exist(mock_data_folder): mock_exists, mock_makedirs = mock_data_folder @@ -456,6 +644,7 @@ async def test_create_data_folder_does_not_exist(mock_data_folder): config_flow.create_data_folder() mock_makedirs.assert_called_once() + @pytest.mark.asyncio async def test_get_mawaqit_token_from_file(): # test for get_mawaqit_token_from_file @@ -463,16 +652,15 @@ async def test_get_mawaqit_token_from_file(): with patch("builtins.open", mock_open(read_data=token)): assert config_flow.get_mawaqit_token_from_file() == token + @pytest.mark.asyncio async def test_is_data_folder_empty_true(): # test for is_data_folder_empty with patch("os.listdir", return_value=[]): assert config_flow.is_data_folder_empty() == True + @pytest.mark.asyncio async def test_is_data_folder_empty_false(): with patch("os.listdir", return_value=["some_file.txt"]): assert config_flow.is_data_folder_empty() == False - - - \ No newline at end of file From 4bbab26b9999cb42c4ec3b0a9bedcd1c96326fac Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 06:01:37 -0700 Subject: [PATCH 24/38] multiple changes (no more api.txt, enhanced one instance logic , changed deprecated , ..) test should be rewriten later --- custom_components/mawaqit/config_flow.py | 40 +++--- custom_components/mawaqit/manifest.json | 5 +- custom_components/mawaqit/mawaqit_wrapper.py | 19 ++- custom_components/mawaqit/sensor.py | 119 +++++++++++------- .../tests/mawaqit/test_config_flow.py | 40 +++--- 5 files changed, 137 insertions(+), 86 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 91cae25..3917c5a 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -85,9 +85,10 @@ async def async_step_user(self, user_input=None): username, password ) - text_file = open("{}/data/api.txt".format(CURRENT_DIR), "w") - text_file.write(mawaqit_token) - text_file.close() + # text_file = open("{}/data/api.txt".format(CURRENT_DIR), "w") + # text_file.write(mawaqit_token) + # text_file.close() + mawaqit_wrapper.set_mawaqit_token_from_env(mawaqit_token) try: nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( @@ -120,7 +121,7 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = get_mawaqit_token_from_file() + mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() if user_input is not None: name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() @@ -183,7 +184,7 @@ async def _show_config_form2(self): lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = get_mawaqit_token_from_file() + mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( lat, longi, token=mawaqit_token @@ -227,7 +228,7 @@ async def async_step_init(self, user_input=None): index = name_servers.index(mosque) mosque_id = uuid_servers[index] - mawaqit_token = get_mawaqit_token_from_file() + mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() try: nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( @@ -266,7 +267,7 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = get_mawaqit_token_from_file() + mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( lat, longi, token=mawaqit_token @@ -334,22 +335,19 @@ def create_data_folder(): os.makedirs("{}/data".format(CURRENT_DIR)) -def get_mawaqit_token_from_file(): - f = open("{}/data/api.txt".format(CURRENT_DIR)) - mawaqit_token = f.read() - f.close() - return mawaqit_token +# def get_mawaqit_token_from_file(): +# # f = open("{}/data/api.txt".format(CURRENT_DIR)) +# # mawaqit_token = f.read() +# # f.close() +# mawaqit_token = mawaqit_wrapper.get_mawaqit_api_token() +# return mawaqit_token -def is_data_folder_empty(): - return not os.listdir("{}/data".format(CURRENT_DIR)) +def is_already_configured(): + return os.path.isfile("{}/data/my_mosque_NN.txt".format(CURRENT_DIR)) def is_another_instance() -> bool: - if is_data_folder_empty(): - return False - files_in_directory = os.listdir(f"{CURRENT_DIR}/data") - # Check if the data folder contains only one file named api.txt and no other files - if len(files_in_directory) == 1 and files_in_directory[0] == "api.txt": - return False - return True + if is_already_configured(): + return True + return False diff --git a/custom_components/mawaqit/manifest.json b/custom_components/mawaqit/manifest.json index f2b488c..af278be 100755 --- a/custom_components/mawaqit/manifest.json +++ b/custom_components/mawaqit/manifest.json @@ -3,7 +3,7 @@ "name": "MAWAQIT", "codeowners": [ "@MAWAQIT", - "@mohaThr", + "@moha-tah", "@Yeyvo" ], "config_flow": true, @@ -13,5 +13,6 @@ "requirements": [ "mawaqit==0.0.1" ], - "version": "2.1.0" + "version": "2.1.0", + "issue_tracker": "https://github.com/mawaqit/home-assistant/issues" } \ No newline at end of file diff --git a/custom_components/mawaqit/mawaqit_wrapper.py b/custom_components/mawaqit/mawaqit_wrapper.py index 232edd1..5ec840d 100644 --- a/custom_components/mawaqit/mawaqit_wrapper.py +++ b/custom_components/mawaqit/mawaqit_wrapper.py @@ -1,5 +1,5 @@ import logging - +import os from mawaqit import AsyncMawaqitClient from mawaqit.consts import BadCredentialsException @@ -13,7 +13,10 @@ async def _test_credentials(username, password): await client.login() return True except BadCredentialsException: + _LOGGER.error("Error : Bad Credentials") return False + except Exception as e: + _LOGGER.error("Error %s", e) finally: await client.close() @@ -25,6 +28,8 @@ async def get_mawaqit_api_token(username, password): token = await client.get_api_token() except BadCredentialsException as e: _LOGGER.error("Error on retrieving API Token: %s", e) + except Exception as e: + _LOGGER.error("Error %s", e) finally: await client.close() return token @@ -42,6 +47,8 @@ async def all_mosques_neighborhood( nearest_mosques = await client.all_mosques_neighborhood() except BadCredentialsException as e: _LOGGER.error("Error on retrieving mosques: %s", e) + except Exception as e: + _LOGGER.error("Error %s", e) finally: await client.close() @@ -62,7 +69,17 @@ async def fetch_prayer_times( except BadCredentialsException as e: _LOGGER.error("Error on retrieving prayer times: %s", e) + except Exception as e: + _LOGGER.error("Error %s", e) finally: await client.close() return dict_calendar + + +def get_mawaqit_token_from_env(): + return os.environ.get("MAWAQIT_API_KEY", "NA") + + +def set_mawaqit_token_from_env(mawaqit_token): + os.environ["MAWAQIT_API_KEY"] = mawaqit_token diff --git a/custom_components/mawaqit/sensor.py b/custom_components/mawaqit/sensor.py index f531b0f..330739c 100755 --- a/custom_components/mawaqit/sensor.py +++ b/custom_components/mawaqit/sensor.py @@ -1,7 +1,7 @@ """Platform to retrieve Mawaqit prayer times information for Home Assistant.""" from homeassistant.components.sensor import SensorEntity -from homeassistant.const import DEVICE_CLASS_TIMESTAMP +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.dt as dt_util @@ -24,19 +24,34 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] for sensor_type in SENSOR_TYPES: - if sensor_type in ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha", - "Jumua", "Jumua 2", #"Aid", "Aid 2", - "Fajr Iqama", "Shurouq Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama", - "Next Salat Name", "Next Salat Time", "Next Salat Preparation"]: + if sensor_type in [ + "Fajr", + "Shurouq", + "Dhuhr", + "Asr", + "Maghrib", + "Isha", + "Jumua", + "Jumua 2", # "Aid", "Aid 2", + "Fajr Iqama", + "Shurouq Iqama", + "Dhuhr Iqama", + "Asr Iqama", + "Maghrib Iqama", + "Isha Iqama", + "Next Salat Name", + "Next Salat Time", + "Next Salat Preparation", + ]: sensor = MawaqitPrayerTimeSensor(sensor_type, client) entities.append(sensor) async_add_entities(entities, True) - - name = 'My Mosque' + + name = "My Mosque" sensor1 = [MyMosqueSensor(name, hass)] async_add_entities(sensor1, True) - + class MawaqitPrayerTimeSensor(SensorEntity): """Representation of an Mawaqit prayer time sensor.""" @@ -60,34 +75,45 @@ def icon(self): """Icon to display in the front end.""" return PRAYER_TIMES_ICON - # @property - # def state(self): - # """Return the state of the sensor.""" - # return ( - # self.client.prayer_times_info.get(self.sensor_type) - # .astimezone(dt_util.UTC) - # .isoformat() - # ) + # @property + # def state(self): + # """Return the state of the sensor.""" + # return ( + # self.client.prayer_times_info.get(self.sensor_type) + # .astimezone(dt_util.UTC) + # .isoformat() + # ) @property def native_value(self): """Return the state of the sensor. .astimezone(dt_util.UTC)""" - if self.sensor_type in ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha", - "Jumua", "Jumua 2", #"Aid", "Aid 2", - "Fajr Iqama", "Shurouq Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama", - "Next Salat Time", "Next Salat Preparation"]: - + if self.sensor_type in [ + "Fajr", + "Shurouq", + "Dhuhr", + "Asr", + "Maghrib", + "Isha", + "Jumua", + "Jumua 2", # "Aid", "Aid 2", + "Fajr Iqama", + "Shurouq Iqama", + "Dhuhr Iqama", + "Asr Iqama", + "Maghrib Iqama", + "Isha Iqama", + "Next Salat Time", + "Next Salat Preparation", + ]: time = self.client.prayer_times_info.get(self.sensor_type) if time is not None: return time.astimezone(dt_util.UTC) else: return None - + else: - return ( - self.client.prayer_times_info.get(self.sensor_type) - ) - + return self.client.prayer_times_info.get(self.sensor_type) + @property def should_poll(self): """Disable polling.""" @@ -96,11 +122,25 @@ def should_poll(self): @property def device_class(self): """Return the device class.""" - if self.sensor_type in ["Fajr", "Shurouq", "Dhuhr", "Asr", "Maghrib", "Isha", - "Jumua", "Jumua 2", #"Aid", "Aid 2", - "Fajr Iqama", "Shurouq Iqama", "Dhuhr Iqama", "Asr Iqama", "Maghrib Iqama", "Isha Iqama", - "Next Salat Time", "Next Salat Preparation"]: - return DEVICE_CLASS_TIMESTAMP + if self.sensor_type in [ + "Fajr", + "Shurouq", + "Dhuhr", + "Asr", + "Maghrib", + "Isha", + "Jumua", + "Jumua 2", # "Aid", "Aid 2", + "Fajr Iqama", + "Shurouq Iqama", + "Dhuhr Iqama", + "Asr Iqama", + "Maghrib Iqama", + "Isha Iqama", + "Next Salat Time", + "Next Salat Preparation", + ]: + return SensorDeviceClass.TIMESTAMP else: return None @@ -125,37 +165,32 @@ def __init__(self, name, hass): self._latitude = latitude self._longitude = longitude - - #@Throttle(TIME_BETWEEN_UPDATES) + # @Throttle(TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data from the Mawaqit API.""" current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open('{}/data/my_mosque_NN.txt'.format(current_dir)) + f = open("{}/data/my_mosque_NN.txt".format(current_dir)) data = json.load(f) f.close() - - for (k, v) in data.items(): + + for k, v in data.items(): if str(k) != "uuid" and str(k) != "id" and str(k) != "slug": self._attributes[k] = str(v) - @property def name(self): """Return the name of the sensor.""" return self._name - @property def state(self): """Return the state of the sensor.""" - return self._attributes['name'] - + return self._attributes["name"] @property def icon(self): """Return the icon of the sensor.""" - return 'mdi:mosque' - + return "mdi:mosque" @property def extra_state_attributes(self): diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index 6ee1948..1fe465d 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -15,7 +15,7 @@ @pytest.mark.asyncio async def test_step_user_one_instance_allowed(hass: HomeAssistant): - # test if if the data folder is not empty we abort + # test if if the data folder is doesn't contain specific config file we abort flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass with patch( @@ -246,7 +246,7 @@ async def test_async_step_mosques(hass, mock_mosques_test_data): # Mock external dependencies with ( patch( - "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", return_value="TOKEN", ), patch( @@ -348,7 +348,7 @@ async def test_options_flow_valid_input( return_value=mocked_mosques_data, ), patch( - "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", return_value="TOKEN", ), patch( @@ -395,7 +395,7 @@ async def test_options_flow_valid_input( # return_value=mocked_mosques_data, # ), # patch( -# "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", +# "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", # return_value="TOKEN", # ), # patch( @@ -440,7 +440,7 @@ async def test_options_flow_error_no_mosques_around( return_value=mocked_mosques_data, ), patch( - "homeassistant.components.mawaqit.config_flow.get_mawaqit_token_from_file", + "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", return_value="TOKEN", ), patch( @@ -645,22 +645,22 @@ async def test_create_data_folder_does_not_exist(mock_data_folder): mock_makedirs.assert_called_once() -@pytest.mark.asyncio -async def test_get_mawaqit_token_from_file(): - # test for get_mawaqit_token_from_file - token = "some-token" - with patch("builtins.open", mock_open(read_data=token)): - assert config_flow.get_mawaqit_token_from_file() == token +# @pytest.mark.asyncio +# async def test_get_mawaqit_token_from_file(): +# # test for get_mawaqit_token_from_file +# token = "some-token" +# with patch("builtins.open", mock_open(read_data=token)): +# assert config_flow.get_mawaqit_token_from_file() == token -@pytest.mark.asyncio -async def test_is_data_folder_empty_true(): - # test for is_data_folder_empty - with patch("os.listdir", return_value=[]): - assert config_flow.is_data_folder_empty() == True +# @pytest.mark.asyncio +# async def test_is_data_folder_empty_true(): +# # test for is_data_folder_empty +# with patch("os.listdir", return_value=[]): +# assert config_flow.is_data_folder_empty() == True -@pytest.mark.asyncio -async def test_is_data_folder_empty_false(): - with patch("os.listdir", return_value=["some_file.txt"]): - assert config_flow.is_data_folder_empty() == False +# @pytest.mark.asyncio +# async def test_is_data_folder_empty_false(): +# with patch("os.listdir", return_value=["some_file.txt"]): +# assert config_flow.is_data_folder_empty() == False From 7fde5b88abbbddd4e60b063404b66dccef610adc Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 06:47:57 -0700 Subject: [PATCH 25/38] added mandatory files for HACS + corrected the tests --- .github/workflows/action_integration.yml | 17 +++ .github/workflows/actions.yml | 14 ++ custom_components/mawaqit/config_flow.py | 3 +- .../tests/mawaqit/test_config_flow.py | 138 ++++++++---------- hacs.json | 5 + 5 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 .github/workflows/action_integration.yml create mode 100644 .github/workflows/actions.yml create mode 100644 hacs.json diff --git a/.github/workflows/action_integration.yml b/.github/workflows/action_integration.yml new file mode 100644 index 0000000..8369e39 --- /dev/null +++ b/.github/workflows/action_integration.yml @@ -0,0 +1,17 @@ +name: HACS Action + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + hacs: + name: HACS Action + runs-on: "ubuntu-latest" + steps: + - name: HACS Action + uses: "hacs/action@main" + with: + category: "integration" \ No newline at end of file diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..0a1bd9c --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,14 @@ +name: Validate with hassfest + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + validate: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + - uses: "home-assistant/actions/hassfest@master" \ No newline at end of file diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 3917c5a..38d6f20 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -262,7 +262,8 @@ async def async_step_init(self, user_input=None): self.hass.config_entries.async_update_entry( self.config_entry, title=title, data=data ) - return self.config_entry + # return self.config_entry + return self.async_create_entry(title=None, data=None) lat = self.hass.config.latitude longi = self.hass.config.longitude diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index 1fe465d..1c42a05 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -19,8 +19,8 @@ async def test_step_user_one_instance_allowed(hass: HomeAssistant): flow = config_flow.MawaqitPrayerFlowHandler() flow.hass = hass with patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=False, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=True, ): result = await flow.async_step_user(None) @@ -36,8 +36,8 @@ async def test_show_form_user_no_input_reopens_form(hass: HomeAssistant): flow.hass = hass with patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=False, ): # Invoke the initial step of the flow without user input result = await flow.async_step_user(user_input=None) @@ -67,8 +67,8 @@ async def test_async_step_user_connection_error(hass: HomeAssistant): side_effect=connection_error_instance, ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=False, ), ): # Simulate user input to trigger the flow's logic @@ -96,8 +96,8 @@ async def test_async_step_user_invalid_credentials(hass: HomeAssistant): return_value=False, ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=False, ), ): # Simulate user input with incorrect credentials @@ -124,8 +124,8 @@ async def test_async_step_user_valid_credentials(hass: HomeAssistant): return_value=True, ), patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=False, ), patch( "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_api_token", @@ -155,8 +155,8 @@ async def test_async_step_user_no_neighborhood(hass: HomeAssistant): # Patching the methods used in the flow to simulate external interactions with ( patch( - "homeassistant.components.mawaqit.config_flow.is_data_folder_empty", - return_value=True, + "homeassistant.components.mawaqit.config_flow.is_another_instance", + return_value=False, ), patch( "homeassistant.components.mawaqit.mawaqit_wrapper._test_credentials", @@ -371,7 +371,6 @@ async def test_options_flow_valid_input( mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque # TODO chage this if we remove the create_entry line 278 - print("**********************33333333333333333333333333333") result = await flow.async_step_init( user_input={CONF_CALC_METHOD: mosque_uuid_label} ) @@ -382,51 +381,6 @@ async def test_options_flow_valid_input( # assert result["data"][CONF_UUID] == mosque_uuid -# @pytest.mark.asyncio -# async def test_options_flow_valid_input_v2( -# hass: HomeAssistant, config_entry_setup, mock_mosques_test_data -# ): -# mock_mosques, mocked_mosques_data = mock_mosques_test_data - -# """Test the options flow.""" -# with ( -# patch( -# "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", -# return_value=mocked_mosques_data, -# ), -# patch( -# "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", -# return_value="TOKEN", -# ), -# patch( -# "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", -# return_value=mock_mosques, -# ), -# patch( -# "homeassistant.components.mawaqit.mawaqit_wrapper.fetch_prayer_times", -# return_value={}, -# ), -# ): # empty data -# # Initialize the options flow -# flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) -# flow.hass = hass # Assign HomeAssistant instance - -# # Simulate user input in the options flow -# mosque_uuid_label = mocked_mosques_data[0][ -# 1 -# ] # Assuming the user selects the first mosque -# mosque_uuid = mocked_mosques_data[1][1] # uuid of the first mosque - -# # TODO chage this if we remove the create_entry line 278 -# result = await flow.async_step_init( -# user_input={CONF_CALC_METHOD: mosque_uuid_label} -# ) -# assert ( -# result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -# ) # Assert that an entry is created/updated -# # assert result["data"][CONF_UUID] == mosque_uuid - - @pytest.mark.asyncio async def test_options_flow_error_no_mosques_around( hass: HomeAssistant, config_entry_setup, mock_mosques_test_data @@ -645,22 +599,56 @@ async def test_create_data_folder_does_not_exist(mock_data_folder): mock_makedirs.assert_called_once() -# @pytest.mark.asyncio -# async def test_get_mawaqit_token_from_file(): -# # test for get_mawaqit_token_from_file -# token = "some-token" -# with patch("builtins.open", mock_open(read_data=token)): -# assert config_flow.get_mawaqit_token_from_file() == token - - -# @pytest.mark.asyncio -# async def test_is_data_folder_empty_true(): -# # test for is_data_folder_empty -# with patch("os.listdir", return_value=[]): -# assert config_flow.is_data_folder_empty() == True +@pytest.mark.usefixtures("test_folder_setup") +@pytest.mark.parametrize( + "file_exists, expected_result", + [ + (True, True), # The file exists, function should return True + (False, False), # The file does not exist, function should return False + ], +) +def test_is_already_configured(file_exists, expected_result, test_folder_setup): + with ( + patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", + new=test_folder_setup, + ), + patch( + "homeassistant.components.mawaqit.config_flow.os.path.isfile", + return_value=file_exists, + ) as mock_isfile, + ): + result = config_flow.is_already_configured() + assert result == expected_result + mock_isfile.assert_called_once_with( + f"{test_folder_setup}/data/my_mosque_NN.txt" + ) -# @pytest.mark.asyncio -# async def test_is_data_folder_empty_false(): -# with patch("os.listdir", return_value=["some_file.txt"]): -# assert config_flow.is_data_folder_empty() == False +@pytest.mark.usefixtures("test_folder_setup") +@pytest.mark.parametrize( + "configured, expected_result", + [ + ( + True, + True, + ), # is_already_configured returns True, is_another_instance should also return True + ( + False, + False, + ), # is_already_configured returns False, is_another_instance should return False + ], +) +def test_is_another_instance(configured, expected_result, test_folder_setup): + with ( + patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", + new=test_folder_setup, + ), + patch( + "homeassistant.components.mawaqit.config_flow.is_already_configured", + return_value=configured, + ), + ): + result = config_flow.is_another_instance() + assert result == expected_result diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..3de1b5f --- /dev/null +++ b/hacs.json @@ -0,0 +1,5 @@ +{ + "name": "MAWAQIT", + "render-readme": "TRUE", + "persistent_directory": "mawaqit/data" +} \ No newline at end of file From 32e89173d5c910a59f15197934551ee31eb0cd62 Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 06:55:46 -0700 Subject: [PATCH 26/38] fixed some isssues with hacs + removed some comments --- custom_components/mawaqit/config_flow.py | 8 -------- custom_components/mawaqit/manifest.json | 8 ++++---- hacs.json | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 38d6f20..67df85f 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -336,14 +336,6 @@ def create_data_folder(): os.makedirs("{}/data".format(CURRENT_DIR)) -# def get_mawaqit_token_from_file(): -# # f = open("{}/data/api.txt".format(CURRENT_DIR)) -# # mawaqit_token = f.read() -# # f.close() -# mawaqit_token = mawaqit_wrapper.get_mawaqit_api_token() -# return mawaqit_token - - def is_already_configured(): return os.path.isfile("{}/data/my_mosque_NN.txt".format(CURRENT_DIR)) diff --git a/custom_components/mawaqit/manifest.json b/custom_components/mawaqit/manifest.json index af278be..8b7888e 100755 --- a/custom_components/mawaqit/manifest.json +++ b/custom_components/mawaqit/manifest.json @@ -1,6 +1,4 @@ { - "domain": "mawaqit", - "name": "MAWAQIT", "codeowners": [ "@MAWAQIT", "@moha-tah", @@ -8,11 +6,13 @@ ], "config_flow": true, "documentation": "https://github.com/mawaqit/home-assistant", + "domain": "mawaqit", "integration_type": "hub", "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/mawaqit/home-assistant/issues", + "name": "MAWAQIT", "requirements": [ "mawaqit==0.0.1" ], - "version": "2.1.0", - "issue_tracker": "https://github.com/mawaqit/home-assistant/issues" + "version": "2.1.0" } \ No newline at end of file diff --git a/hacs.json b/hacs.json index 3de1b5f..d689603 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "MAWAQIT", - "render-readme": "TRUE", + "render_readme": "TRUE", "persistent_directory": "mawaqit/data" } \ No newline at end of file From 997889692fe4c8becf93a68c8b12b431f9f73c78 Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 06:58:57 -0700 Subject: [PATCH 27/38] fixed some isssues with hacs --- custom_components/mawaqit/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mawaqit/manifest.json b/custom_components/mawaqit/manifest.json index 8b7888e..97cfcd5 100755 --- a/custom_components/mawaqit/manifest.json +++ b/custom_components/mawaqit/manifest.json @@ -1,4 +1,6 @@ { + "domain": "mawaqit", + "name": "MAWAQIT", "codeowners": [ "@MAWAQIT", "@moha-tah", @@ -6,11 +8,9 @@ ], "config_flow": true, "documentation": "https://github.com/mawaqit/home-assistant", - "domain": "mawaqit", "integration_type": "hub", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/mawaqit/home-assistant/issues", - "name": "MAWAQIT", "requirements": [ "mawaqit==0.0.1" ], From 6ad4879638dc3cc52d47c1a752bdba737d6cec90 Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 07:03:26 -0700 Subject: [PATCH 28/38] fixed some isssues with hacs --- hacs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hacs.json b/hacs.json index d689603..71d0955 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "MAWAQIT", - "render_readme": "TRUE", + "render_readme": true, "persistent_directory": "mawaqit/data" } \ No newline at end of file From b3faf44236b7dadcc341cb103d7ec339e6ac4585 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:23:52 +0200 Subject: [PATCH 29/38] Update config_flow.py Fixes #59 --- custom_components/mawaqit/config_flow.py | 130 +++++++++++++---------- 1 file changed, 74 insertions(+), 56 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index 67df85f..fd112a0 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -85,10 +85,10 @@ async def async_step_user(self, user_input=None): username, password ) - # text_file = open("{}/data/api.txt".format(CURRENT_DIR), "w") - # text_file.write(mawaqit_token) - # text_file.close() - mawaqit_wrapper.set_mawaqit_token_from_env(mawaqit_token) + #text_file = open("{}/data/.env".format(CURRENT_DIR), "w") + #text_file.write("MAWAQIT_API_KEY=" + mawaqit_token) + #text_file.close() + os.environ['MAWAQIT_API_KEY'] = mawaqit_token try: nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( @@ -97,10 +97,10 @@ async def async_step_user(self, user_input=None): except NoMosqueAround: return self.async_abort(reason="no_mosque") - write_all_mosques_NN_file(nearest_mosques) + await write_all_mosques_NN_file(nearest_mosques, self.hass) # creation of the list of mosques to be displayed in the options - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) file_path = f"{CURRENT_DIR}/data/mosq_list_data" with open(file_path, "w+") as text_file: @@ -121,10 +121,10 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() + mawaqit_token = get_mawaqit_token_from_file() if user_input is not None: - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) mosque = user_input[CONF_UUID] index = name_servers.index(mosque) @@ -134,7 +134,7 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: lat, longi, token=mawaqit_token ) - write_my_mosque_NN_file(nearest_mosques[index]) + await write_my_mosque_NN_file(nearest_mosques[index], self.hass) # the mosque chosen by user dict_calendar = await mawaqit_wrapper.fetch_prayer_times( @@ -184,15 +184,15 @@ async def _show_config_form2(self): lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() + mawaqit_token = get_mawaqit_token_from_file() nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( lat, longi, token=mawaqit_token ) - write_all_mosques_NN_file(nearest_mosques) + await write_all_mosques_NN_file(nearest_mosques, self.hass) - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) return self.async_show_form( step_id="mosques", @@ -222,13 +222,13 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) mosque = user_input[CONF_CALC_METHOD] index = name_servers.index(mosque) mosque_id = uuid_servers[index] - mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() + mawaqit_token = get_mawaqit_token_from_file() try: nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( @@ -237,8 +237,8 @@ async def async_step_init(self, user_input=None): except NoMosqueAround: raise NoMosqueAround("No mosque around.") - write_my_mosque_NN_file(nearest_mosques[index]) - + await write_my_mosque_NN_file(nearest_mosques[index], self.hass) + # the mosque chosen by user dict_calendar = await mawaqit_wrapper.fetch_prayer_times( mosque=mosque_id, token=mawaqit_token @@ -262,23 +262,22 @@ async def async_step_init(self, user_input=None): self.hass.config_entries.async_update_entry( self.config_entry, title=title, data=data ) - # return self.config_entry - return self.async_create_entry(title=None, data=None) + return self.config_entry lat = self.hass.config.latitude longi = self.hass.config.longitude - mawaqit_token = mawaqit_wrapper.get_mawaqit_token_from_env() + mawaqit_token = get_mawaqit_token_from_file() nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( lat, longi, token=mawaqit_token ) - write_all_mosques_NN_file(nearest_mosques) + await write_all_mosques_NN_file(nearest_mosques, self.hass) - name_servers, uuid_servers, CALC_METHODS = read_all_mosques_NN_file() + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) - current_mosque = read_my_mosque_NN_file()["uuid"] + current_mosque = await read_my_mosque_NN_file(self.hass)["uuid"] try: index = uuid_servers.index(current_mosque) @@ -296,39 +295,53 @@ async def async_step_init(self, user_input=None): return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) -def read_all_mosques_NN_file(): - name_servers = [] - uuid_servers = [] - CALC_METHODS = [] - - with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR), "r") as f: - dict_mosques = json.load(f) - for mosque in dict_mosques: - distance = mosque["proximity"] - distance = distance / 1000 - distance = round(distance, 2) - name_servers.extend([mosque["label"] + " (" + str(distance) + "km)"]) - uuid_servers.extend([mosque["uuid"]]) - CALC_METHODS.extend([mosque["label"]]) - - return name_servers, uuid_servers, CALC_METHODS - - -def write_all_mosques_NN_file(mosques): - with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR), "w") as f: - json.dump(mosques, f) - - -def read_my_mosque_NN_file(): - with open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR), "r") as f: - mosque = json.load(f) - return mosque - - -def write_my_mosque_NN_file(mosque): - text_file = open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR), "w") - json.dump(mosque, text_file) - text_file.close() +async def read_all_mosques_NN_file(hass): + + def read(): + name_servers = [] + uuid_servers = [] + CALC_METHODS = [] + + with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR)) as f: + dict_mosques = json.load(f) + for mosque in dict_mosques: + distance = mosque["proximity"] + distance = distance / 1000 + distance = round(distance, 2) + name_servers.extend([mosque["label"] + " (" + str(distance) + "km)"]) + uuid_servers.extend([mosque["uuid"]]) + CALC_METHODS.extend([mosque["label"]]) + + return name_servers, uuid_servers, CALC_METHODS + + return await hass.async_add_executor_job(read) + + +async def write_all_mosques_NN_file(mosques, hass): + + def write(): + with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), 'w') as f: + json.dump(mosques, f) + + return await hass.async_add_executor_job(write) + + +async def read_my_mosque_NN_file(hass): + + def read(): + with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR)) as f: + mosque = json.load(f) + return mosque + + return await hass.async_add_executor_job(read) + + +async def write_my_mosque_NN_file(mosque, hass): + def write(): + with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), 'w') as f: + json.dump(mosque, f) + + return await hass.async_add_executor_job(write) def create_data_folder(): @@ -336,6 +349,10 @@ def create_data_folder(): os.makedirs("{}/data".format(CURRENT_DIR)) +def get_mawaqit_token_from_file(): + return os.environ.get('MAWAQIT_API_KEY', 'NA') + + def is_already_configured(): return os.path.isfile("{}/data/my_mosque_NN.txt".format(CURRENT_DIR)) @@ -343,4 +360,5 @@ def is_already_configured(): def is_another_instance() -> bool: if is_already_configured(): return True - return False + else: + return False From 0cb487251a745a055a060b4a8a15983b30450879 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:21:51 +0200 Subject: [PATCH 30/38] Update strings.json --- custom_components/mawaqit/strings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/mawaqit/strings.json b/custom_components/mawaqit/strings.json index f65b0df..167085a 100755 --- a/custom_components/mawaqit/strings.json +++ b/custom_components/mawaqit/strings.json @@ -31,8 +31,8 @@ "wrong_credential": "Wrong login or password, please retry.", "cannot_connect_to_server": "Cannot connect to the server, please retry later." }, - "abort": { - "one_instance_allowed": "Une seule instance (entrée) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosquée, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante.", + "abort": { + "one_instance_allowed": "A single MAWAQIT entry is necessary. If you want to reconfigure your mosque, you can click on \"Configure\" next to the existing entry.", "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." } }, @@ -45,4 +45,4 @@ } } } -} \ No newline at end of file +} From 4ba44e86bf4f099067a8e5da672bd3dcf7bb5ebf Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:22:50 +0200 Subject: [PATCH 31/38] Update en.json --- custom_components/mawaqit/translations/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mawaqit/translations/en.json b/custom_components/mawaqit/translations/en.json index d2db728..fd9c47d 100755 --- a/custom_components/mawaqit/translations/en.json +++ b/custom_components/mawaqit/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood.", - "one_instance_allowed": "Une seule instance (entr\u00e9e) MAWAQIT est suffisante. Si vous souhaitez modifier votre mosqu\u00e9e, veuillez cliquer sur le bouton \"Configurer\" de l'instance existante." + "one_instance_allowed": "A single MAWAQIT entry is necessary. If you want to reconfigure your mosque, you can click on \"Configure\" next to the existing entry." }, "error": { "cannot_connect_to_server": "Cannot connect to the server, please retry later.", @@ -45,4 +45,4 @@ } }, "title": "Setup MAWAQIT" -} \ No newline at end of file +} From c25f3e7c38f5e1a2e12cf45d8987de71d1c80292 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:24:14 +0200 Subject: [PATCH 32/38] Delete custom_components/mawaqit/strings.json --- custom_components/mawaqit/strings.json | 48 -------------------------- 1 file changed, 48 deletions(-) delete mode 100755 custom_components/mawaqit/strings.json diff --git a/custom_components/mawaqit/strings.json b/custom_components/mawaqit/strings.json deleted file mode 100755 index 167085a..0000000 --- a/custom_components/mawaqit/strings.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "title": "Setup MAWAQIT", - "config": { - "step": { - "user": { - "title": "login to MAWAQIT", - "description": "Register on https://mawaqit.net/ and connect below. The 5 nearest mosques to your location will be suggested and you will choose your favourite one.", - "data": { - "username": "E-mail", - "password": "Password", - "server": "Server" - } - }, - "mosques": { - "title": "Your favourite mosque", - "data": { - "mosques": "Multiple mosques are available around you, select one :" - } - }, - "select_server": { - "title": "Select your favourite mosque", - "description": "Multiple mosques available, select one :", - "data": { - "username": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]", - "server": "Server" - } - } - }, - "error": { - "wrong_credential": "Wrong login or password, please retry.", - "cannot_connect_to_server": "Cannot connect to the server, please retry later." - }, - "abort": { - "one_instance_allowed": "A single MAWAQIT entry is necessary. If you want to reconfigure your mosque, you can click on \"Configure\" next to the existing entry.", - "no_mosque": "Sorry, there's no MAWAQIT mosque in your neighborhood." - } - }, - "options": { - "step": { - "init": { - "data": { - "calculation_method": "Change your mosque :" - } - } - } - } -} From 8bfd838b53f256a5e624f5deab8f1757bc61f6f0 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:25:54 +0200 Subject: [PATCH 33/38] Update .gitignore and remove strings.json --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 88c2c83..1dd7897 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ custom_components/mawaqit/data -custom_components/mawaqit/translations/* custom_components/mawaqit/test.py custom_components/mawaqit/strings.json custom_components/.DS_Store From 0506d2a853d9e04fa3dbb443f237b3ed1d6d5617 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:50:29 +0200 Subject: [PATCH 34/38] Update config_flow.py --- custom_components/mawaqit/config_flow.py | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index fd112a0..e39dc4b 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -85,9 +85,6 @@ async def async_step_user(self, user_input=None): username, password ) - #text_file = open("{}/data/.env".format(CURRENT_DIR), "w") - #text_file.write("MAWAQIT_API_KEY=" + mawaqit_token) - #text_file.close() os.environ['MAWAQIT_API_KEY'] = mawaqit_token try: @@ -103,8 +100,12 @@ async def async_step_user(self, user_input=None): name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) file_path = f"{CURRENT_DIR}/data/mosq_list_data" - with open(file_path, "w+") as text_file: - json.dump({"CALC_METHODS": CALC_METHODS}, text_file) + + def write(): + with open(file_path, "w+") as text_file: + json.dump({"CALC_METHODS": CALC_METHODS}, text_file) + + await self.hass.async_add_executor_job(write) return await self.async_step_mosques() @@ -141,11 +142,11 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: mosque=mosque_id, token=mawaqit_token ) - text_file = open( - "{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w" - ) - json.dump(dict_calendar, text_file) - text_file.close() + def write(): + with open("{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w") as f: + json.dump(dict_calendar, f) + + await self.hass.async_add_executor_job(write) title = "MAWAQIT" + " - " + nearest_mosques[index]["name"] data = { @@ -244,11 +245,11 @@ async def async_step_init(self, user_input=None): mosque=mosque_id, token=mawaqit_token ) - text_file = open( - "{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w" - ) - json.dump(dict_calendar, text_file) - text_file.close() + def write(): + with open("{dir}/data/pray_time{name}.txt".format(dir=CURRENT_DIR, name=""), "w") as f: + json.dump(dict_calendar, f) + + await self.hass.async_add_executor_job(write) title = "MAWAQIT" + " - " + nearest_mosques[index]["name"] From 270aa4378a67a75af229eb4802feee3dd2771dd5 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:50:57 +0200 Subject: [PATCH 35/38] Update sensor.py Fixes #59 --- custom_components/mawaqit/sensor.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/custom_components/mawaqit/sensor.py b/custom_components/mawaqit/sensor.py index 330739c..c30498a 100755 --- a/custom_components/mawaqit/sensor.py +++ b/custom_components/mawaqit/sensor.py @@ -169,9 +169,13 @@ def __init__(self, name, hass): async def async_update(self): """Get the latest data from the Mawaqit API.""" current_dir = os.path.dirname(os.path.realpath(__file__)) - f = open("{}/data/my_mosque_NN.txt".format(current_dir)) - data = json.load(f) - f.close() + + def read(): + with open("{}/data/my_mosque_NN.txt".format(current_dir), "r") as f: + data = json.load(f) + return data + + data = read() for k, v in data.items(): if str(k) != "uuid" and str(k) != "id" and str(k) != "slug": From 42feb05cab795b77c1865dc4f697326c522d1dd9 Mon Sep 17 00:00:00 2001 From: Mohamed Tahiri <98562658+moha-tah@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:55:21 +0200 Subject: [PATCH 36/38] Update sensor.py --- custom_components/mawaqit/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mawaqit/sensor.py b/custom_components/mawaqit/sensor.py index c30498a..4272fde 100755 --- a/custom_components/mawaqit/sensor.py +++ b/custom_components/mawaqit/sensor.py @@ -175,7 +175,7 @@ def read(): data = json.load(f) return data - data = read() + data = await self.hass.async_add_executor_job(read) for k, v in data.items(): if str(k) != "uuid" and str(k) != "id" and str(k) != "slug": From e9c09b2377890f8f6f356f8832746d3761979c24 Mon Sep 17 00:00:00 2001 From: yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:43:26 -0700 Subject: [PATCH 37/38] restructure code + readapted some test --- custom_components/mawaqit/config_flow.py | 99 ++++------- custom_components/mawaqit/utils.py | 55 ++++++ .../tests/mawaqit/test_config_flow.py | 156 ++++++------------ custom_components/tests/mawaqit/test_utils.py | 105 ++++++++++++ 4 files changed, 245 insertions(+), 170 deletions(-) create mode 100644 custom_components/mawaqit/utils.py create mode 100644 custom_components/tests/mawaqit/test_utils.py diff --git a/custom_components/mawaqit/config_flow.py b/custom_components/mawaqit/config_flow.py index fd112a0..7cb0d0a 100755 --- a/custom_components/mawaqit/config_flow.py +++ b/custom_components/mawaqit/config_flow.py @@ -6,6 +6,13 @@ import json from typing import Any import voluptuous as vol +from .utils import ( + write_all_mosques_NN_file, + read_my_mosque_NN_file, + write_my_mosque_NN_file, + create_data_folder, + read_all_mosques_NN_file, +) from .const import ( CONF_CALC_METHOD, @@ -85,10 +92,10 @@ async def async_step_user(self, user_input=None): username, password ) - #text_file = open("{}/data/.env".format(CURRENT_DIR), "w") - #text_file.write("MAWAQIT_API_KEY=" + mawaqit_token) - #text_file.close() - os.environ['MAWAQIT_API_KEY'] = mawaqit_token + # text_file = open("{}/data/.env".format(CURRENT_DIR), "w") + # text_file.write("MAWAQIT_API_KEY=" + mawaqit_token) + # text_file.close() + os.environ["MAWAQIT_API_KEY"] = mawaqit_token try: nearest_mosques = await mawaqit_wrapper.all_mosques_neighborhood( @@ -100,7 +107,9 @@ async def async_step_user(self, user_input=None): await write_all_mosques_NN_file(nearest_mosques, self.hass) # creation of the list of mosques to be displayed in the options - name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file( + self.hass + ) file_path = f"{CURRENT_DIR}/data/mosq_list_data" with open(file_path, "w+") as text_file: @@ -124,7 +133,9 @@ async def async_step_mosques(self, user_input=None) -> FlowResult: mawaqit_token = get_mawaqit_token_from_file() if user_input is not None: - name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file( + self.hass + ) mosque = user_input[CONF_UUID] index = name_servers.index(mosque) @@ -192,7 +203,9 @@ async def _show_config_form2(self): await write_all_mosques_NN_file(nearest_mosques, self.hass) - name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file( + self.hass + ) return self.async_show_form( step_id="mosques", @@ -222,7 +235,9 @@ async def async_step_init(self, user_input=None): lat = self.hass.config.latitude longi = self.hass.config.longitude - name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file( + self.hass + ) mosque = user_input[CONF_CALC_METHOD] index = name_servers.index(mosque) @@ -238,7 +253,7 @@ async def async_step_init(self, user_input=None): raise NoMosqueAround("No mosque around.") await write_my_mosque_NN_file(nearest_mosques[index], self.hass) - + # the mosque chosen by user dict_calendar = await mawaqit_wrapper.fetch_prayer_times( mosque=mosque_id, token=mawaqit_token @@ -262,7 +277,8 @@ async def async_step_init(self, user_input=None): self.hass.config_entries.async_update_entry( self.config_entry, title=title, data=data ) - return self.config_entry + # return self.config_entry + return self.async_create_entry(title=None, data=None) lat = self.hass.config.latitude longi = self.hass.config.longitude @@ -275,9 +291,12 @@ async def async_step_init(self, user_input=None): await write_all_mosques_NN_file(nearest_mosques, self.hass) - name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file(self.hass) + name_servers, uuid_servers, CALC_METHODS = await read_all_mosques_NN_file( + self.hass + ) - current_mosque = await read_my_mosque_NN_file(self.hass)["uuid"] + current_mosque_data = await read_my_mosque_NN_file(self.hass) + current_mosque = current_mosque_data["uuid"] try: index = uuid_servers.index(current_mosque) @@ -295,62 +314,8 @@ async def async_step_init(self, user_input=None): return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) -async def read_all_mosques_NN_file(hass): - - def read(): - name_servers = [] - uuid_servers = [] - CALC_METHODS = [] - - with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR)) as f: - dict_mosques = json.load(f) - for mosque in dict_mosques: - distance = mosque["proximity"] - distance = distance / 1000 - distance = round(distance, 2) - name_servers.extend([mosque["label"] + " (" + str(distance) + "km)"]) - uuid_servers.extend([mosque["uuid"]]) - CALC_METHODS.extend([mosque["label"]]) - - return name_servers, uuid_servers, CALC_METHODS - - return await hass.async_add_executor_job(read) - - -async def write_all_mosques_NN_file(mosques, hass): - - def write(): - with open('{}/data/all_mosques_NN.txt'.format(CURRENT_DIR), 'w') as f: - json.dump(mosques, f) - - return await hass.async_add_executor_job(write) - - -async def read_my_mosque_NN_file(hass): - - def read(): - with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR)) as f: - mosque = json.load(f) - return mosque - - return await hass.async_add_executor_job(read) - - -async def write_my_mosque_NN_file(mosque, hass): - def write(): - with open('{}/data/my_mosque_NN.txt'.format(CURRENT_DIR), 'w') as f: - json.dump(mosque, f) - - return await hass.async_add_executor_job(write) - - -def create_data_folder(): - if not os.path.exists("{}/data".format(CURRENT_DIR)): - os.makedirs("{}/data".format(CURRENT_DIR)) - - def get_mawaqit_token_from_file(): - return os.environ.get('MAWAQIT_API_KEY', 'NA') + return os.environ.get("MAWAQIT_API_KEY", "NA") def is_already_configured(): diff --git a/custom_components/mawaqit/utils.py b/custom_components/mawaqit/utils.py new file mode 100644 index 0000000..ef9b769 --- /dev/null +++ b/custom_components/mawaqit/utils.py @@ -0,0 +1,55 @@ +import json +import os + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) + + +async def write_all_mosques_NN_file(mosques, hass): + def write(): + with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR), "w") as f: + json.dump(mosques, f) + + await hass.async_add_executor_job(write) + + +async def read_my_mosque_NN_file(hass): + def read(): + with open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR)) as f: + mosque = json.load(f) + return mosque + + return await hass.async_add_executor_job(read) + + +async def write_my_mosque_NN_file(mosque, hass): + def write(): + with open("{}/data/my_mosque_NN.txt".format(CURRENT_DIR), "w") as f: + json.dump(mosque, f) + + await hass.async_add_executor_job(write) + + +def create_data_folder(): + if not os.path.exists("{}/data".format(CURRENT_DIR)): + os.makedirs("{}/data".format(CURRENT_DIR)) + + +async def read_all_mosques_NN_file(hass): + def read(): + name_servers = [] + uuid_servers = [] + CALC_METHODS = [] + + with open("{}/data/all_mosques_NN.txt".format(CURRENT_DIR)) as f: + dict_mosques = json.load(f) + for mosque in dict_mosques: + distance = mosque["proximity"] + distance = distance / 1000 + distance = round(distance, 2) + name_servers.extend([mosque["label"] + " (" + str(distance) + "km)"]) + uuid_servers.extend([mosque["uuid"]]) + CALC_METHODS.extend([mosque["label"]]) + + return name_servers, uuid_servers, CALC_METHODS + + return await hass.async_add_executor_job(read) diff --git a/custom_components/tests/mawaqit/test_config_flow.py b/custom_components/tests/mawaqit/test_config_flow.py index 1c42a05..e229b7a 100644 --- a/custom_components/tests/mawaqit/test_config_flow.py +++ b/custom_components/tests/mawaqit/test_config_flow.py @@ -1,4 +1,4 @@ -from unittest.mock import patch, Mock, MagicMock, mock_open +from unittest.mock import patch, Mock, MagicMock, mock_open, AsyncMock import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.core import HomeAssistant @@ -13,6 +13,40 @@ from tests.common import MockConfigEntry +@pytest.fixture +async def mock_data_folder(): + # Utility fixture to mock os.path.exists and os.makedirs + with patch("os.path.exists") as mock_exists: + with patch("os.makedirs") as mock_makedirs: + yield mock_exists, mock_makedirs + + +@pytest.fixture(scope="function") +def test_folder_setup(): + # Define the folder name + folder_name = "./test_dir" + # Create the folder + os.makedirs(folder_name, exist_ok=True) + # Pass the folder name to the test + yield folder_name + # No deletion here, handled by another fixture + + +@pytest.fixture(scope="function", autouse=True) +def test_folder_cleanup(request, test_folder_setup): + # This fixture does not need to do anything before the test, + # so it yields control immediately + yield + # Teardown: Delete the folder after the test runs + folder_path = test_folder_setup # Corrected variable name + + def cleanup(): + if os.path.exists(folder_path): + os.rmdir(folder_path) # Make sure the folder is empty before calling rmdir + + request.addfinalizer(cleanup) + + @pytest.mark.asyncio async def test_step_user_one_instance_allowed(hass: HomeAssistant): # test if if the data folder is doesn't contain specific config file we abort @@ -239,12 +273,17 @@ async def mock_mosques_test_data(): return mock_mosques, mocked_mosques_data +# @pytest.mark.usefixtures("test_folder_setup") @pytest.mark.asyncio async def test_async_step_mosques(hass, mock_mosques_test_data): mock_mosques, mocked_mosques_data = mock_mosques_test_data # Mock external dependencies with ( + patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", + new=test_folder_setup, + ), patch( "homeassistant.components.mawaqit.mawaqit_wrapper.get_mawaqit_token_from_env", return_value="TOKEN", @@ -381,14 +420,19 @@ async def test_options_flow_valid_input( # assert result["data"][CONF_UUID] == mosque_uuid +@pytest.mark.usefixtures("test_folder_setup") @pytest.mark.asyncio async def test_options_flow_error_no_mosques_around( - hass: HomeAssistant, config_entry_setup, mock_mosques_test_data + hass: HomeAssistant, config_entry_setup, mock_mosques_test_data, test_folder_setup ): _, mocked_mosques_data = mock_mosques_test_data """Test the options flow.""" with ( + patch( + "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", + new=test_folder_setup, + ), patch( "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", return_value=mocked_mosques_data, @@ -439,15 +483,15 @@ async def test_options_flow_no_input_reopens_form( with ( patch( "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", - return_value={}, + return_value={} ), patch( "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", - return_value=mocked_mosques_data, + return_value=mocked_mosques_data ), patch( "homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file", - return_value=mock_mosques[0], + return_value=mock_mosques[0] ), ): # Initialize the options flow @@ -473,17 +517,17 @@ async def test_options_flow_no_input_error_reopens_form( with ( patch( "homeassistant.components.mawaqit.mawaqit_wrapper.all_mosques_neighborhood", - return_value={}, + return_value={} ), patch( "homeassistant.components.mawaqit.config_flow.read_all_mosques_NN_file", - return_value=mocked_mosques_data, + return_value=mocked_mosques_data ), patch( "homeassistant.components.mawaqit.config_flow.read_my_mosque_NN_file", - return_value={"uuid": "non_existent_uuid"}, + return_value={"uuid": "non_existent_uuid"} ), - ): # , \ + ): # Initialize the options flow flow = config_flow.MawaqitPrayerOptionsFlowHandler(config_entry_setup) flow.hass = hass # Assign HomeAssistant instance @@ -497,100 +541,6 @@ async def test_options_flow_no_input_error_reopens_form( assert result["step_id"] == "init" -@pytest.fixture -async def mock_data_folder(): - # Utility fixture to mock os.path.exists and os.makedirs - with patch("os.path.exists") as mock_exists: - with patch("os.makedirs") as mock_makedirs: - yield mock_exists, mock_makedirs - - -@pytest.fixture -async def mock_file_io(): - # Utility fixture for mocking open - with patch("builtins.open", mock_open()) as mocked_file: - yield mocked_file - - -@pytest.mark.asyncio -async def test_read_all_mosques_NN_file(mock_mosques_test_data): - sample_data, expected_output = mock_mosques_test_data - with patch("builtins.open", mock_open(read_data=json.dumps(sample_data))): - assert config_flow.read_all_mosques_NN_file() == expected_output - - -@pytest.fixture(scope="function") -def test_folder_setup(): - # Define the folder name - folder_name = "./test_dir" - # Create the folder - os.makedirs(folder_name, exist_ok=True) - # Pass the folder name to the test - yield folder_name - # No deletion here, handled by another fixture - - -@pytest.fixture(scope="function", autouse=True) -def test_folder_cleanup(request, test_folder_setup): - # This fixture does not need to do anything before the test, - # so it yields control immediately - yield - # Teardown: Delete the folder after the test runs - folder_path = test_folder_setup # Corrected variable name - - def cleanup(): - if os.path.exists(folder_path): - os.rmdir(folder_path) # Make sure the folder is empty before calling rmdir - - request.addfinalizer(cleanup) - - -@pytest.mark.asyncio -async def test_write_all_mosques_NN_file(mock_file_io, test_folder_setup): - # test for write_all_mosques_NN_file - with patch( - "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" - ): - mosques = [{"label": "Mosque A", "uuid": "uuid1"}] - config_flow.write_all_mosques_NN_file(mosques) - mock_file_io.assert_called_with( - f"{test_folder_setup}/data/all_mosques_NN.txt", "w" - ) - assert mock_file_io().write.called, "The file's write method was not called." - - -@pytest.mark.asyncio -async def test_read_my_mosque_NN_file(): - # test for read_my_mosque_NN_file - sample_mosque = {"label": "My Mosque", "uuid": "myuuid"} - with patch("builtins.open", mock_open(read_data=json.dumps(sample_mosque))): - assert config_flow.read_my_mosque_NN_file() == sample_mosque - - -@pytest.mark.asyncio -# @patch('path.to.your.config_flow_module.CURRENT_DIR', './test_dir') -async def test_write_my_mosque_NN_file(mock_file_io, test_folder_setup): - # test for write_my_mosque_NN_file - with patch( - "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" - ): - mosque = {"label": "My Mosque", "uuid": "myuuid"} - config_flow.write_my_mosque_NN_file(mosque) - mock_file_io.assert_called_with( - f"{test_folder_setup}/data/my_mosque_NN.txt", "w" - ) - assert mock_file_io().write.called, "The file's write method was not called." - - -@pytest.mark.asyncio -async def test_create_data_folder_already_exists(mock_data_folder): - # test for create_data_folder - mock_exists, mock_makedirs = mock_data_folder - mock_exists.return_value = True - config_flow.create_data_folder() - mock_makedirs.assert_not_called() - - @pytest.mark.asyncio async def test_create_data_folder_does_not_exist(mock_data_folder): mock_exists, mock_makedirs = mock_data_folder diff --git a/custom_components/tests/mawaqit/test_utils.py b/custom_components/tests/mawaqit/test_utils.py new file mode 100644 index 0000000..fbfb31f --- /dev/null +++ b/custom_components/tests/mawaqit/test_utils.py @@ -0,0 +1,105 @@ +# from unittest.mock import patch, Mock, MagicMock, mock_open +# import pytest +# import json +# import os +# import aiohttp +# from tests.common import MockConfigEntry +# from homeassistant.components.mawaqit import config_flow +# @pytest.mark.asyncio +# async def test_read_all_mosques_NN_file(mock_mosques_test_data): +# sample_data, expected_output = mock_mosques_test_data +# with patch("builtins.open", mock_open(read_data=json.dumps(sample_data))): +# assert config_flow.read_all_mosques_NN_file() == expected_output + + +# @pytest.fixture(scope="function") +# def test_folder_setup(): +# # Define the folder name +# folder_name = "./test_dir" +# # Create the folder +# os.makedirs(folder_name, exist_ok=True) +# # Pass the folder name to the test +# yield folder_name +# # No deletion here, handled by another fixture + + +# @pytest.fixture(scope="function", autouse=True) +# def test_folder_cleanup(request, test_folder_setup): +# # This fixture does not need to do anything before the test, +# # so it yields control immediately +# yield +# # Teardown: Delete the folder after the test runs +# folder_path = test_folder_setup # Corrected variable name + +# def cleanup(): +# if os.path.exists(folder_path): +# os.rmdir(folder_path) # Make sure the folder is empty before calling rmdir + +# request.addfinalizer(cleanup) + +# @pytest.fixture +# async def mock_file_io(): +# # Utility fixture for mocking open +# with patch("builtins.open", mock_open()) as mocked_file: +# yield mocked_file + +# @pytest.mark.asyncio +# async def test_write_all_mosques_NN_file(mock_file_io, test_folder_setup): +# # test for write_all_mosques_NN_file +# with patch( +# "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" +# ): +# mosques = [{"label": "Mosque A", "uuid": "uuid1"}] +# config_flow.write_all_mosques_NN_file(mosques) +# mock_file_io.assert_called_with( +# f"{test_folder_setup}/data/all_mosques_NN.txt", "w" +# ) +# assert mock_file_io().write.called, "The file's write method was not called." + + +# @pytest.mark.asyncio +# async def test_read_my_mosque_NN_file(hass): +# # Sample data to be returned by the mock +# sample_mosque = {"label": "My Mosque", "uuid": "myuuid"} +# mock_file_data = json.dumps(sample_mosque) + +# test_dir = "./test_dir" +# expected_file_path = f"{test_dir}/data/my_mosque_NN.txt" + +# with ( +# patch("builtins.open", mock_open(read_data=mock_file_data)) as mock_file, +# patch( +# "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", +# new=f"{test_dir}", +# ), +# ): +# # Call the function to be tested +# result = await config_flow.read_my_mosque_NN_file(hass) +# # Assert the file was opened correctly +# mock_file.assert_called_once_with(expected_file_path) +# # Verify the function returns the correct result +# assert result == sample_mosque + + +# @pytest.mark.asyncio +# # @patch('path.to.your.config_flow_module.CURRENT_DIR', './test_dir') +# async def test_write_my_mosque_NN_file(mock_file_io, test_folder_setup): +# # test for write_my_mosque_NN_file +# with patch( +# "homeassistant.components.mawaqit.config_flow.CURRENT_DIR", new="./test_dir" +# ): +# mosque = {"label": "My Mosque", "uuid": "myuuid"} +# config_flow.write_my_mosque_NN_file(mosque) +# mock_file_io.assert_called_with( +# f"{test_folder_setup}/data/my_mosque_NN.txt", "w" +# ) +# assert mock_file_io().write.called, "The file's write method was not called." + + +# @pytest.mark.asyncio +# async def test_create_data_folder_already_exists(mock_data_folder): +# # test for create_data_folder +# mock_exists, mock_makedirs = mock_data_folder +# mock_exists.return_value = True +# config_flow.create_data_folder() +# mock_makedirs.assert_not_called() From 87342cb94a015da4564dbcb1f954e06449aa3847 Mon Sep 17 00:00:00 2001 From: Yeyvo <20130066+Yeyvo@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:23:59 +0200 Subject: [PATCH 38/38] rename action to hassfest.yaml --- .github/workflows/{actions.yml => hassfest.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{actions.yml => hassfest.yaml} (100%) diff --git a/.github/workflows/actions.yml b/.github/workflows/hassfest.yaml similarity index 100% rename from .github/workflows/actions.yml rename to .github/workflows/hassfest.yaml