Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions pynws/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,33 @@
"blizzard": "Blizzard",
"fog": "Fog/mist",
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use strEnum for these, too.

OBS_TEMPERATURE = "temperature"
OBS_BARO_PRESSURE = "barometricPressure"
OBS_SEA_PRESSURE = "seaLevelPressure"
OBS_REL_HUMIDITY = "relativeHumidity"
OBS_WIND_SPEED = "windSpeed"
OBS_WIND_DIRECTION = "windDirection"
OBS_VISIBILITY = "visibility"
OBS_ELEVATION = "elevation"
OBS_DESCRIPTION = "textDescription"
OBS_DEWPOINT = "dewpoint"
OBS_WIND_GUST = "windGust"
OBS_STATION = "station"
OBS_TIMESTAMP = "timestamp"
OBS_ICON = "icon"
OBS_MAX_TEMP_24H = "maxTemperatureLast24Hours"
OBS_MIN_TEMP_24H = "minTemperatureLast24Hours"
OBS_PRECIPITATION_1H = "precipitationLastHour"
OBS_PRECIPITATION_3H = "precipitationLast3Hours"
OBS_PRECIPITATION_6H = "precipitationLast6Hours"
OBS_WIND_CHILL = "windChill"
OBS_HEAT_INDEX = "heatIndex"
OBS_RAW_MESSAGE = "rawMessage"

OBS_ITEM_VALUE = "value"
OBS_ITEM_UNIT_CODE = "unitCode"

# derived observations
OBS_ICON_TIME = "iconTime"
OBS_ICON_WEATHER = "iconWeather"
93 changes: 61 additions & 32 deletions pynws/simple_nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,36 @@

from metar import Metar

from .const import ALERT_ID, API_WEATHER_CODE
from .const import (
ALERT_ID,
API_WEATHER_CODE,
OBS_BARO_PRESSURE,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using strEnum eliminates the need to import the values separately.

OBS_DESCRIPTION,
OBS_DEWPOINT,
OBS_ELEVATION,
OBS_HEAT_INDEX,
OBS_ICON,
OBS_ICON_TIME,
OBS_ICON_WEATHER,
OBS_ITEM_UNIT_CODE,
OBS_ITEM_VALUE,
OBS_MAX_TEMP_24H,
OBS_MIN_TEMP_24H,
OBS_PRECIPITATION_1H,
OBS_PRECIPITATION_3H,
OBS_PRECIPITATION_6H,
OBS_RAW_MESSAGE,
OBS_REL_HUMIDITY,
OBS_SEA_PRESSURE,
OBS_STATION,
OBS_TEMPERATURE,
OBS_TIMESTAMP,
OBS_VISIBILITY,
OBS_WIND_CHILL,
OBS_WIND_DIRECTION,
OBS_WIND_GUST,
OBS_WIND_SPEED,
)
from .nws import Nws

WIND_DIRECTIONS = [
Expand Down Expand Up @@ -56,27 +85,27 @@ def m_p_s_to_km_p_hr(m_p_s):
WIND = {name: idx * 360 / 16 for idx, name in enumerate(WIND_DIRECTIONS)}

OBSERVATIONS = {
"temperature": ["temp", "C", None],
"barometricPressure": None,
"seaLevelPressure": ["press", "HPA", 100],
"relativeHumidity": None,
"windSpeed": ["wind_speed", "MPS", 3.6],
"windDirection": ["wind_dir", None, None],
"visibility": ["vis", "M", None],
"elevation": None,
"textDescription": None,
"dewpoint": None,
"windGust": None,
"station": None,
"timestamp": None,
"icon": None,
"maxTemperatureLast24Hours": None,
"minTemperatureLast24Hours": None,
"precipitationLastHour": None,
"precipitationLast3Hours": None,
"precipitationLast6Hours": None,
"windChill": None,
"heatIndex": None,
OBS_TEMPERATURE: ["temp", "C", None],
OBS_BARO_PRESSURE: None,
OBS_SEA_PRESSURE: ["press", "HPA", 100],
OBS_REL_HUMIDITY: None,
OBS_WIND_SPEED: ["wind_speed", "MPS", 3.6],
OBS_WIND_DIRECTION: ["wind_dir", None, None],
OBS_VISIBILITY: ["vis", "M", None],
OBS_ELEVATION: None,
OBS_DESCRIPTION: None,
OBS_DEWPOINT: None,
OBS_WIND_GUST: None,
OBS_STATION: None,
OBS_TIMESTAMP: None,
OBS_ICON: None,
OBS_MAX_TEMP_24H: None,
OBS_MIN_TEMP_24H: None,
OBS_PRECIPITATION_1H: None,
OBS_PRECIPITATION_3H: None,
OBS_PRECIPITATION_6H: None,
OBS_WIND_CHILL: None,
OBS_HEAT_INDEX: None,
}


Expand Down Expand Up @@ -153,7 +182,7 @@ async def set_station(self, station=None):
@staticmethod
def extract_metar(obs):
"""Return parsed metar if available."""
metar_msg = obs.get("rawMessage")
metar_msg = obs.get(OBS_RAW_MESSAGE)
if metar_msg:
try:
metar_obs = Metar.Metar(metar_msg)
Expand All @@ -170,7 +199,7 @@ async def update_observation(self, limit=0, start_time=None):
return None
self._observation = sorted(
obs,
key=lambda item: self.extract_observation_value(item, "timestamp"),
key=lambda item: self.extract_observation_value(item, OBS_TIMESTAMP),
reverse=True,
)
self._metar_obs = [self.extract_metar(iobs) for iobs in self._observation]
Expand Down Expand Up @@ -250,10 +279,10 @@ def extract_observation_value(observation, value):
if obs_value is None:
return None
if isinstance(observation[value], dict):
obs_sub_value = observation[value].get("value")
obs_sub_value = observation[value].get(OBS_ITEM_VALUE)
if obs_sub_value is None:
return None
return float(obs_sub_value), observation[value].get("unitCode")
return float(obs_sub_value), observation[value].get(OBS_ITEM_UNIT_CODE)
return observation[value]

@property
Expand Down Expand Up @@ -285,13 +314,13 @@ def observation(self):
data[obs] = met_prop.value()
if met[2] is not None:
data[obs] = data[obs] * met[2]
if data.get("icon"):
time, weather = parse_icon(data["icon"])
data["iconTime"] = time
data["iconWeather"] = convert_weather(weather)
if data.get(OBS_ICON):
time, weather = parse_icon(data[OBS_ICON])
data[OBS_ICON_TIME] = time
data[OBS_ICON_WEATHER] = convert_weather(weather)
else:
data["iconTime"] = None
data["iconWeather"] = None
data[OBS_ICON_TIME] = None
data[OBS_ICON_WEATHER] = None
return data

@property
Expand Down
121 changes: 67 additions & 54 deletions tests/test_simple_nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
import pytest

from pynws import NwsError, SimpleNWS
from pynws.const import (
OBS_DEWPOINT,
OBS_REL_HUMIDITY,
OBS_SEA_PRESSURE,
OBS_TEMPERATURE,
OBS_VISIBILITY,
OBS_WIND_DIRECTION,
OBS_WIND_GUST,
OBS_WIND_SPEED,
OBS_ICON_WEATHER,
OBS_ICON_TIME,
)

from tests.helpers import data_return_function, setup_app

LATLON = (0, 0)
Expand Down Expand Up @@ -48,17 +61,17 @@ async def test_nws_observation(aiohttp_client, loop, mock_urls, observation_json
await nws.update_observation()
observation = nws.observation
assert observation
assert observation["temperature"] == 10
assert observation["dewpoint"] == 10
assert observation["relativeHumidity"] == 10
assert observation["windDirection"] == 10
assert observation["visibility"] == 10000
assert observation["seaLevelPressure"] == 100000
assert observation["windSpeed"] == 36 # converted to km_gr
assert observation["iconTime"] == "day"
assert observation["windGust"] == 36 # same
assert observation["iconWeather"][0][0] == "A few clouds"
assert observation["iconWeather"][0][1] is None
assert observation[OBS_TEMPERATURE] == 10
assert observation[OBS_DEWPOINT] == 10
assert observation[OBS_REL_HUMIDITY] == 10
assert observation[OBS_WIND_DIRECTION] == 10
assert observation[OBS_VISIBILITY] == 10000
assert observation[OBS_SEA_PRESSURE] == 100000
assert observation[OBS_WIND_SPEED] == 36 # converted to km_gr
assert observation[OBS_ICON_TIME] == "day"
assert observation[OBS_WIND_GUST] == 36 # same
assert observation[OBS_ICON_WEATHER][0][0] == "A few clouds"
assert observation[OBS_ICON_WEATHER][0][1] is None


async def test_nws_observation_units(aiohttp_client, loop, mock_urls):
Expand All @@ -69,9 +82,9 @@ async def test_nws_observation_units(aiohttp_client, loop, mock_urls):
await nws.update_observation()
observation = nws.observation
assert observation
assert round(observation["temperature"], 1) == -12.2
assert observation["windSpeed"] == 10 # converted to km_gr
assert observation["windGust"] == 10
assert round(observation[OBS_TEMPERATURE], 1) == -12.2
assert observation[OBS_WIND_SPEED] == 10 # converted to km_gr
assert observation[OBS_WIND_GUST] == 10


async def test_nws_observation_metar(aiohttp_client, loop, mock_urls):
Expand All @@ -82,14 +95,14 @@ async def test_nws_observation_metar(aiohttp_client, loop, mock_urls):
await nws.update_observation()
observation = nws.observation

assert observation["temperature"] == 25.6
assert observation["dewpoint"] is None
assert observation["relativeHumidity"] is None
assert observation["windDirection"] == 350.0
assert observation["visibility"] == 16093.44
assert round(observation["seaLevelPressure"]) == 101761
assert round(observation["windSpeed"], 2) == 9.26
assert observation["windGust"] is None
assert observation[OBS_TEMPERATURE] == 25.6
assert observation[OBS_DEWPOINT] is None
assert observation[OBS_REL_HUMIDITY] is None
assert observation[OBS_WIND_DIRECTION] == 350.0
assert observation[OBS_VISIBILITY] == 16093.44
assert round(observation[OBS_SEA_PRESSURE]) == 101761
assert round(observation[OBS_WIND_SPEED], 2) == 9.26
assert observation[OBS_WIND_GUST] is None


async def test_nws_observation_metar_noparse(aiohttp_client, loop, mock_urls):
Expand All @@ -99,7 +112,7 @@ async def test_nws_observation_metar_noparse(aiohttp_client, loop, mock_urls):
await nws.set_station(STATION)
await nws.update_observation()
observation = nws.observation
assert observation["temperature"] is None
assert observation[OBS_TEMPERATURE] is None


async def test_nws_observation_empty(aiohttp_client, loop, mock_urls):
Expand All @@ -110,16 +123,16 @@ async def test_nws_observation_empty(aiohttp_client, loop, mock_urls):
await nws.update_observation()
observation = nws.observation

assert observation["temperature"] is None
assert observation["dewpoint"] is None
assert observation["relativeHumidity"] is None
assert observation["windDirection"] is None
assert observation["visibility"] is None
assert observation["seaLevelPressure"] is None
assert observation["windSpeed"] is None
assert observation["windGust"] is None
assert observation["iconTime"] is None
assert observation["iconWeather"] is None
assert observation[OBS_TEMPERATURE] is None
assert observation[OBS_DEWPOINT] is None
assert observation[OBS_REL_HUMIDITY] is None
assert observation[OBS_WIND_DIRECTION] is None
assert observation[OBS_VISIBILITY] is None
assert observation[OBS_SEA_PRESSURE] is None
assert observation[OBS_WIND_SPEED] is None
assert observation[OBS_WIND_GUST] is None
assert observation[OBS_ICON_TIME] is None
assert observation[OBS_ICON_WEATHER] is None


async def test_nws_observation_noprop(aiohttp_client, loop, mock_urls):
Expand All @@ -141,16 +154,16 @@ async def test_nws_observation_missing_value(aiohttp_client, loop, mock_urls):
await nws.update_observation()
observation = nws.observation

assert observation["temperature"] is None
assert observation["dewpoint"] is None
assert observation["relativeHumidity"] is None
assert observation["windDirection"] is None
assert observation["visibility"] is None
assert observation["seaLevelPressure"] is None
assert observation["windSpeed"] is None
assert observation["windGust"] is None
assert observation["iconTime"] is None
assert observation["iconWeather"] is None
assert observation[OBS_TEMPERATURE] is None
assert observation[OBS_DEWPOINT] is None
assert observation[OBS_REL_HUMIDITY] is None
assert observation[OBS_WIND_DIRECTION] is None
assert observation[OBS_VISIBILITY] is None
assert observation[OBS_SEA_PRESSURE] is None
assert observation[OBS_WIND_SPEED] is None
assert observation[OBS_WIND_GUST] is None
assert observation[OBS_ICON_TIME] is None
assert observation[OBS_ICON_WEATHER] is None


@freeze_time("2019-10-13T14:30:00-04:00")
Expand All @@ -161,10 +174,10 @@ async def test_nws_forecast(aiohttp_client, loop, mock_urls):
await nws.update_forecast()
forecast = nws.forecast

assert forecast[0]["iconWeather"][0][0] == "Thunderstorm (high cloud cover)"
assert forecast[0]["iconWeather"][0][1] == 40
assert forecast[0]["iconWeather"][1][0] == "Overcast"
assert forecast[0]["iconWeather"][1][1] is None
assert forecast[0][OBS_ICON_WEATHER][0][0] == "Thunderstorm (high cloud cover)"
assert forecast[0][OBS_ICON_WEATHER][0][1] == 40
assert forecast[0][OBS_ICON_WEATHER][1][0] == "Overcast"
assert forecast[0][OBS_ICON_WEATHER][1][1] is None
assert forecast[0]["windSpeedAvg"] == 10
assert forecast[0]["windBearing"] == 180

Expand All @@ -176,13 +189,13 @@ async def test_nws_forecast_discard_stale(aiohttp_client, loop, mock_urls):
nws = SimpleNWS(*LATLON, USERID, client, filter_forecast=True)
await nws.update_forecast_hourly()
forecast = nws.forecast_hourly
assert forecast[0]["temperature"] == 77
assert forecast[0][OBS_TEMPERATURE] == 77

nws = SimpleNWS(*LATLON, USERID, client, filter_forecast=False)
await nws.update_forecast_hourly()
forecast = nws.forecast_hourly

assert forecast[0]["temperature"] == 78
assert forecast[0][OBS_TEMPERATURE] == 78


@freeze_time("2019-10-14T20:30:00-04:00")
Expand All @@ -193,7 +206,7 @@ async def test_nws_forecast_hourly(aiohttp_client, loop, mock_urls):
await nws.update_forecast_hourly()
forecast = nws.forecast_hourly

assert forecast[0]["temperature"] == 78
assert forecast[0][OBS_TEMPERATURE] == 78


@freeze_time("2019-10-13T14:30:00-04:00")
Expand All @@ -204,10 +217,10 @@ async def test_nws_forecast_strings(aiohttp_client, loop, mock_urls):
await nws.update_forecast()
forecast = nws.forecast

assert forecast[0]["iconWeather"][0][0] == "Thunderstorm (high cloud cover)"
assert forecast[0]["iconWeather"][0][1] == 40
assert forecast[0]["iconWeather"][1][0] == "Overcast"
assert forecast[0]["iconWeather"][1][1] is None
assert forecast[0][OBS_ICON_WEATHER][0][0] == "Thunderstorm (high cloud cover)"
assert forecast[0][OBS_ICON_WEATHER][0][1] == 40
assert forecast[0][OBS_ICON_WEATHER][1][0] == "Overcast"
assert forecast[0][OBS_ICON_WEATHER][1][1] is None
assert forecast[0]["windSpeedAvg"] == 10
assert forecast[0]["windBearing"] == 180

Expand Down