Skip to content

Commit 3375e72

Browse files
authored
Add requests_remaining and requests_per_day properties (#6)
* Add requests_remaining and requests_per_day properties
1 parent 8be5580 commit 3375e72

File tree

6 files changed

+96
-3
lines changed

6 files changed

+96
-3
lines changed

.github/workflows/run_tests.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ jobs:
2424
run: |
2525
python -m pip install --upgrade pip
2626
pip install flake8 pep517
27-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27+
if [ -f requirements-test.txt ]; then \
28+
pip install -r requirements-test.txt; \
29+
elif [ -f requirements.txt ]; then \
30+
pip install -r requirements.txt; \
31+
fi
2832
- name: Lint with flake8
2933
run: |
3034
# stop the build if there are Python syntax errors or undefined names

airly/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,23 @@ def create_measurements_session_point(
4848
return MeasurementsSession(
4949
self._rh, MeasurementsSession.Mode.POINT,
5050
latitude=latitude, longitude=longitude)
51+
52+
@property
53+
def requests_remaining(self):
54+
"""
55+
Returns the remaining number of requests on a given day as Int or None if value
56+
is unavailable. The counter is reset each day at midnight UTC.
57+
"""
58+
59+
return self._rh.requests_remaining
60+
61+
@property
62+
def requests_per_day(self):
63+
"""
64+
Returns the allowed number of requests per day as Int or None if value is
65+
unavailable.
66+
According to the API documentation, the default rate limit per API key is 100
67+
requests per day. But for old API keys (pre-2020), the rate limit is 1000
68+
requests per day.
69+
"""
70+
return self._rh.requests_per_day

airly/_private.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,20 @@ def __init__(self, api_key, session: aiohttp.ClientSession, base_url,
2727
self.headers['Accept-Language'] = language
2828
self.base_url = base_url
2929
self.session = session
30+
self.requests_per_day = None
31+
self.requests_remaining = None
3032

3133
async def get(self, request_path):
3234
url = self.base_url + request_path
3335
_LOGGER.debug("Sending request: " + url)
3436
async with self.session.get(url, headers=self.headers) as response:
37+
# The values for the X-RateLimit-Limit-day and X-RateLimit-Remaining-day
38+
# headers should be returned for HTTP status code 200 and 429, but sometimes
39+
# they are missing for unknown reasons.
40+
if "X-RateLimit-Limit-day" in response.headers:
41+
self.requests_per_day = int(response.headers["X-RateLimit-Limit-day"])
42+
if "X-RateLimit-Remaining-day" in response.headers:
43+
self.requests_remaining = int(response.headers["X-RateLimit-Remaining-day"])
3544
if response.status != 200:
3645
_LOGGER.warning("Invalid response from Airly API: %s",
3746
response.status)

requirements-test.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-e .
2+
aioresponses
3+
aiounittest

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
-e .
1+
-e .

tests/test__private.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
from airly._private import _DictToObj
1+
import json
22
from unittest import TestCase
33

4+
import aiohttp
5+
from aioresponses import aioresponses
6+
from aiounittest import AsyncTestCase
7+
8+
from airly import Airly
9+
from airly._private import _DictToObj
10+
11+
LATITUDE = 12
12+
LONGITUDE = 21
13+
414
class _DictToObjTestCase(TestCase):
515
def test_init_with_iterable(self):
616
data = { 'key1': 'value1', 'key2': 2 }
@@ -16,3 +26,50 @@ def test_init_with_kwargs(self):
1626
self.assertEqual('value1', sut.key1)
1727
self.assertEqual(2, sut['key2'])
1828
self.assertEqual(2, sut.key2)
29+
30+
31+
class RemainingRequestsTestCase(AsyncTestCase):
32+
async def test_valid_remaining_requests_headers(self):
33+
with open("data/measurements_typical.json") as file:
34+
data = json.load(file)
35+
headers = {"X-RateLimit-Limit-day": "1000", "X-RateLimit-Remaining-day": "993"}
36+
37+
session = aiohttp.ClientSession()
38+
39+
with aioresponses() as session_mock:
40+
session_mock.get(
41+
'https://airapi.airly.eu/v2/measurements/point?lat=12.000000&lng=21.000000',
42+
payload=data,
43+
headers=headers,
44+
)
45+
airly = Airly("abcdef", session)
46+
measurements = airly.create_measurements_session_point(LATITUDE, LONGITUDE)
47+
await measurements.update()
48+
49+
await session.close()
50+
51+
self.assertEqual(1000, airly.requests_per_day)
52+
self.assertEqual(993, airly.requests_remaining)
53+
54+
async def test_invalid_remaining_requests_headers(self):
55+
with open("data/measurements_typical.json") as file:
56+
data = json.load(file)
57+
headers = {}
58+
59+
session = aiohttp.ClientSession()
60+
61+
with aioresponses() as session_mock:
62+
session_mock.get(
63+
'https://airapi.airly.eu/v2/measurements/point?lat=12.000000&lng=21.000000',
64+
payload=data,
65+
headers=headers,
66+
)
67+
airly = Airly("abcdef", session)
68+
measurements = airly.create_measurements_session_point(LATITUDE, LONGITUDE)
69+
await measurements.update()
70+
71+
await session.close()
72+
73+
self.assertIsNone(airly.requests_per_day)
74+
self.assertIsNone(airly.requests_remaining)
75+

0 commit comments

Comments
 (0)