Skip to content

Commit

Permalink
Release 1.2.0 (#562)
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentame authored Feb 22, 2023
1 parent 41a68f7 commit 688078e
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 112 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
select = B,B9,C,D,DAR,E,F,N,RST,S,W
ignore = E203,E501,RST201,RST203,RST301,W503
max-line-length = 80
max-line-length = 100
max-complexity = 10
docstring-convention = google
per-file-ignores = tests/*:S101
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "meteofrance-api"
version = "1.1.0"
version = "1.2.0"
description = "Python client for Météo-France API."
authors = ["oncleben31 <oncleben31@gmail.com>", "quentame <polletquentin74@me.com>", "HACF <contact@hacf.fr>"]
license = "MIT"
Expand Down
10 changes: 4 additions & 6 deletions src/meteofrance_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from .const import METEOFRANCE_API_TOKEN
from .const import METEOFRANCE_API_URL
from .model import CurrentPhenomenons
from .model import Observation
from .model import Forecast
from .model import Full
from .model import Observation
from .model import PictureOfTheDay
from .model import Place
from .model import Rain
Expand Down Expand Up @@ -74,10 +74,9 @@ def search_places(
resp = self.session.request("get", "places", params=params)
return [Place(place_data) for place_data in resp.json()]


#
# Observation
#
#
def get_observation(
self,
latitude: float,
Expand All @@ -102,10 +101,10 @@ def get_observation(
resp = self.session.request(
"get",
"v2/observation",
params={"lat": latitude, "lon": longitude, "lang": language})
params={"lat": latitude, "lon": longitude, "lang": language},
)
return Observation(resp.json())


def get_observation_for_place(
self,
place: Place,
Expand All @@ -125,7 +124,6 @@ def get_observation_for_place(
"""
return self.get_observation(place.latitude, place.longitude, language)


#
# Forecast
#
Expand Down
10 changes: 9 additions & 1 deletion src/meteofrance_api/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@
from .warning import CurrentPhenomenons
from .warning import Full

__all__ = ["Forecast", "Observation", "Place", "PictureOfTheDay", "Rain", "CurrentPhenomenons", "Full"]
__all__ = [
"Forecast",
"Observation",
"Place",
"PictureOfTheDay",
"Rain",
"CurrentPhenomenons",
"Full",
]
207 changes: 114 additions & 93 deletions src/meteofrance_api/model/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,110 +2,131 @@
"""Weather observation Python model for the Météo-France REST API."""
import sys
from datetime import datetime
from typing import Any
from typing import Dict
from typing import Optional

if sys.version_info >= (3, 8):
from typing import TypedDict # pylint: disable=no-name-in-module
from typing import TypedDict # pylint: disable=no-name-in-module
else:
from typing_extensions import TypedDict
from typing_extensions import TypedDict


class ObservationDataPropertiesGridded(TypedDict, total=True):
time: str
T: float
wind_speed: float
wind_direction: int
wind_icon: str
weather_icon: str
weather_description: str
class ObservationDataPropertiesGridded(TypedDict, total=False):
"""Data structure of the observation gridded properties object from the REST API."""

time: str
T: float
wind_speed: float
wind_direction: int
wind_icon: str
weather_icon: str
weather_description: str


class ObservationDataProperties(TypedDict, total=False):
timezone: str
gridded: ObservationDataPropertiesGridded
"""Data structure of the observation properties object from the REST API."""

timezone: str
gridded: ObservationDataPropertiesGridded


class ObservationData(TypedDict, total=False):
"""Describing the data structure of the observation object returned by the REST API."""

properties: ObservationDataProperties
"""Data structure of the observation object from the REST API."""

update_time: str
geometry: Dict[str, Any]
type: str
properties: ObservationDataProperties


class Observation:
"""Class to access the results of an `observation` API request.
Attributes:
timezone: The observation timezone
time: The time at which the observation was made
temperature: The observed temperature (°C)
wind_speed: The observed wind speed (km/h)
wind_direction: The observed wind direction (°)
wind_icon: An icon ID illustrating the observed wind direction
weather_icon: An icon ID illustrating the observed weather condition
weather_description: A description of the observed weather condition
"""

def __init__(self, raw_data: ObservationData) -> None:
"""Initialize an Observation object.
Args:
raw_data: A dictionary representing the JSON response from 'observation' REST
API request. The structure is described by the ObservationData class.
"""
self.properties = raw_data.get('properties', dict())

@property
def timezone(self) -> str:
"""Returns the observation timezone"""
return self.properties.get('timezone')

@property
def _gridded(self) -> ObservationDataPropertiesGridded:
"""Returns the observation gridded properties"""
return self.properties.get('gridded', dict())

@property
def time_as_string(self) -> str:
"""Returns the time at which the observation was made"""
return self._gridded.get('time')

@property
def time_as_datetime(self) -> str:
"""Returns the time at which the observation was made"""
time = self.time_as_string
return None if time is None else datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%f%z')

@property
def temperature(self) -> float:
"""Returns the observed temperature (°C)"""
return self._gridded.get('T')

@property
def wind_speed(self) -> float:
"""Returns the observed wind speed (km/h)"""
return self._gridded.get('wind_speed')

@property
def wind_direction(self) -> int:
"""Returns the observed wind direction (°)"""
return self._gridded.get('wind_direction')

@property
def wind_icon(self) -> str:
"""Returns an icon ID illustrating the observed wind direction"""
return self._gridded.get('wind_icon')

@property
def weather_icon(self) -> str:
"""Returns an icon ID illustrating the observed weather condition"""
return self._gridded.get('weather_icon')

@property
def weather_description(self) -> str:
"""Returns a description of the observed weather condition"""
return self._gridded.get('weather_description')

def __repr__(self) -> str:
"""Returns a stringified version of the object"""
return f"Observation(timezone={self.timezone}, time={self.time}, temperature={self.temperature}°C, "\
f"wind_speed={self.wind_speed} km/h, wind_direction={self.wind_direction}°, wind_icon={self.wind_icon}, "\
f"weather_icon={self.weather_icon}, weather_description={self.weather_description}"
"""Class to access the results of an `observation` API request.
Attributes:
timezone: The observation timezone
time: The time at which the observation was made
temperature: The observed temperature (°C)
wind_speed: The observed wind speed (km/h)
wind_direction: The observed wind direction (°)
wind_icon: An icon ID illustrating the observed wind direction
weather_icon: An icon ID illustrating the observed weather condition
weather_description: A description of the observed weather condition
"""

def __init__(self, raw_data: ObservationData) -> None:
"""Initialize an Observation object.
Args:
raw_data: A dictionary representing the JSON response from 'observation' REST
API request. The structure is described by the ObservationData class.
"""
self.properties = raw_data.get("properties", {})

@property
def timezone(self) -> Optional[str]:
"""Returns the observation timezone."""
return self.properties.get("timezone")

@property
def _gridded(self) -> ObservationDataPropertiesGridded:
"""Returns the observation gridded properties."""
return self.properties.get("gridded", {})

@property
def time_as_string(self) -> Optional[str]:
"""Returns the time at which the observation was made."""
return self._gridded.get("time")

@property
def time_as_datetime(self) -> Optional[datetime]:
"""Returns the time at which the observation was made."""
time = self.time_as_string
return (
None if time is None else datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%f%z")
)

@property
def temperature(self) -> Optional[float]:
"""Returns the observed temp (°C)."""
return self._gridded.get("T")

@property
def wind_speed(self) -> Optional[float]:
"""Returns the observed wind speed (km/h)."""
return self._gridded.get("wind_speed")

@property
def wind_direction(self) -> Optional[int]:
"""Returns the observed wind direction (°)."""
return self._gridded.get("wind_direction")

@property
def wind_icon(self) -> Optional[str]:
"""Returns an icon ID illustrating the observed wind direction."""
return self._gridded.get("wind_icon")

@property
def weather_icon(self) -> Optional[str]:
"""Returns an icon ID illustrating the observed weather condition."""
return self._gridded.get("weather_icon")

@property
def weather_description(self) -> Optional[str]:
"""Returns a description of the observed weather condition."""
return self._gridded.get("weather_description")

def __repr__(self) -> str:
"""Returns a stringified version of the object."""
return (
f"Observation("
f" timezone={self.timezone},"
f" time={self.time_as_string},"
f" temperature={self.temperature}°C,"
f" wind_speed={self.wind_speed} km/h,"
f" wind_direction={self.wind_direction}°,"
f" wind_icon={self.wind_icon},"
f" weather_icon={self.weather_icon},"
f" weather_description={self.weather_description}"
")"
)
34 changes: 24 additions & 10 deletions tests/test_observation.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
# coding: utf-8
"""Tests Météo-France module. Observation class."""
import time
from datetime import datetime, timezone, timedelta
from datetime import datetime
from datetime import timedelta
from datetime import timezone

from .const import MOUNTAIN_CITY
from meteofrance_api import MeteoFranceClient
from meteofrance_api.model import Observation
from meteofrance_api.model import Place


def assert_types(observation) -> None:
"""Check observation types"""
def assert_types(observation: Observation) -> None:
"""Check observation types."""
assert type(observation.timezone) == str
assert type(observation.time_as_string) == str
assert type(observation.time_as_datetime) == datetime
assert type(observation.temperature) == float
assert type(observation.wind_speed) == float
assert (
type(observation.temperature) == float or type(observation.temperature) == int
)
assert type(observation.wind_speed) == float or type(observation.wind_speed) == int
assert type(observation.wind_direction) == int
assert type(observation.wind_icon) == str
assert type(observation.weather_icon) == str
assert type(observation.weather_description) == str


def assert_datetime(observation) -> None:
def assert_datetime(observation: Observation) -> None:
"""Check observation time is before now but after now - 1h."""
now = datetime.now(timezone.utc)
assert now - timedelta(hours=1) < observation.time_as_datetime < now
assert (
True
if observation.time_as_datetime is None
else now - timedelta(hours=1) < observation.time_as_datetime < now
)


def test_observation_france() -> None:
"""Test weather observation results from API (valid result, from lat/lon)."""
client = MeteoFranceClient()
observation = client.get_observation(latitude=48.8075, longitude=2.24028)


assert str(observation)

assert_types(observation)
assert_datetime(observation)

Expand All @@ -41,6 +51,8 @@ def test_observation_world() -> None:
client = MeteoFranceClient()
observation = client.get_observation(latitude=45.5016889, longitude=73.567256)

assert str(observation)

assert observation.timezone is None
assert observation.time_as_string is None
assert observation.time_as_datetime is None
Expand All @@ -56,6 +68,8 @@ def test_observation_place() -> None:
"""Test weather observation results from API (valid result, from place)."""
client = MeteoFranceClient()
observation = client.get_observation_for_place(place=Place(MOUNTAIN_CITY))


assert str(observation)

assert_types(observation)
assert_datetime(observation)

0 comments on commit 688078e

Please sign in to comment.