diff --git a/README.md b/README.md index 13b882d..3f736d5 100644 --- a/README.md +++ b/README.md @@ -48,26 +48,28 @@ pip install 'tracarbon[datadog,prometheus,kubernetes]' | Datadog | Send the metrics to Datadog. | ### πŸ—ΊοΈ Locations -| **Location** | **Description** | **Source** | -|--------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Worldwide | Get the latest co2g/kwh in near real-time using the CO2 Signal API. See [here](http://api.electricitymap.org/v3/zones) for the list of available zones. | [CO2Signal API](https://www.co2signal.com) | -| Europe | Static file created from the European Environment Agency Emission for the co2g/kwh in European countries. | [EEA website](https://www.eea.europa.eu/data-and-maps/daviz/co2-emission-intensity-9#tab-googlechartid_googlechartid_googlechartid_googlechartid_chart_11111) | -| AWS | Static file of the AWS Grid emissions factors. | [cloud-carbon-coefficients](https://github.com/cloud-carbon-footprint/cloud-carbon-coefficients/blob/main/data/grid-emissions-factors-aws.csv) | +| **Location** | **Description** | **Source** | +|--------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Worldwide | Get the latest co2g/kwh in near real-time using the CO2Signal or ElectricityMaps APIs. See [here](http://api.electricitymap.org/v3/zones) for the list of available zones. | [CO2Signal API](https://www.co2signal.com) or [ElectricityMaps](https://static.electricitymaps.com/api/docs/index.html) | +| Europe | Static file created from the European Environment Agency Emission for the co2g/kwh in European countries. | [EEA website](https://www.eea.europa.eu/data-and-maps/daviz/co2-emission-intensity-9#tab-googlechartid_googlechartid_googlechartid_googlechartid_chart_11111) | +| AWS | Static file of the AWS Grid emissions factors. | [cloud-carbon-coefficients](https://github.com/cloud-carbon-footprint/cloud-carbon-coefficients/blob/main/data/grid-emissions-factors-aws.csv) | ### βš™οΈ Configuration The environment variables can be set from an environment file `.env`. -| **Parameter** | **Description** | -|-------------------------------|:-------------------------------------------------------------------------------| -| TRACARBON_CO2SIGNAL_API_KEY | The api key received from [CO2 Signal](https://www.co2signal.com). | -| TRACARBON_METRIC_PREFIX_NAME | The prefix to use in all the metrics name. | -| TRACARBON_INTERVAL_IN_SECONDS | The interval in seconds to wait between the metrics evaluation. | -| TRACARBON_LOG_LEVEL | The level to use for displaying the logs. | +| **Parameter** | **Description** | +|-------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| TRACARBON_CO2SIGNAL_API_KEY | The api key received from [CO2Signal](https://www.co2signal.com) or [ElectricityMaps](https://static.electricitymaps.com/api/docs/index.html). | +| TRACARBON_CO2SIGNAL_URL | The url of [CO2Signal](https://docs.co2signal.com/#get-latest-by-country-code) is the default endpoint to retrieve the last known state of the zone, but it could be changed to [ElectricityMaps](https://static.electricitymaps.com/api/docs/index.html#live-carbon-intensity). | +| TRACARBON_METRIC_PREFIX_NAME | The prefix to use in all the metrics name. | +| TRACARBON_INTERVAL_IN_SECONDS | The interval in seconds to wait between the metrics evaluation. | +| TRACARBON_LOG_LEVEL | The level to use for displaying the logs. | ## πŸ”Ž Usage **Request your API key** -- Go to https://www.co2signal.com/ and get your free API key (for non-commercial use only) for getting the latest carbon intensity from your location in near-real time. +- Go to [CO2Signal](https://www.co2signal.com/) and get your free API key for non-commercial use, or go to [ElectricityMaps](https://static.electricitymaps.com/api/docs/index.html) for commercial use. +- This API is used to retrieve the last known carbon intensity (in gCO2eq/kWh) of electricity consumed in your location. - Set your API key in the environment variables, in the `.env` file or directly in the configuration. - If you would like to start without an API key, it's possible, the carbon intensity will be loaded statistically from a file. - Launch Tracarbon πŸš€ diff --git a/helm/tracarbon/templates/daemonset.yaml b/helm/tracarbon/templates/daemonset.yaml index fb2ed13..71182e4 100644 --- a/helm/tracarbon/templates/daemonset.yaml +++ b/helm/tracarbon/templates/daemonset.yaml @@ -24,9 +24,13 @@ spec: - {{ . }} {{ end }} env: - {{- if .Values.tracarbon.co2_signal_api_key }} + {{- if .Values.tracarbon.co2signal_api_key }} - name: TRACARBON_CO2SIGNAL_API_KEY - value: '{{ .Values.tracarbon.co2_signal_api_key }}' + value: '{{ .Values.tracarbon.co2signal_api_key }}' + {{- end }} + {{- if .Values.tracarbon.co2signal_url }} + - name: TRACARBON_CO2SIGNAL_URL + value: '{{ .Values.tracarbon.co2signal_url }}' {{- end }} - name: TRACARBON_INTERVAL_IN_SECONDS value: '{{ .Values.tracarbon.interval_in_seconds }}' diff --git a/helm/tracarbon/values.yaml b/helm/tracarbon/values.yaml index b86b16e..3311630 100644 --- a/helm/tracarbon/values.yaml +++ b/helm/tracarbon/values.yaml @@ -15,7 +15,7 @@ tracarbon: args: - --exporter-name=Prometheus - --containers - co2_signal_api_key: "" + co2signal_api_key: "" interval_in_seconds: 60 log_level: "INFO" metric_prefix_name: "tracarbon" diff --git a/poetry.lock b/poetry.lock index ed3d9d6..5da6cd8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2330,4 +2330,4 @@ prometheus = ["prometheus-client"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d484176deae5ae3ae487217883ca7cb858b1ea492e84fedaa400f87e7b2fb9d3" +content-hash = "3de89785788f17415f7bb3bde5fd82e6f69d0c3c2bf1e574a5b905525f3af6a0" diff --git a/pyproject.toml b/pyproject.toml index 03f984c..ea19286 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ pytest-cov = "^4.0.0" pytest-xdist = "^3.1.0" pytest-clarity = "^1.0.1" sphinx = "^6.1.3" -pydata-sphinx-theme = "^0.13.0" +pydata-sphinx-theme = "^0.13.1" toml = "^0.10.2" types-ujson = "^5.7.0" datadog = "^0.44.0" diff --git a/tests/locations/test_country.py b/tests/locations/test_country.py index 9df5adb..93a9c94 100644 --- a/tests/locations/test_country.py +++ b/tests/locations/test_country.py @@ -30,7 +30,40 @@ async def test_france_location_should_return_latest_known(mocker): assert result == co2_expected assert country.name == "fr" - assert country.co2g_kwh == 51.1 + assert country.co2g_kwh == co2_expected + assert country.co2g_kwh_source == CarbonIntensitySource.CO2SignalAPI + + +@pytest.mark.asyncio +async def test_france_location_with_recent_api_versions_should_return_latest_known( + mocker, +): + co2_expected = 83 + response = { + "zone": "FR", + "carbonIntensity": co2_expected, + "datetime": "2023-03-20T17:00:00.000Z", + "updatedAt": "2023-03-20T16:51:02.892Z", + "createdAt": "2023-03-17T17:54:01.319Z", + "emissionFactorType": "lifecycle", + "isEstimated": True, + "estimationMethod": "TIME_SLICER_AVERAGE", + } + mocker.patch.object(Country, "request", return_value=response) + co2signal_api_key = "API_KEY" + + country = Country( + name="fr", + co2signal_api_key=co2signal_api_key, + co2g_kwh_source=CarbonIntensitySource.CO2SignalAPI, + co2g_kwh=co2_expected, + ) + + result = await country.get_latest_co2g_kwh() + + assert result == co2_expected + assert country.name == "fr" + assert country.co2g_kwh == co2_expected assert country.co2g_kwh_source == CarbonIntensitySource.CO2SignalAPI diff --git a/tracarbon/builder.py b/tracarbon/builder.py index b91cc0a..8d538d5 100644 --- a/tracarbon/builder.py +++ b/tracarbon/builder.py @@ -29,6 +29,7 @@ def __init__( else: self.location = Country.get_location( co2signal_api_key=self.configuration.co2signal_api_key, + co2signal_url=self.configuration.co2signal_url, ) if exporter: self.exporter = exporter diff --git a/tracarbon/cli/__init__.py b/tracarbon/cli/__init__.py index de3c50f..fc916a1 100644 --- a/tracarbon/cli/__init__.py +++ b/tracarbon/cli/__init__.py @@ -93,6 +93,7 @@ def run_metrics( tracarbon_builder = TracarbonBuilder() location = Country.get_location( co2signal_api_key=tracarbon_builder.configuration.co2signal_api_key, + co2signal_url=tracarbon_builder.configuration.co2signal_url, country_code_alpha_iso_2=country_code_alpha_iso_2, ) metric_generators: List[MetricGenerator] = [ diff --git a/tracarbon/conf.py b/tracarbon/conf.py index dca0a61..96f100c 100755 --- a/tracarbon/conf.py +++ b/tracarbon/conf.py @@ -48,6 +48,7 @@ class TracarbonConfiguration(BaseModel): log_level: str interval_in_seconds: int co2signal_api_key: str + co2signal_url: str def __init__( self, @@ -55,6 +56,7 @@ def __init__( interval_in_seconds: int = 60, log_level: str = "INFO", co2signal_api_key: str = "", + co2signal_url: str = "https://api.co2signal.com/v1/latest?countryCode=", env_file_path: Optional[str] = None, **data: Any, ) -> None: @@ -72,5 +74,6 @@ def __init__( co2signal_api_key=os.environ.get( "TRACARBON_CO2SIGNAL_API_KEY", co2signal_api_key ), + co2signal_url=os.environ.get("TRACARBON_CO2SIGNAL_URL", co2signal_url), **data, ) diff --git a/tracarbon/exporters/prometheus_exporter.py b/tracarbon/exporters/prometheus_exporter.py index 34b4662..b54cce0 100644 --- a/tracarbon/exporters/prometheus_exporter.py +++ b/tracarbon/exporters/prometheus_exporter.py @@ -53,7 +53,7 @@ async def launch(self, metric_generator: MetricGenerator) -> None: ) metric_value = await metric.value() logger.info( - f"Sending metric[{metric_name}] with value [{metric_value}] and labels{metric.format_tags()} to Prometeus." + f"Sending metric[{metric_name}] with value [{metric_value}] and labels{metric.format_tags()} to Prometheus." ) self.prometheus_metrics[metric_name].labels( *[tag.value for tag in metric.tags] diff --git a/tracarbon/general_metrics.py b/tracarbon/general_metrics.py index 6ac7ac9..898bc9c 100644 --- a/tracarbon/general_metrics.py +++ b/tracarbon/general_metrics.py @@ -60,6 +60,9 @@ def __init__(self, location: Location, **data: Any) -> None: co2signal_api_key=data["co2signal_api_key"] if "co2signal_api_key" in data else location.co2signal_api_key, + co2signal_url=data["co2signal_url"] + if "co2signal_url" in data + else location.co2signal_url, location=location, ) super().__init__(location=location, metrics=[], **data) @@ -184,6 +187,9 @@ def __init__(self, location: Location, **data: Any) -> None: co2signal_api_key=data["co2signal_api_key"] if "co2signal_api_key" in data else location.co2signal_api_key, + co2signal_url=data["co2signal_url"] + if "co2signal_url" in data + else location.co2signal_url, location=location, ) if "kubernetes" not in data: diff --git a/tracarbon/locations/country.py b/tracarbon/locations/country.py index b45fa94..92e1fa5 100644 --- a/tracarbon/locations/country.py +++ b/tracarbon/locations/country.py @@ -56,7 +56,7 @@ def get_current_country( logger.debug(f"Send request to this url: {url}, timeout {timeout}s") text = requests.get(url, timeout=timeout).text content_json = ujson.loads(text) - return content_json["country"].lower() + return content_json["country"] except Exception as exception: logger.error(f"Failed to request this url: {url}") raise exception @@ -65,6 +65,7 @@ def get_current_country( def get_location( cls, co2signal_api_key: Optional[str] = None, + co2signal_url: Optional[str] = None, country_code_alpha_iso_2: Optional[str] = None, ) -> "Country": """ @@ -72,6 +73,7 @@ def get_location( :param country_code_alpha_iso_2: the alpha iso 2 country name. :param co2signal_api_key: api key for fetching CO2 Signal API. + :param co2signal_url: api url for fetching CO2 Signal API endpoint. :return: the country """ # Cloud Providers @@ -85,6 +87,7 @@ def get_location( if co2signal_api_key: return cls( co2signal_api_key=co2signal_api_key, + co2signal_url=co2signal_url, name=country_code_alpha_iso_2, co2g_kwh_source=CarbonIntensitySource.CO2SignalAPI, ) @@ -107,12 +110,16 @@ async def get_latest_co2g_kwh(self) -> float: ) if not self.co2signal_api_key: raise CO2SignalAPIKeyIsMissing() + url = f"{self.co2signal_url}{self.name}" response = await self.request( - url=f"https://api.co2signal.com/v1/latest?countryCode={self.name}", + url=url, headers={"auth-token": self.co2signal_api_key}, ) try: - self.co2g_kwh = float(response["data"]["carbonIntensity"]) + logger.debug(f"Response from the {url}: {response}.") + if "data" in response: + response = response["data"] + self.co2g_kwh = float(response["carbonIntensity"]) logger.info( f"The latest carbon intensity of your country {self.name} is: {self.co2g_kwh} CO2g/kwh." ) diff --git a/tracarbon/locations/location.py b/tracarbon/locations/location.py index 75e9484..74113b3 100755 --- a/tracarbon/locations/location.py +++ b/tracarbon/locations/location.py @@ -22,6 +22,7 @@ class Location(ABC, BaseModel): name: str co2g_kwh_source: CarbonIntensitySource = CarbonIntensitySource.FILE co2signal_api_key: Optional[str] = None + co2signal_url: Optional[str] = None co2g_kwh: float = 0.0 @classmethod