33
44import asyncio
55import socket
6- from dataclasses import dataclass
6+ from dataclasses import dataclass , field
7+ from datetime import datetime , timedelta , timezone
78from importlib import metadata
89from 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 :
0 commit comments