"""Async Python client for electricitymaps.com.""" from __future__ import annotations from dataclasses import dataclass import logging import socket from typing import TYPE_CHECKING, Self from aiohttp import ClientError, ClientResponseError, ClientSession from .const import ApiEndpoints from .exceptions import ( ElectricityMapsConnectionError, ElectricityMapsConnectionTimeoutError, ElectricityMapsInvalidTokenError, ) from .models import ( CarbonIntensityHistory, HomeAssistantCarbonIntensityResponse, LatestCarbonIntensity, LatestPowerBreakdown, PowerBreakdownHistory, Zone, ZonesResponse, ) if TYPE_CHECKING: from .request import BaseRequest, CoordinatesRequest, ZoneRequest _LOGGER = logging.getLogger(__name__) @dataclass(kw_only=True) class ElectricityMaps: """ElectricityMaps API client.""" token: str session: ClientSession | None = None _close_session: bool = False async def _get( self, *, url: str, request: BaseRequest | None = None, unauthenticated: bool = False, ) -> str: """Execute a GET request against the API.""" if self.session is None: self.session = ClientSession() self._close_session = True headers = {} if unauthenticated else {"auth-token": self.token} _LOGGER.debug("Doing request: GET %s %s", url, str(request)) params = {} if request: params = request.get_request_parameters() try: async with self.session.get( url, headers=headers, params=params, ) as response: response.raise_for_status() response_text = await response.text() except TimeoutError as exception: msg = "Timeout occurred while connecting to the Electricity Maps API" raise ElectricityMapsConnectionTimeoutError(msg) from exception except ( ClientError, socket.gaierror, ) as exception: if isinstance(exception, ClientResponseError) and exception.status == 401: msg = "The given token is invalid" raise ElectricityMapsInvalidTokenError(msg) from exception msg = "Error occurred while communicating to the Electricity Maps API" raise ElectricityMapsConnectionError(msg) from exception _LOGGER.debug( "Got response with status %s and body: %s", response.status, response_text, ) return response_text async def carbon_intensity_for_home_assistant( self, request: CoordinatesRequest | ZoneRequest, ) -> HomeAssistantCarbonIntensityResponse: """Get carbon intensity.""" result = await self._get( url=ApiEndpoints.CARBON_INTENSITY_HA, request=request, ) return HomeAssistantCarbonIntensityResponse.from_json(result) async def latest_carbon_intensity( self, request: CoordinatesRequest | ZoneRequest, ) -> LatestCarbonIntensity: """Get latest carbon intensity.""" result = await self._get( url=ApiEndpoints.LATEST_CARBON_INTENSITY, request=request, ) return LatestCarbonIntensity.from_json(result) async def carbon_intensity_history( self, request: CoordinatesRequest | ZoneRequest, ) -> CarbonIntensityHistory: """Get carbon intensity history.""" result = await self._get( url=ApiEndpoints.HISTORY_CARBON_INTENSITY, request=request, ) return CarbonIntensityHistory.from_json(result) async def latest_power_breakdown( self, request: CoordinatesRequest | ZoneRequest, ) -> LatestPowerBreakdown: """Get latest power breakdown.""" result = await self._get( url=ApiEndpoints.LATEST_POWER_BREAKDOWN, request=request, ) return LatestPowerBreakdown.from_json(result) async def power_breakdown_history( self, request: CoordinatesRequest | ZoneRequest, ) -> PowerBreakdownHistory: """Get power breakdown history.""" result = await self._get( url=ApiEndpoints.HISTORY_POWER_BREAKDOWN, request=request, ) return PowerBreakdownHistory.from_json(result) async def zones(self) -> dict[str, Zone]: """Get a dict of zones where carbon intensity is available.""" result = await self._get(url=ApiEndpoints.ZONES, unauthenticated=True) return ZonesResponse.from_json(result).zones async def close(self) -> None: """Close open client session.""" if self.session and self._close_session: await self.session.close() async def __aenter__(self) -> Self: """Async enter.""" return self async def __aexit__(self, *_exc_info: object) -> None: """Async exit.""" await self.close()