예제 #1
0
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
예제 #5
0
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()
예제 #8
0
    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,
    )
예제 #10
0
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"
예제 #13
0
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()