Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warning dictionary API #682

Merged
merged 2 commits into from Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/meteofrance_api/client.py
Expand Up @@ -13,6 +13,7 @@
from .model import PictureOfTheDay
from .model import Place
from .model import Rain
from .model import WarningDictionary
from .session import MeteoFranceSession

# TODO: investigate bulletincote, montagne, etc...
Expand Down Expand Up @@ -305,6 +306,29 @@ def get_warning_thumbnail(self, domain: str = "france") -> str:
f"&domain={domain}"
)

def get_warning_dictionary(self, language: str = "fr") -> WarningDictionary:
"""Retrieves the meteorological dictionary from the Météo-France API.

This dictionary includes information about various meteorological
phenomena and color codes used for weather warnings.

Args:
language (str): The language in which to retrieve the
dictionary data. Default is 'fr' for French. Other language codes
can be used if supported by the API.

Returns:
WarningDictionary: An object containing structured data about
meteorological phenomena and warning color codes. It has two main
attributes: 'phenomenons' (list of PhenomenonDictionaryEntry) and
'colors' (list of ColorDictionaryEntry).
"""
resp = self.session.request(
"get", "v3/warning/dictionary", params={"lang": language}
)
dictionary = WarningDictionary(resp.json())
return dictionary

#
# Picture of the day
#
Expand Down
3 changes: 3 additions & 0 deletions src/meteofrance_api/model/__init__.py
@@ -1,4 +1,5 @@
"""Météo-France models for the REST API."""
from .dictionary import WarningDictionary
from .forecast import Forecast
from .observation import Observation
from .picture_of_the_day import PictureOfTheDay
Expand All @@ -7,6 +8,7 @@
from .warning import CurrentPhenomenons
from .warning import Full


__all__ = [
"Forecast",
"Observation",
Expand All @@ -15,4 +17,5 @@
"Rain",
"CurrentPhenomenons",
"Full",
"WarningDictionary",
]
120 changes: 120 additions & 0 deletions src/meteofrance_api/model/dictionary.py
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""Dictionary Python model for the Météo-France REST API."""
from typing import List
from typing import Optional
from typing import TypedDict


class PhenomenonDictionaryEntry(TypedDict):
"""Represents a single meteorological phenomenon entry.

Attributes:
id: An integer representing the unique identifier of the phenomenon.
name: A string representing the name of the phenomenon.
"""

id: int
name: str


class ColorDictionaryEntry(TypedDict):
"""Represents a single color entry used in meteorological warnings.

Attributes:
id: An integer representing the unique identifier of the color.
level: An integer representing the severity level associated with the color.
name: A string representing the name of the color.
hexaCode: A string representing the hexadecimal code of the color.
"""

id: int
level: int
name: str
hexaCode: str # noqa: N815


class WarningDisctionaryData(TypedDict):
Quentame marked this conversation as resolved.
Show resolved Hide resolved
"""Structured data representing the meteorological dictionary.

Attributes:
phenomenons: A list of PhenomenonDictionaryEntry instances.
colors: A list of ColorDictionaryEntry instances.
"""

phenomenons: List[PhenomenonDictionaryEntry]
colors: List[ColorDictionaryEntry]


class WarningDictionary:
"""A class to represent and manipulate the Meteo France meteorological dictionary data.

Methods:
get_phenomenon_name_by_id(phenomenon_id: int): Returns the name of the phenomenon for the given ID.
get_color_name_by_id(color_id: int): Returns the name of the color for the given ID.
"""

def __init__(self, raw_data: WarningDisctionaryData) -> None:
"""Initializes the WarningDictionary with raw dictionary data.

Args:
raw_data: A dictionary representing the JSON response from the Meteo France API.
"""
self.raw_data = raw_data

def get_phenomenon_by_id(
self, phenomenon_id: int
) -> Optional[PhenomenonDictionaryEntry]:
"""Retrieves a meteorological phenomenon based on its ID.

Args:
phenomenon_id: The ID of the meteorological phenomenon.

Returns:
The phenomenon if found, otherwise returns None.
"""
for phenomenon in self.raw_data["phenomenons"]:
if phenomenon["id"] == phenomenon_id:
return phenomenon
return None

def get_phenomenon_name_by_id(self, phenomenon_id: int) -> Optional[str]:
"""Retrieves the name of a meteorological phenomenon based on its ID.

Args:
phenomenon_id: The ID of the meteorological phenomenon.

Returns:
The name of the phenomenon if found, otherwise returns None.
"""
phenomenon = self.get_phenomenon_by_id(phenomenon_id)
if phenomenon is not None:
return phenomenon["name"]
return None

def get_color_by_id(self, color_id: int) -> Optional[ColorDictionaryEntry]:
"""Retrieves a warning color based on its ID.

Args:
color_id: The ID of the color.

Returns:
The the color object if found, otherwise returns None.
"""
for color in self.raw_data["colors"]:
if color["id"] == color_id:
return color
return None

def get_color_name_by_id(self, color_id: int) -> Optional[str]:
"""Retrieves the name of a warning color based on its ID.

Args:
color_id: The ID of the color.

Returns:
The name of the color if found, otherwise returns None.
"""
color = self.get_color_by_id(color_id)
if color is not None:
return color["name"]
return None
94 changes: 94 additions & 0 deletions tests/test_warning_dictionary.py
@@ -0,0 +1,94 @@
# coding: utf-8
"""Tests for WarningDictionary class in the Météo-France module."""
import pytest

from meteofrance_api import MeteoFranceClient
from meteofrance_api.model import WarningDictionary
from meteofrance_api.model.dictionary import WarningDisctionaryData


class TestWarningDictionary:
"""Tests for WarningDictionary class in the Météo-France module."""

@pytest.fixture
def sample_dictionary_data(self) -> WarningDisctionaryData:
"""Provide sample data for WarningDictionary."""
return {
"phenomenons": [
{"id": 1, "name": "Wind"},
{"id": 2, "name": "Rain-flood"},
{"id": 3, "name": "Thunderstorms"},
{"id": 4, "name": "Flood"},
{"id": 5, "name": "Snow-ice"},
{"id": 6, "name": "Heat wave"},
{"id": 7, "name": "Extreme Cold"},
{"id": 8, "name": "Avalanche"},
{"id": 9, "name": "Waves-flooding"},
],
"colors": [
{"id": 1, "level": 1, "name": "green", "hexaCode": "#31aa35"},
{"id": 2, "level": 2, "name": "yellow", "hexaCode": "#fff600"},
{"id": 3, "level": 3, "name": "orange", "hexaCode": "#ffb82b"},
{"id": 4, "level": 4, "name": "red", "hexaCode": "#CC0000"},
],
}

def test_dictionary(self) -> None:
"""Test dictionary."""
client = MeteoFranceClient()

dictionary = client.get_warning_dictionary()

assert isinstance(dictionary, WarningDictionary)
color = dictionary.get_color_name_by_id(1)
assert isinstance(color, str)
phenomenon = dictionary.get_phenomenon_name_by_id(1)
assert isinstance(phenomenon, str)

def test_get_phenomenon_by_id(
self, sample_dictionary_data: WarningDisctionaryData
) -> None:
"""Test get_phenomenon_by_id method."""
dictionary = WarningDictionary(sample_dictionary_data)

phenomenon = dictionary.get_phenomenon_by_id(1)
assert phenomenon == {"id": 1, "name": "Wind"}

# Test for non-existing ID
assert dictionary.get_phenomenon_by_id(99) is None

def test_get_phenomenon_name_by_id(
self, sample_dictionary_data: WarningDisctionaryData
) -> None:
"""Test get_phenomenon_name_by_id method."""
dictionary = WarningDictionary(sample_dictionary_data)

name = dictionary.get_phenomenon_name_by_id(2)
assert name == "Rain-flood"

# Test for non-existing ID
assert dictionary.get_phenomenon_name_by_id(99) is None

def test_get_color_by_id(
self, sample_dictionary_data: WarningDisctionaryData
) -> None:
"""Test get_color_by_id method."""
dictionary = WarningDictionary(sample_dictionary_data)

color = dictionary.get_color_by_id(2)
assert color == {"id": 2, "level": 2, "name": "yellow", "hexaCode": "#fff600"}

# Test for non-existing ID
assert dictionary.get_color_by_id(99) is None

def test_get_color_name_by_id(
self, sample_dictionary_data: WarningDisctionaryData
) -> None:
"""Test get_color_name_by_id method."""
dictionary = WarningDictionary(sample_dictionary_data)

name = dictionary.get_color_name_by_id(1)
assert name == "green"

# Test for non-existing ID
assert dictionary.get_color_name_by_id(99) is None