Skip to content

Commit 156d107

Browse files
authored
Merge pull request #15 from joostlek/credits
Add credit calculations
2 parents 091be04 + 88085a5 commit 156d107

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

.github/workflows/linting.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ name: Linting
55
on:
66
push:
77
pull_request:
8+
types: [opened, reopened]
89
workflow_dispatch:
910

1011
env:

src/python_opensky/opensky.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import asyncio
55
import socket
6-
from dataclasses import dataclass
6+
from dataclasses import dataclass, field
7+
from datetime import datetime, timedelta, timezone
78
from importlib import metadata
89
from typing import Any, cast
910

@@ -24,7 +25,10 @@ class OpenSky:
2425
session: ClientSession | None = None
2526
request_timeout: int = 10
2627
api_host: str = "python_opensky-network.org"
28+
opensky_credits: int = 400
29+
timezone = timezone.utc
2730
_close_session: bool = False
31+
_credit_usage: dict[datetime, int] = field(default_factory=dict)
2832

2933
async def _request(
3034
self,
@@ -103,6 +107,7 @@ async def _request(
103107

104108
async def states(self, bounding_box: BoundingBox | None = None) -> StatesResponse:
105109
"""Retrieve state vectors for a given time."""
110+
credit_cost = 4
106111
params = {
107112
"time": 0,
108113
"extended": "true",
@@ -114,6 +119,7 @@ async def states(self, bounding_box: BoundingBox | None = None) -> StatesRespons
114119
params[MAX_LATITUDE] = bounding_box.max_latitude
115120
params[MIN_LONGITUDE] = bounding_box.min_longitude
116121
params[MAX_LONGITUDE] = bounding_box.max_longitude
122+
credit_cost = self.calculate_credit_costs(bounding_box)
117123

118124
data = await self._request("states/all", data=params)
119125

@@ -143,8 +149,37 @@ async def states(self, bounding_box: BoundingBox | None = None) -> StatesRespons
143149
"states": [dict(zip(keys, state, strict=True)) for state in data["states"]],
144150
}
145151

152+
self._register_credit_usage(credit_cost)
153+
146154
return StatesResponse.parse_obj(data)
147155

156+
@staticmethod
157+
def calculate_credit_costs(bounding_box: BoundingBox) -> int:
158+
"""Calculate the amount of credits a request costs."""
159+
latitude_degrees = bounding_box.max_latitude - bounding_box.min_latitude
160+
longitude_degrees = bounding_box.max_longitude - bounding_box.min_longitude
161+
area = latitude_degrees * longitude_degrees
162+
if area < 25:
163+
return 1
164+
if area < 100:
165+
return 2
166+
if area < 400:
167+
return 3
168+
return 4
169+
170+
def _register_credit_usage(self, opensky_credits: int) -> None:
171+
self._credit_usage[datetime.now(self.timezone)] = opensky_credits
172+
173+
def remaining_credits(self) -> int:
174+
"""Calculate the remaining opensky credits."""
175+
now = datetime.now(self.timezone)
176+
used_credits = sum(
177+
v
178+
for k, v in self._credit_usage.items()
179+
if now - timedelta(hours=24) <= k <= now
180+
)
181+
return self.opensky_credits - used_credits
182+
148183
async def close(self) -> None:
149184
"""Close open client session."""
150185
if self.session and self._close_session:

tests/test_states.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,27 @@ async def test_states_with_bounding_box(
8989
await opensky.close()
9090

9191

92+
async def test_credit_usage(
93+
aresponses: ResponsesMockServer,
94+
) -> None:
95+
"""Test credit usage."""
96+
aresponses.add(
97+
OPENSKY_URL,
98+
"/api/states/all",
99+
"GET",
100+
aresponses.Response(
101+
status=200,
102+
headers={"Content-Type": "application/json"},
103+
text=load_fixture("states.json"),
104+
),
105+
)
106+
async with aiohttp.ClientSession() as session:
107+
opensky = OpenSky(session=session)
108+
await opensky.states()
109+
assert opensky.remaining_credits() == 396
110+
await opensky.close()
111+
112+
92113
async def test_new_session(
93114
aresponses: ResponsesMockServer,
94115
) -> None:
@@ -173,3 +194,36 @@ async def test_unexpected_server_response(
173194
with pytest.raises(OpenSkyError):
174195
assert await opensky.states()
175196
await opensky.close()
197+
198+
199+
async def test_calculating_credit_usage() -> None:
200+
"""Test calculating credit usage."""
201+
opensky = OpenSky()
202+
bounding_box = BoundingBox(
203+
min_latitude=49.7,
204+
max_latitude=50.5,
205+
min_longitude=3.2,
206+
max_longitude=4.6,
207+
)
208+
assert opensky.calculate_credit_costs(bounding_box) == 1
209+
bounding_box = BoundingBox(
210+
min_latitude=46.5,
211+
max_latitude=49.9,
212+
min_longitude=-1.4,
213+
max_longitude=6.8,
214+
)
215+
assert opensky.calculate_credit_costs(bounding_box) == 2
216+
bounding_box = BoundingBox(
217+
min_latitude=42.2,
218+
max_latitude=49.8,
219+
min_longitude=-4.7,
220+
max_longitude=10.9,
221+
)
222+
assert opensky.calculate_credit_costs(bounding_box) == 3
223+
bounding_box = BoundingBox(
224+
min_latitude=42.2,
225+
max_latitude=49.8,
226+
min_longitude=-80.7,
227+
max_longitude=10.9,
228+
)
229+
assert opensky.calculate_credit_costs(bounding_box) == 4

0 commit comments

Comments
 (0)