def setup(hass, config): """Set up the Meteo-France component.""" hass.data[DATA_METEO_FRANCE] = {} # Check if at least weather alert have to be monitored for one location. need_weather_alert_watcher = False for location in config[DOMAIN]: if CONF_MONITORED_CONDITIONS in location \ and 'weather_alert' in location[CONF_MONITORED_CONDITIONS]: need_weather_alert_watcher = True # If weather alert monitoring is expected initiate a client to be used by # all weather_alert entities. if need_weather_alert_watcher: from vigilancemeteo import VigilanceMeteoFranceProxy, \ VigilanceMeteoError weather_alert_client = VigilanceMeteoFranceProxy() try: weather_alert_client.update_data() except VigilanceMeteoError as exp: _LOGGER.error(exp) else: weather_alert_client = None hass.data[DATA_METEO_FRANCE]['weather_alert_client'] = weather_alert_client for location in config[DOMAIN]: city = location[CONF_CITY] from meteofrance.client import meteofranceClient, meteofranceError try: client = meteofranceClient(city) except meteofranceError as exp: _LOGGER.error(exp) return client.need_rain_forecast = bool( CONF_MONITORED_CONDITIONS in location and 'next_rain' in location[CONF_MONITORED_CONDITIONS]) hass.data[DATA_METEO_FRANCE][city] = MeteoFranceUpdater(client) hass.data[DATA_METEO_FRANCE][city].update() if CONF_MONITORED_CONDITIONS in location: monitored_conditions = location[CONF_MONITORED_CONDITIONS] _LOGGER.debug("meteo_france sensor platfrom loaded for %s", city) load_platform(hass, 'sensor', DOMAIN, { CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions }, config) load_platform(hass, 'weather', DOMAIN, {CONF_CITY: city}, config) return True
def test_first_try_xml_unreachable(fix_local_data): """Test behaviour when first checksum is unvailable.""" client = VigilanceMeteoFranceProxy() # fake the cheksum file to simulate unreachable file client.URL_VIGILANCE_METEO_XML = "./tests/fake_xml.xml" # should raise an error with pytest.raises(VigilanceMeteoError): client.update_data()
def test_basic(): """Basic test.""" client = VigilanceMeteoFranceProxy() # first update client.update_data() test_status_1 = client.status == UPDATE_STATUS_XML_UPDATED # second update should do nothing as less than 60 secondes after first update client.update_data() test_status_2 = client.status == UPDATE_STATUS_CHECKSUM_CACHED_60S # simulation 2 minutes wait client._latest_check_date = client._latest_check_date - datetime.timedelta( seconds=120) client.update_data() test_status_3 = client.status == UPDATE_STATUS_SAME_CHECKSUM test_xml_tree = client.xml_tree is not None assert (test_status_1, test_status_2, test_status_3, test_xml_tree) == ( True, True, True, True, )
def test_results_when_proxy_raise_error(msg_format): """Test behaviour when Error are raised by proxy.""" client = VigilanceMeteoFranceProxy() # fake the cheksum file to simulate unreachable file client.URL_VIGILANCE_METEO_CHECKSUM = "file:./tests/fake_file.txt" zone = DepartmentWeatherAlert("2A", client) if msg_format == "text": excpected_result = "Impossible de récupérer l'information." else: # msg_format == "html" to avoid partial in codecov excpected_result = "<p>Impossible de récupérer l'information.</p>" assert zone.summary_message(msg_format) == excpected_result
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up an Meteo-France account from a config entry.""" hass.data.setdefault(DOMAIN, {}) # Weather alert weather_alert_client = VigilanceMeteoFranceProxy() try: await hass.async_add_executor_job(weather_alert_client.update_data) except VigilanceMeteoError as exp: _LOGGER.error( "Unexpected error when creating the vigilance_meteoFrance proxy: %s ", exp) return False hass.data[DOMAIN]["weather_alert_client"] = weather_alert_client # Weather city = entry.data[CONF_CITY] try: client = await hass.async_add_executor_job(meteofranceClient, city) except meteofranceError as exp: _LOGGER.error( "Unexpected error when creating the meteofrance proxy: %s", exp) return False hass.data[DOMAIN][city] = MeteoFranceUpdater(client) await hass.async_add_executor_job(hass.data[DOMAIN][city].update) for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform)) _LOGGER.debug("meteo_france sensor platform loaded for %s", city) return True
def test_functional(): """Functional test""" client = VigilanceMeteoFranceProxy() zone = DepartmentWeatherAlert("32", client) # Test the forecast update date and time. It should be near today. test_date = (timezone("UTC").localize(datetime.datetime.utcnow()) - zone.bulletin_date) < datetime.timedelta(days=1) # Test if the URL url_pour_en_savoir_plus is available test_url = urlopen(zone.additional_info_URL).getcode() == 200 # Test to check if there is a overall criticity color for the department test_color = zone.department_color in ALERT_COLOR_LIST # Test the synthesis message test_summary = zone.summary_message is not None # Test the client used is the one given in argument at creation test_client = client == zone.proxy assert (test_date, test_url, test_color, test_summary, test_client) == ( True, True, True, True, True, )
def test_xml_unreachable_and_bulletin_expired(fix_local_data): """Test checksum behaviour in case of error.""" # First update is OK client = VigilanceMeteoFranceProxy() client.update_data() # fake the cheksum file to simulate new checksum file client.URL_VIGILANCE_METEO_CHECKSUM = "file:./tests/vigilance_controle_2.txt" # fake the xml file to simulate unreachable xml file client.URL_VIGILANCE_METEO_XML = "./tests/fake_xml.xml" # fake the date of bulletin. Make it exipred client._bulletin_date = client._bulletin_date - datetime.timedelta(days=2) # simulate 2 minutes wait client._latest_check_date = client._latest_check_date - datetime.timedelta( seconds=120) # should raise an error with pytest.raises(VigilanceMeteoError): client.update_data()
def __init__(self, department, vmf_proxy=None): """Class instance constructor. 2 arguments expected: - The department (Required) number as a 2 character String. Can be between 01 and 95, 2A, 2B or 99 (for Andorre). - a VigilanceMeteoFranceProxy object (Optional) to manage de communication with the Météo France online source. """ # Variables init self._alerts_list = {} self._department = None # If no VigilanceMeteoFranceProxy set in the parameter create a new one. if vmf_proxy is not None: self._viglance_MF_proxy = vmf_proxy else: self._viglance_MF_proxy = VigilanceMeteoFranceProxy() # Check _department variable using the property. # Warning the setter launch update_department_status() methods. self.department = department
def test_xml_unreachable_and_bulletin_valid(): """Test behaviour when checksum URL unreachable""" # First update OK client = VigilanceMeteoFranceProxy() client.update_data() first_xml_tree = client.xml_tree # fake the cheksum file to simulate new checksum file client.URL_VIGILANCE_METEO_CHECKSUM = "file:./tests/vigilance_controle_2.txt" # fake the xml file to simulate unreachable xml file client.URL_VIGILANCE_METEO_XML = "./tests/fake_xml.xml" # simulate 2 minutes wait client._latest_check_date = client._latest_check_date - datetime.timedelta( seconds=120) client.update_data() # Should be no error and the value of the first update checksum assert (client.xml_tree, client.status) == ( first_xml_tree, UPDATE_STATUS_ERROR_BUT_PREVIOUS_BULLETIN_VALID, )
def setup(hass, config): """Set up the Meteo-France component.""" hass.data[DATA_METEO_FRANCE] = {} # Check if at least weather alert have to be monitored for one location. need_weather_alert_watcher = False for location in config[DOMAIN]: if ( CONF_MONITORED_CONDITIONS in location and "weather_alert" in location[CONF_MONITORED_CONDITIONS] ): need_weather_alert_watcher = True # If weather alert monitoring is expected initiate a client to be used by # all weather_alert entities. if need_weather_alert_watcher: _LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo") weather_alert_client = VigilanceMeteoFranceProxy() try: weather_alert_client.update_data() except VigilanceMeteoError as exp: _LOGGER.error( "Unexpected error when creating the vigilance_meteoFrance proxy: %s ", exp, ) else: weather_alert_client = None hass.data[DATA_METEO_FRANCE]["weather_alert_client"] = weather_alert_client for location in config[DOMAIN]: city = location[CONF_CITY] try: client = meteofranceClient(city) except meteofranceError as exp: _LOGGER.error( "Unexpected error when creating the meteofrance proxy: %s", exp ) return client.need_rain_forecast = bool( CONF_MONITORED_CONDITIONS in location and "next_rain" in location[CONF_MONITORED_CONDITIONS] ) hass.data[DATA_METEO_FRANCE][city] = MeteoFranceUpdater(client) hass.data[DATA_METEO_FRANCE][city].update() if CONF_MONITORED_CONDITIONS in location: monitored_conditions = location[CONF_MONITORED_CONDITIONS] _LOGGER.debug("meteo_france sensor platform loaded for %s", city) load_platform( hass, "sensor", DOMAIN, {CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions}, config, ) load_platform(hass, "weather", DOMAIN, {CONF_CITY: city}, config) return True
def test_checksum(fix_local_data): """Test checksum property.""" client = VigilanceMeteoFranceProxy() client.update_data() assert client.checksum == "1751354976"
def test_bulletin_date(fix_local_data): """Test bulletin date property.""" client = VigilanceMeteoFranceProxy() client.update_data() assert client.bulletin_date.isoformat() == "2018-03-18T16:00:00+01:00"
class DepartmentWeatherAlert(object): """A Class to descripe French departments weather alerts. Data are fetch on vigilance.meteofrance.com website. Public attributes from DepartmentWeatherAlert class - department_color: return the overall criticity color for the department - additional_info_URL: return the URL to access more information about department weather alerts from the MétéoFrance website. - bulletin_date: return latest bulletin update date & time with timezone - department: Get or set the department number corresponding to the area watched. - alerts_list: return the list of all alert types. - proxy: return the client (Class VigilanceMeteoFranceProxy) used by the object Methods from DepartmentWeatherAlert class: - update_department_status(): update alerts list by feching latest info from MétéoFrance forcast. - summary_message(format): return a string with textual synthesis of the active alerts in department. According to value of 'format' parameter, the string return change: 'text' (default) or 'html' """ def __init__(self, department, vmf_proxy=None): """Class instance constructor. 2 arguments expected: - The department (Required) number as a 2 character String. Can be between 01 and 95, 2A, 2B or 99 (for Andorre). - a VigilanceMeteoFranceProxy object (Optional) to manage de communication with the Météo France online source. """ # Variables init self._alerts_list = {} self._department = None # If no VigilanceMeteoFranceProxy set in the parameter create a new one. if vmf_proxy is not None: self._viglance_MF_proxy = vmf_proxy else: self._viglance_MF_proxy = VigilanceMeteoFranceProxy() # Check _department variable using the property. # Warning the setter launch update_department_status() methods. self.department = department def update_department_status(self): """Fetch active weather alerts for the department. get the alert color for the 9 different types on the Météo France website and update the variable 'alerts_list'. """ try: self._alerts_list = self._viglance_MF_proxy.get_alert_list( self.department) except VigilanceMeteoError: self._alerts_list = {} def __repr__(self): """"instance representation""" # Order the dictionary keys because before python 3.6 keys are not # ordered alerts_list_ordonnee = "" for key in sorted(self.alerts_list.keys()): alerts_list_ordonnee = alerts_list_ordonnee + "'{}': '{}', ".format( key, self.alerts_list[key]) return ( "DepartmentWeatherAlert: \n - department: '{}'\n - bulletin_date: '{}'" "\n - alerts_list: {{{}}}".format(self._department, self.bulletin_date, alerts_list_ordonnee[:-2])) @property def department_color(self): """Get the department color. It's the color of the most critical alert. """ if any(alert == "Rouge" for alert in self.alerts_list.values()): synthesis = "Rouge" elif any(alert == "Orange" for alert in self.alerts_list.values()): synthesis = "Orange" elif any(alert == "Jaune" for alert in self.alerts_list.values()): synthesis = "Jaune" elif self.alerts_list and all(alert == "Vert" for alert in self.alerts_list.values()): synthesis = "Vert" else: synthesis = None return synthesis @property def additional_info_URL(self): """Get the link to have additional info about alerts in department. Return the vigilance.meteofrance.com URL to get additinonal details about active alerts in the department. """ return ("http://vigilance.meteofrance.com/" "Bulletin_sans.html?a=dept{}&b=1&c=".format(self._department)) def summary_message(self, msg_format="text"): """Get synthesis text message to have the list of the active alerts. msg_format parameter can be 'text' or 'html'. """ if msg_format == "text": if self.department_color == "Vert": message = "Aucune alerte météo en cours." elif self.department_color is None: message = "Impossible de récupérer l'information." else: message = "Alerte météo {} en cours :".format( self.department_color) # Order the dictionary keys because before python 3.6 keys are # not ordered for type_risque in sorted(self.alerts_list.keys()): if self.alerts_list[type_risque] != "Vert": message = message + "\n - {}: {}".format( type_risque, self.alerts_list[type_risque]) elif msg_format == "html": if self.department_color == "Vert": message = "<p>Aucune alerte météo en cours.</p>" elif self.department_color is None: message = "<p>Impossible de récupérer l'information.</p>" else: message = "<p>Alerte météo {} en cours :" "</p><ul>".format( self.department_color) # Order the dictionary keys ecause before python 3.6 keys are # not ordered for type_risque in sorted(self.alerts_list.keys()): if self.alerts_list[type_risque] != "Vert": message = message + "<li>{}: {}</li>".format( type_risque, self.alerts_list[type_risque]) message = message + "</ul>" else: raise ValueError( "msg_format of summary_message() only method accept 'text' or 'html' values. " "Used value: {}".format(msg_format)) return message @property def bulletin_date(self): """Accessor and setter for bulletin update date""" return self._viglance_MF_proxy.bulletin_date @property def alerts_list(self): """Accessor and setter for weather alerts list""" return self._alerts_list @property def department(self): """Accessor for area code linked to the weather report""" return self._department @property def proxy(self): """Accessor for proxy used by the class""" return self._viglance_MF_proxy @department.setter def department(self, department): """Setter with consitency check on the are code value. Departemnt variable should be a 2 chararcters string. In the source XML file, the 92, 93 and 95 departments do not exist. In this case we have to use the 75 department instead. This setter will call the update_department_status() method systematicaly. """ # Check the valide values for department if department not in VALID_DEPARTMENT_LIST: raise ValueError( "Department parameter have to be a 2 characters string" "between '01' and '95' or '2A' or '2B' or '99'." "Used value: {}".format(department)) # Equivalences list validated_department = department # Replace, the missing department in the XLM by the equivalence. if department in EQUIVALENCE_75: validated_department = "75" # Set the variable self._department = validated_department # Call the first update self.update_department_status()