Skip to content
Open
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
2 changes: 2 additions & 0 deletions pyairnow/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
InvalidKeyError
from .forecast import Forecast
from .observation import Observations
from .historical import Historical

API_BASE_URL: str = 'https://www.airnowapi.org'
API_DEFAULT_TIMEOUT: int = 10
Expand All @@ -24,6 +25,7 @@ def __init__(

self.forecast = Forecast(self._get)
self.observations = Observations(self._get)
self.historical = Historical(self._get)

async def _get(
self, endpoint: str, *, base_url: str = API_BASE_URL, **kwargs
Expand Down
85 changes: 85 additions & 0 deletions pyairnow/historical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'''Retrieve Historical Air Quality'''
from datetime import date as date_, datetime, timezone, timedelta
from typing import Callable, Coroutine, Optional, Union
from .errors import AirNowError


class Historical:
'''
Class to retrieve historical air quality by zip code or by latitude and
longitude.
'''
def __init__(self, request: Callable[..., Coroutine]) -> None:
self._request = request

'''If date is given as string, must be in yyyy-mm-d format. '''
async def zipCode(
self,
zipCode: str,
*,
date: Union[date_, datetime, str],
distance: Optional[int] = None
) -> list:
'''Request current observation for zip code'''
params: dict = dict(zipCode=zipCode)
'''Airnow needs T00-0000 appended to the date parameter for historical calls'''
if isinstance(date, str):
y, m, d = date.split('-')
'''create a timezone object with no utc offset'''
tz = timezone(timedelta())
params['date'] = datetime(int(y), int(m), int(d), tzinfo=tz).strftime("%Y-%m-%dT%H%z")
elif isinstance(date, datetime):
tz = timezone(timedelta())
date = date.replace(tzinfo=tz)
params['date'] = date.strftime("%Y-%m-%dT%H%z")
elif isinstance(date, date_):
tz=timezone(timedelta())
params['date'] = datetime(date.year, date.month, date.day, tzinfo=tz).strftime("%Y-%m-%dT%H%z")
else:
raise AirNowError("Bad date type. Date parameter is either date, datetime, or a string.")
Comment on lines +28 to +39

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@asymworks I switched to using strftime to get the right date formatting as you suggested. Interestingly, when a datetime object is set to utc timezone, the offset becomes +0000 and not -0000. I tested this with some simple calls to the api and it works just fine either way.


if distance:
params['distance'] = distance

return await self._request(
'aq/observation/zipCode/historical',
params=params
)

async def latLong(
self,
latitude: Optional[Union[float, str]] = None,
longitude: Optional[Union[float, str]] = None,
*,
date: Union[date_, datetime, str],
distance: Optional[int] = None,
) -> None:
'''Request current observation for latitude/longitude'''
params: dict = dict(
latitude=str(latitude),
longitude=str(longitude),
)

if isinstance(date, str):
y, m, d = date.split('-')
'''create a timezone object with no utc offset'''
tz = timezone(timedelta())
params['date'] = datetime(int(y), int(m), int(d), tzinfo=tz).strftime("%Y-%m-%dT%H%z")
elif isinstance(date, datetime):
tz = timezone(timedelta())
date = date.replace(tzinfo=tz)
params['date'] = date.strftime("%Y-%m-%dT%H%z")
elif isinstance(date, date_):
tz=timezone(timedelta())
params['date'] = datetime(date.year, date.month, date.day, tzinfo=tz).strftime("%Y-%m-%dT%H%z")
else:
raise AirNowError("Bad date type. Date parameter is either date, datetime, or a string.")

if distance:
params['distance'] = distance

return await self._request(
'aq/observation/latLong/historical',
params=params
)

2 changes: 1 addition & 1 deletion tests/mock_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

MOCK_API_KEY = '01234567-89AB-CDEF-0123-456789ABCDEF'
RE_ENDPOINTS = re.compile(
'^/?aq/(forecast|observation)/(zipCode|latLong)(/(current))?/?$'
'^/?aq/(forecast|observation)/(zipCode|latLong)(/(current|historical))?/?$'
)


Expand Down
226 changes: 226 additions & 0 deletions tests/test_15_historical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import datetime
import pytest
from urllib.parse import unquote

from pyairnow import WebServiceAPI

from .mock_api import MOCK_API_KEY


@pytest.mark.asyncio
async def test_api_historical_zipcode(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.zipCode('90001', date='2020-09-01')

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'zipCode'
assert data[0]['when'] == 'historical'

assert 'zipCode' in data[0]['query']
assert data[0]['query']['zipCode'] == '90001'


@pytest.mark.asyncio
async def test_api_historical_zipcode_distance(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.zipCode('90001', date='2020-09-01', distance=100)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'zipCode'
assert data[0]['when'] == 'historical'

assert 'zipCode' in data[0]['query']
assert data[0]['query']['zipCode'] == '90001'

assert 'distance' in data[0]['query']
assert data[0]['query']['distance'] == '100'


@pytest.mark.asyncio
async def test_api_historical_zipcode_date_str(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.zipCode('90001', date='2020-09-01')

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'zipCode'
assert data[0]['when'] == 'historical'

assert 'zipCode' in data[0]['query']
assert data[0]['query']['zipCode'] == '90001'

assert 'date' in data[0]['query']
'''have to decode url format due to + symbol'''
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'


@pytest.mark.asyncio
async def test_api_historical_zipcode_date_date(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.zipCode(
'90001',
date=datetime.date(2020, 9, 1)
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'zipCode'
assert data[0]['when'] == 'historical'

assert 'zipCode' in data[0]['query']
assert data[0]['query']['zipCode'] == '90001'

assert 'date' in data[0]['query']
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'


@pytest.mark.asyncio
async def test_api_historical_zipcode_date_datetime(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.zipCode(
'90001',
date=datetime.datetime(2020, 9, 1)
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'zipCode'
assert data[0]['when'] == 'historical'

assert 'zipCode' in data[0]['query']
assert data[0]['query']['zipCode'] == '90001'

assert 'date' in data[0]['query']
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'


@pytest.mark.asyncio
async def test_api_historical_ll(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.latLong(
34.053718,
-118.244842,
date='2020-09-01')

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'latLong'
assert data[0]['when'] == 'historical'

assert 'latitude' in data[0]['query']
assert data[0]['query']['latitude'] == '34.053718'
assert 'longitude' in data[0]['query']
assert data[0]['query']['longitude'] == '-118.244842'


@pytest.mark.asyncio
async def test_api_historical_ll_distance(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.latLong(
34.053718,
-118.244842,
distance=120,
date='2020-09-01'
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'latLong'
assert data[0]['when'] == 'historical'

assert 'latitude' in data[0]['query']
assert data[0]['query']['latitude'] == '34.053718'
assert 'longitude' in data[0]['query']
assert data[0]['query']['longitude'] == '-118.244842'

assert 'distance' in data[0]['query']
assert data[0]['query']['distance'] == '120'


@pytest.mark.asyncio
async def test_api_historical_ll_date_str(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.latLong(
34.053718,
-118.244842,
date='2020-09-01'
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'latLong'
assert data[0]['when'] == 'historical'

assert 'latitude' in data[0]['query']
assert data[0]['query']['latitude'] == '34.053718'
assert 'longitude' in data[0]['query']
assert data[0]['query']['longitude'] == '-118.244842'

assert 'date' in data[0]['query']
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'


@pytest.mark.asyncio
async def test_api_historical_ll_date_date(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.latLong(
34.053718, -118.244842,
date=datetime.date(2020, 9, 1)
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'latLong'
assert data[0]['when'] == 'historical'

assert 'latitude' in data[0]['query']
assert data[0]['query']['latitude'] == '34.053718'
assert 'longitude' in data[0]['query']
assert data[0]['query']['longitude'] == '-118.244842'

assert 'date' in data[0]['query']
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'


@pytest.mark.asyncio
async def test_api_historical_ll_date_datetime(mock_airnowapi):
client = WebServiceAPI(MOCK_API_KEY)
data = await client.historical.latLong(
34.053718, -118.244842,
date=datetime.datetime(2020, 9, 1)
)

assert isinstance(data, list)
assert len(data) == 1

assert data[0]['type'] == 'observation'
assert data[0]['mode'] == 'latLong'
assert data[0]['when'] == 'historical'

assert 'latitude' in data[0]['query']
assert data[0]['query']['latitude'] == '34.053718'
assert 'longitude' in data[0]['query']
assert data[0]['query']['longitude'] == '-118.244842'

assert 'date' in data[0]['query']
assert unquote(data[0]['query']['date']) == '2020-09-01T00+0000'