"""Utility functions for working with the OpenWeatherMap API."""importloggingfromcollections.abcimportCallablefromdecimalimportDecimalfromfunctoolsimportwrapsimportrequestsfromdjango.appsimportappsfromdjango.utilsimporttimezonefrom..app_settingsimportOWM_API_KEYfrom..app_settingsimportOWM_API_RATE_LIMITSfrom..app_settingsimportOWM_MODEL_MAPPINGSlogger=logging.getLogger(__name__)
[docs]defget_api_call_counts(api_name:str)->tuple[int,int]:"""Get the number of API calls made in the last minute and last month."""now=timezone.now()one_minute_ago=now-timezone.timedelta(minutes=1)one_month_ago=now-timezone.timedelta(days=30)model_string=OWM_MODEL_MAPPINGS.get("APICallLog")APICallLog=apps.get_model(model_string)ifmodel_stringelseNoneifnotAPICallLog:return0,0calls_last_minute=APICallLog.objects.filter(api_name=api_name,timestamp__gte=one_minute_ago).count()calls_last_month=APICallLog.objects.filter(api_name=api_name,timestamp__gte=one_month_ago).count()returncalls_last_minute,calls_last_month
[docs]defcheck_api_limits(func:Callable)->Callable:"""Decorator to check API call limits before running a task."""@wraps(func)defwrapper(*args,**kwargs)->bool|Callable:"""Check API call limits before running the task."""api_name="one_call"rate_limits=OWM_API_RATE_LIMITS.get(api_name,{})calls_per_minute=rate_limits.get("calls_per_minute",60)calls_per_month=rate_limits.get("calls_per_month",1000000)calls_last_minute,calls_last_month=get_api_call_counts(api_name)ifcalls_last_minute>=calls_per_minuteorcalls_last_month>=calls_per_month:logger.warning("API call limit exceeded. Skipping task.")returnFalsereturnfunc(*args,**kwargs)returnwrapper
[docs]deflog_api_call(api_name:str)->None:"""Log an API call to the database."""model_string=OWM_MODEL_MAPPINGS.get("APICallLog")APICallLog=apps.get_model(model_string)ifmodel_stringelseNoneifAPICallLog:APICallLog.objects.create(api_name=api_name)
[docs]defmake_api_call(lat:Decimal,lon:Decimal,exclude:list[str]|None=None)->dict|None:"""Make an API call to OpenWeatherMap."""api_key=OWM_API_KEYifnotapi_key:logger.error("OpenWeatherMap API key not set. Please set OWM_API_KEY in your settings.")returnNoneexclude=""ifexcludeisnotNone:exclude=",".join(exclude)url="https://api.openweathermap.org/data/3.0/onecall?"f"lat={lat}&lon={lon}&exclude={exclude}&appid={api_key}"try:response=requests.get(url,timeout=10)ifresponse.status_code==200:ifhasattr(response,"json"):data=response.json()elifhasattr(response,"json_response"):data=response.json_response()else:logger.error("Error parsing JSON response.")returnNone# Convert relevant float values to Decimalcurrent=data.get("current",{})forkeyin["temp","feels_like","dew_point","uvi","wind_speed","wind_gust"]:ifkeyincurrent:current[key]=str(current[key])returndatalogger.error("Error fetching weather data: %s",response.text)returnNoneexceptrequests.RequestExceptionase:logger.exception("Error fetching weather data: %s",e)returnNone