Skip to content

Commit

Permalink
Sort station observations at lower layer; Add latest observation (#72)
Browse files Browse the repository at this point in the history
* Sort station observations to work around bug weather-gov/api#474

* Fix exception with invalid data

* Add method to retrieve the latest station observation

* Added missing file and fixed black complaints

* Sort observations in descending order

* Remove duplicate sort of observation values

* Merge debugging logic into raw_stations_observations_latest
  • Loading branch information
lymanepp committed Mar 13, 2022
1 parent b4f2874 commit 61db51f
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 7 deletions.
1 change: 1 addition & 0 deletions pynws/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
API_URL = "https://api.weather.gov/"
API_POINTS_STATIONS = "points/{},{}/stations"
API_STATIONS_OBSERVATIONS = "stations/{}/observations/"
API_STATIONS_OBSERVATIONS_LATEST = "stations/{}/observations/latest"
API_ACCEPT = "application/geo+json"
API_USER = "pynws {}"
API_DETAILED_FORECAST = "gridpoints/{}/{},{}"
Expand Down
13 changes: 12 additions & 1 deletion pynws/nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
raw_points,
raw_points_stations,
raw_stations_observations,
raw_stations_observations_latest,
)
from pynws.forecast import DetailedForecast

Expand Down Expand Up @@ -50,7 +51,17 @@ async def get_stations_observations(self, limit=0, start_time=None):
res = await raw_stations_observations(
self.station, self.session, self.userid, limit, start_time
)
return [o["properties"] for o in res["features"]]
observations = [o["properties"] for o in res["features"]]
return sorted(observations, key=lambda o: o.get("timestamp"), reverse=True)

async def get_stations_observations_latest(self):
"""Returns latest observation"""
if self.station is None:
raise NwsError("Need to set station")
res = await raw_stations_observations_latest(
self.station, self.session, self.userid
)
return res.get("properties")

async def get_points(self):
"""Saves griddata from latlon."""
Expand Down
7 changes: 7 additions & 0 deletions pynws/raw_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ async def raw_stations_observations(station, websession, userid, limit=0, start=
return await _make_request(websession, url, header, params)


async def raw_stations_observations_latest(station, websession, userid):
"""Get observation response from station"""
url = pynws.urls.stations_observations_latest_url(station)
header = get_header(userid)
return await _make_request(websession, url, header)


async def raw_points_stations(lat, lon, websession, userid):
"""Get list of stations for lat/lon"""
url = pynws.urls.points_stations_url(lat, lon)
Expand Down
6 changes: 1 addition & 5 deletions pynws/simple_nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,7 @@ async def update_observation(self, limit=0, start_time=None):
obs = await self.get_stations_observations(limit, start_time=start_time)
if obs is None:
return None
self._observation = sorted(
obs,
key=lambda item: self.extract_observation_value(item, "timestamp"),
reverse=True,
)
self._observation = obs
self._metar_obs = [self.extract_metar(iobs) for iobs in self._observation]

async def update_forecast(self):
Expand Down
6 changes: 6 additions & 0 deletions pynws/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
API_POINTS,
API_POINTS_STATIONS,
API_STATIONS_OBSERVATIONS,
API_STATIONS_OBSERVATIONS_LATEST,
API_URL,
)

Expand All @@ -16,6 +17,11 @@ def stations_observations_url(station):
return API_URL + API_STATIONS_OBSERVATIONS.format(station)


def stations_observations_latest_url(station):
"""Formats observation url."""
return API_URL + API_STATIONS_OBSERVATIONS_LATEST.format(station)


def points_stations_url(lat, lon):
"""formats station url"""
return API_URL + API_POINTS_STATIONS.format(str(lat), str(lon))
Expand Down
7 changes: 6 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ def mock_urls():
with patch(
"pynws.urls.stations_observations_url"
) as mock_stations_observations_url, patch(
"pynws.urls.stations_observations_latest_url"
) as mock_stations_observations_latest_url, patch(
"pynws.urls.points_url"
) as mock_points_url, patch(
"pynws.urls.detailed_forecast_url"
Expand All @@ -20,11 +22,14 @@ def mock_urls():
"pynws.urls.alerts_active_zone_url"
) as mock_alerts_active_zone_url:
mock_stations_observations_url.return_value = "/stations_observations"
mock_stations_observations_latest_url.return_value = (
"/stations_observations_latest"
)
mock_points_url.return_value = "/points"
mock_detailed_forecast_url.return_value = "/gridpoints"
mock_gridpoints_forecast_url.return_value = "/gridpoints_forecast"
mock_gridpoints_forecast_hourly_url.return_value = "/gridpoints_forecast_hourly"
mock_points_stations_url.return_value = "/points_stations"
mock_alerts_active_zone_url.return_value = "/alerts_active_zone"

yield mock_stations_observations_url, mock_points_url, mock_detailed_forecast_url, mock_gridpoints_forecast_url, mock_gridpoints_forecast_hourly_url, mock_points_stations_url, mock_alerts_active_zone_url
yield mock_stations_observations_url, mock_stations_observations_latest_url, mock_points_url, mock_detailed_forecast_url, mock_gridpoints_forecast_url, mock_gridpoints_forecast_hourly_url, mock_points_stations_url, mock_alerts_active_zone_url
162 changes: 162 additions & 0 deletions tests/fixtures/stations_observations_latest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"@version": "1.1",
"wx": "https://api.weather.gov/ontology#",
"s": "https://schema.org/",
"geo": "http://www.opengis.net/ont/geosparql#",
"unit": "http://codes.wmo.int/common/unit/",
"@vocab": "https://api.weather.gov/ontology#",
"geometry": {
"@id": "s:GeoCoordinates",
"@type": "geo:wktLiteral"
},
"city": "s:addressLocality",
"state": "s:addressRegion",
"distance": {
"@id": "s:Distance",
"@type": "s:QuantitativeValue"
},
"bearing": {
"@type": "s:QuantitativeValue"
},
"value": {
"@id": "s:value"
},
"unitCode": {
"@id": "s:unitCode",
"@type": "@id"
},
"forecastOffice": {
"@type": "@id"
},
"forecastGridData": {
"@type": "@id"
},
"publicZone": {
"@type": "@id"
},
"county": {
"@type": "@id"
}
}
],
"id": "https://api.weather.gov/stations/KFLL/observations/2022-03-02T23:53:00+00:00",
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-80.150000000000006,
26.07
]
},
"properties": {
"@id": "https://api.weather.gov/stations/KFLL/observations/2022-03-02T23:53:00+00:00",
"@type": "wx:ObservationStation",
"elevation": {
"unitCode": "wmoUnit:m",
"value": 3
},
"station": "https://api.weather.gov/stations/KFLL",
"timestamp": "2022-03-02T23:53:00+00:00",
"rawMessage": "KFLL 022353Z 02004KT 10SM FEW026 BKN055 23/18 A3008 RMK AO2 SLP187 60000 T02280183 10244 20228 53008 $",
"textDescription": "Mostly Cloudy",
"icon": "https://api.weather.gov/icons/land/night/bkn?size=medium",
"presentWeather": [],
"temperature": {
"unitCode": "wmoUnit:degC",
"value": 22.800000000000001,
"qualityControl": "V"
},
"dewpoint": {
"unitCode": "wmoUnit:degC",
"value": 18.300000000000001,
"qualityControl": "V"
},
"windDirection": {
"unitCode": "wmoUnit:degree_(angle)",
"value": 20,
"qualityControl": "V"
},
"windSpeed": {
"unitCode": "wmoUnit:km_h-1",
"value": 7.5599999999999996,
"qualityControl": "V"
},
"windGust": {
"unitCode": "wmoUnit:km_h-1",
"value": null,
"qualityControl": "Z"
},
"barometricPressure": {
"unitCode": "wmoUnit:Pa",
"value": 101860,
"qualityControl": "V"
},
"seaLevelPressure": {
"unitCode": "wmoUnit:Pa",
"value": 101870,
"qualityControl": "V"
},
"visibility": {
"unitCode": "wmoUnit:m",
"value": 16090,
"qualityControl": "C"
},
"maxTemperatureLast24Hours": {
"unitCode": "wmoUnit:degC",
"value": null
},
"minTemperatureLast24Hours": {
"unitCode": "wmoUnit:degC",
"value": null
},
"precipitationLastHour": {
"unitCode": "wmoUnit:m",
"value": null,
"qualityControl": "Z"
},
"precipitationLast3Hours": {
"unitCode": "wmoUnit:m",
"value": null,
"qualityControl": "Z"
},
"precipitationLast6Hours": {
"unitCode": "wmoUnit:m",
"value": 0,
"qualityControl": "C"
},
"relativeHumidity": {
"unitCode": "wmoUnit:percent",
"value": 75.770935807607003,
"qualityControl": "V"
},
"windChill": {
"unitCode": "wmoUnit:degC",
"value": null,
"qualityControl": "V"
},
"heatIndex": {
"unitCode": "wmoUnit:degC",
"value": 23.11401887942111,
"qualityControl": "V"
},
"cloudLayers": [
{
"base": {
"unitCode": "wmoUnit:m",
"value": 790
},
"amount": "FEW"
},
{
"base": {
"unitCode": "wmoUnit:m",
"value": 1680
},
"amount": "BKN"
}
]
}
}
5 changes: 5 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def setup_app(
points_stations="points_stations.json",
points="points.json",
stations_observations="stations_observations.json",
stations_observations_latest="stations_observations_latest.json",
detailed_forecast="detailed_forecast.json",
gridpoints_forecast="gridpoints_forecast.json",
gridpoints_forecast_hourly="gridpoints_forecast_hourly.json",
Expand All @@ -33,6 +34,10 @@ def setup_app(
app.router.add_get(
"/stations_observations", data_return_function(stations_observations)
)
app.router.add_get(
"/stations_observations_latest",
data_return_function(stations_observations_latest),
)
app.router.add_get("/gridpoints", data_return_function(detailed_forecast))
app.router.add_get(
"/gridpoints_forecast", data_return_function(gridpoints_forecast)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ async def test_nws_stations_observations(aiohttp_client, loop, mock_urls):
assert isinstance(observations, list)


async def test_nws_stations_observations_latest(aiohttp_client, loop, mock_urls):
app = setup_app()
client = await aiohttp_client(app)
nws = Nws(client, USERID, LATLON)
assert nws
with pytest.raises(NwsError):
await nws.get_stations_observations_latest()
nws.station = STATION
observation = await nws.get_stations_observations_latest()
assert observation
assert isinstance(observation, dict)


async def test_nws_detailed_forecast(aiohttp_client, loop, mock_urls):
app = setup_app()
client = await aiohttp_client(app)
Expand Down

0 comments on commit 61db51f

Please sign in to comment.