codecarbon 3.1.0__tar.gz → 3.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecarbon-3.1.0 → codecarbon-3.1.1}/PKG-INFO +7 -3
- {codecarbon-3.1.0 → codecarbon-3.1.1}/README.md +6 -2
- codecarbon-3.1.1/codecarbon/_version.py +1 -0
- codecarbon-3.1.0/codecarbon/core/co2_signal.py → codecarbon-3.1.1/codecarbon/core/electricitymaps_api.py +22 -15
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/emissions.py +28 -6
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/emissions_tracker.py +55 -12
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/file.py +59 -7
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/PKG-INFO +7 -3
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/SOURCES.txt +4 -2
- {codecarbon-3.1.0 → codecarbon-3.1.1}/pyproject.toml +1 -1
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_config.py +3 -2
- codecarbon-3.1.1/tests/test_electricitymaps_api.py +45 -0
- codecarbon-3.1.1/tests/test_electricitymaps_backward_compatibility.py +80 -0
- codecarbon-3.1.1/tests/test_electricitymaps_config_backward_compatibility.py +63 -0
- codecarbon-3.1.0/codecarbon/_version.py +0 -1
- codecarbon-3.1.0/tests/test_co2_signal.py +0 -47
- {codecarbon-3.1.0 → codecarbon-3.1.1}/LICENSE +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/cli/main.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/api_client.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/config.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/cpu.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/gpu.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/measure.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/powermetrics.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/rapl.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/resource_tracker.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/schemas.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/units.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/core/util.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/hardware/cpu_power.csv +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/geography.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/hardware.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/logger.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/ram.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/external/task.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/input.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/lock.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/base_output.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/carbonboard.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/requires.txt +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/setup.cfg +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_api_call.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_cli.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_cloud.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_core_util.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_cpu.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_cpu_load.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_custom_handler.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_emissions.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_emissions_tracker.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_emissions_tracker_constant.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_emissions_tracker_flush.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_energy.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_geography.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_gpu.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_lock.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_logging_output.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_offline_emissions_tracker.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_powermetrics.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_ram.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_rapl_mmio_scanning.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_rapl_permissions.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_viz_data.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/test_viz_units.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/testdata.py +0 -0
- {codecarbon-3.1.0 → codecarbon-3.1.1}/tests/testutils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://codecarbon.io/
|
|
@@ -220,7 +220,7 @@ In order to contribute a change to our code base, please submit a pull request (
|
|
|
220
220
|
|
|
221
221
|
Check out our [contribution guidelines :arrow_upper_right:](https://github.com/mlco2/codecarbon/blob/master/CONTRIBUTING.md)
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
224
224
|
|
|
225
225
|
# How To Cite 📝
|
|
226
226
|
|
|
@@ -271,7 +271,11 @@ Here is a sample for BibTeX:
|
|
|
271
271
|
|
|
272
272
|
# Contact 📝
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
275
|
+
|
|
276
|
+
Codecarbon was formerly developed by volunteers from [**Mila**](http://mila.quebec) and the [**DataForGoodFR**](https://twitter.com/dataforgood_fr) community alongside donated professional time of engineers at [**Comet.ml**](https://comet.ml) and [**BCG GAMMA**](https://www.bcg.com/en-nl/beyond-consulting/bcg-gamma/default).
|
|
277
|
+
|
|
278
|
+
Now CodeCarbon is supported by [**Code Carbon**](https://www.helloasso.com/associations/code-carbon), a French non-profit organization whose mission is to accelerate the development and adoption of CodeCarbon.
|
|
275
279
|
|
|
276
280
|
## Star History
|
|
277
281
|
|
|
@@ -143,7 +143,7 @@ In order to contribute a change to our code base, please submit a pull request (
|
|
|
143
143
|
|
|
144
144
|
Check out our [contribution guidelines :arrow_upper_right:](https://github.com/mlco2/codecarbon/blob/master/CONTRIBUTING.md)
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
147
147
|
|
|
148
148
|
# How To Cite 📝
|
|
149
149
|
|
|
@@ -194,7 +194,11 @@ Here is a sample for BibTeX:
|
|
|
194
194
|
|
|
195
195
|
# Contact 📝
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
198
|
+
|
|
199
|
+
Codecarbon was formerly developed by volunteers from [**Mila**](http://mila.quebec) and the [**DataForGoodFR**](https://twitter.com/dataforgood_fr) community alongside donated professional time of engineers at [**Comet.ml**](https://comet.ml) and [**BCG GAMMA**](https://www.bcg.com/en-nl/beyond-consulting/bcg-gamma/default).
|
|
200
|
+
|
|
201
|
+
Now CodeCarbon is supported by [**Code Carbon**](https://www.helloasso.com/associations/code-carbon), a French non-profit organization whose mission is to accelerate the development and adoption of CodeCarbon.
|
|
198
202
|
|
|
199
203
|
## Star History
|
|
200
204
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.1.1"
|
|
@@ -5,19 +5,19 @@ import requests
|
|
|
5
5
|
from codecarbon.core.units import EmissionsPerKWh, Energy
|
|
6
6
|
from codecarbon.external.geography import GeoMetadata
|
|
7
7
|
|
|
8
|
-
URL: str = "https://api.
|
|
9
|
-
|
|
8
|
+
URL: str = "https://api.electricitymaps.com/v3/carbon-intensity/latest"
|
|
9
|
+
ELECTRICITYMAPS_API_TIMEOUT: int = 30
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def get_emissions(
|
|
13
|
-
energy: Energy, geo: GeoMetadata,
|
|
13
|
+
energy: Energy, geo: GeoMetadata, electricitymaps_api_token: str = ""
|
|
14
14
|
) -> float:
|
|
15
15
|
"""
|
|
16
16
|
Calculate the CO2 emissions based on energy consumption and geographic location.
|
|
17
17
|
|
|
18
|
-
This function retrieves the carbon intensity (in grams of CO2 per kWh) from the
|
|
19
|
-
|
|
20
|
-
CO2 emissions for a given amount of energy consumption.
|
|
18
|
+
This function retrieves the carbon intensity (in grams of CO2 per kWh) from the
|
|
19
|
+
Electricity Maps API (formerly CO2 Signal) based on the geographic location provided.
|
|
20
|
+
It then calculates the total CO2 emissions for a given amount of energy consumption.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
23
|
energy (Energy):
|
|
@@ -25,8 +25,8 @@ def get_emissions(
|
|
|
25
25
|
geo (GeoMetadata):
|
|
26
26
|
Geographic metadata, including either latitude/longitude
|
|
27
27
|
or a country code.
|
|
28
|
-
|
|
29
|
-
The API token for authenticating with the
|
|
28
|
+
electricitymaps_api_token (str, optional):
|
|
29
|
+
The API token for authenticating with the Electricity Maps API (default is an empty string).
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
32
|
float:
|
|
@@ -34,8 +34,8 @@ def get_emissions(
|
|
|
34
34
|
carbon intensity of the specified geographic location.
|
|
35
35
|
|
|
36
36
|
Raises:
|
|
37
|
-
|
|
38
|
-
If the
|
|
37
|
+
ElectricityMapsAPIError:
|
|
38
|
+
If the Electricity Maps API request fails or returns an error.
|
|
39
39
|
"""
|
|
40
40
|
params: Dict[str, Any]
|
|
41
41
|
if geo.latitude:
|
|
@@ -45,18 +45,25 @@ def get_emissions(
|
|
|
45
45
|
resp = requests.get(
|
|
46
46
|
URL,
|
|
47
47
|
params=params,
|
|
48
|
-
headers={"auth-token":
|
|
49
|
-
timeout=
|
|
48
|
+
headers={"auth-token": electricitymaps_api_token},
|
|
49
|
+
timeout=ELECTRICITYMAPS_API_TIMEOUT,
|
|
50
50
|
)
|
|
51
51
|
if resp.status_code != 200:
|
|
52
52
|
message = resp.json().get("error") or resp.json().get("message")
|
|
53
|
-
raise
|
|
54
|
-
|
|
53
|
+
raise ElectricityMapsAPIError(message)
|
|
54
|
+
|
|
55
|
+
# API v3 response structure: carbonIntensity is at the root level
|
|
56
|
+
response_data = resp.json()
|
|
57
|
+
carbon_intensity_g_per_kWh = response_data.get("carbonIntensity")
|
|
58
|
+
|
|
59
|
+
if carbon_intensity_g_per_kWh is None:
|
|
60
|
+
raise ElectricityMapsAPIError("No carbonIntensity data in response")
|
|
61
|
+
|
|
55
62
|
emissions_per_kWh: EmissionsPerKWh = EmissionsPerKWh.from_g_per_kWh(
|
|
56
63
|
carbon_intensity_g_per_kWh
|
|
57
64
|
)
|
|
58
65
|
return emissions_per_kWh.kgs_per_kWh * energy.kWh
|
|
59
66
|
|
|
60
67
|
|
|
61
|
-
class
|
|
68
|
+
class ElectricityMapsAPIError(Exception):
|
|
62
69
|
pass
|
|
@@ -10,7 +10,7 @@ from typing import Dict, Optional
|
|
|
10
10
|
|
|
11
11
|
import pandas as pd
|
|
12
12
|
|
|
13
|
-
from codecarbon.core import
|
|
13
|
+
from codecarbon.core import electricitymaps_api
|
|
14
14
|
from codecarbon.core.units import EmissionsPerKWh, Energy
|
|
15
15
|
from codecarbon.external.geography import CloudMetadata, GeoMetadata
|
|
16
16
|
from codecarbon.external.logger import logger
|
|
@@ -19,10 +19,25 @@ from codecarbon.input import DataSource, DataSourceException
|
|
|
19
19
|
|
|
20
20
|
class Emissions:
|
|
21
21
|
def __init__(
|
|
22
|
-
self,
|
|
22
|
+
self,
|
|
23
|
+
data_source: DataSource,
|
|
24
|
+
electricitymaps_api_token: Optional[str] = None,
|
|
25
|
+
co2_signal_api_token: Optional[
|
|
26
|
+
str
|
|
27
|
+
] = None, # Deprecated, for backward compatibility
|
|
23
28
|
):
|
|
24
29
|
self._data_source = data_source
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
# Handle backward compatibility
|
|
32
|
+
if co2_signal_api_token is not None:
|
|
33
|
+
logger.warning(
|
|
34
|
+
"Parameter 'co2_signal_api_token' is deprecated and will be removed in a future version. "
|
|
35
|
+
"Please use 'electricitymaps_api_token' instead."
|
|
36
|
+
)
|
|
37
|
+
if electricitymaps_api_token is None:
|
|
38
|
+
electricitymaps_api_token = co2_signal_api_token
|
|
39
|
+
|
|
40
|
+
self._electricitymaps_api_token = electricitymaps_api_token
|
|
26
41
|
|
|
27
42
|
def get_cloud_emissions(
|
|
28
43
|
self, energy: Energy, cloud: CloudMetadata, geo: GeoMetadata = None
|
|
@@ -123,12 +138,19 @@ class Emissions:
|
|
|
123
138
|
:param geo: Country and region metadata
|
|
124
139
|
:return: CO2 emissions in kg
|
|
125
140
|
"""
|
|
126
|
-
if self.
|
|
141
|
+
if self._electricitymaps_api_token:
|
|
127
142
|
try:
|
|
128
|
-
|
|
143
|
+
emissions = electricitymaps_api.get_emissions(
|
|
144
|
+
energy, geo, self._electricitymaps_api_token
|
|
145
|
+
)
|
|
146
|
+
logger.debug(
|
|
147
|
+
"electricitymaps_api.get_emissions: "
|
|
148
|
+
+ f"Retrieved emissions for {geo.country_name} using Electricity Maps API :{emissions * 1000} g CO2eq"
|
|
149
|
+
)
|
|
150
|
+
return emissions
|
|
129
151
|
except Exception as e:
|
|
130
152
|
logger.error(
|
|
131
|
-
"
|
|
153
|
+
"electricitymaps_api.get_emissions: "
|
|
132
154
|
+ str(e)
|
|
133
155
|
+ " >>> Using CodeCarbon's data."
|
|
134
156
|
)
|
|
@@ -171,7 +171,10 @@ class BaseEmissionsTracker(ABC):
|
|
|
171
171
|
emissions_endpoint: Optional[str] = _sentinel,
|
|
172
172
|
experiment_id: Optional[str] = _sentinel,
|
|
173
173
|
experiment_name: Optional[str] = _sentinel,
|
|
174
|
-
|
|
174
|
+
electricitymaps_api_token: Optional[str] = _sentinel,
|
|
175
|
+
co2_signal_api_token: Optional[
|
|
176
|
+
str
|
|
177
|
+
] = _sentinel, # Deprecated, use electricitymaps_api_token
|
|
175
178
|
tracking_mode: Optional[str] = _sentinel,
|
|
176
179
|
log_level: Optional[Union[int, str]] = _sentinel,
|
|
177
180
|
on_csv_write: Optional[str] = _sentinel,
|
|
@@ -221,8 +224,9 @@ class BaseEmissionsTracker(ABC):
|
|
|
221
224
|
data.
|
|
222
225
|
:param experiment_id: Id of the experiment.
|
|
223
226
|
:param experiment_name: Label of the experiment
|
|
224
|
-
:param
|
|
225
|
-
|
|
227
|
+
:param electricitymaps_api_token: API token for electricitymaps.com (formerly co2signal.com)
|
|
228
|
+
:param co2_signal_api_token: [DEPRECATED] Use electricitymaps_api_token instead.
|
|
229
|
+
Old parameter name for backward compatibility.
|
|
226
230
|
:param tracking_mode: One of "process" or "machine" in order to measure the
|
|
227
231
|
power consumption due to the entire machine or to try and
|
|
228
232
|
isolate the tracked processe's in isolation.
|
|
@@ -268,7 +272,31 @@ class BaseEmissionsTracker(ABC):
|
|
|
268
272
|
self._set_from_conf(api_call_interval, "api_call_interval", 8, int)
|
|
269
273
|
self._set_from_conf(api_endpoint, "api_endpoint", "https://api.codecarbon.io")
|
|
270
274
|
self._set_from_conf(api_key, "api_key", "api_key")
|
|
271
|
-
|
|
275
|
+
|
|
276
|
+
# Handle backward compatibility for co2_signal_api_token
|
|
277
|
+
if co2_signal_api_token is not _sentinel:
|
|
278
|
+
logger.warning(
|
|
279
|
+
"Parameter 'co2_signal_api_token' is deprecated and will be removed in a future version. "
|
|
280
|
+
"Please use 'electricitymaps_api_token' instead."
|
|
281
|
+
)
|
|
282
|
+
if electricitymaps_api_token is _sentinel:
|
|
283
|
+
electricitymaps_api_token = co2_signal_api_token
|
|
284
|
+
|
|
285
|
+
self._set_from_conf(electricitymaps_api_token, "electricitymaps_api_token")
|
|
286
|
+
# Also check for old config name for backward compatibility
|
|
287
|
+
if (
|
|
288
|
+
not hasattr(self, "_electricitymaps_api_token")
|
|
289
|
+
or self._electricitymaps_api_token is None
|
|
290
|
+
):
|
|
291
|
+
self._set_from_conf(_sentinel, "co2_signal_api_token", prevent_setter=True)
|
|
292
|
+
old_token = self._external_conf.get("co2_signal_api_token")
|
|
293
|
+
if old_token:
|
|
294
|
+
logger.warning(
|
|
295
|
+
"Configuration parameter 'co2_signal_api_token' is deprecated. "
|
|
296
|
+
"Please update your config to use 'electricitymaps_api_token' instead."
|
|
297
|
+
)
|
|
298
|
+
self._electricitymaps_api_token = old_token
|
|
299
|
+
|
|
272
300
|
self._set_from_conf(emissions_endpoint, "emissions_endpoint")
|
|
273
301
|
self._set_from_conf(experiment_name, "experiment_name", "base")
|
|
274
302
|
self._set_from_conf(gpu_ids, "gpu_ids")
|
|
@@ -378,7 +406,7 @@ class BaseEmissionsTracker(ABC):
|
|
|
378
406
|
self._conf["provider"] = cloud.provider
|
|
379
407
|
|
|
380
408
|
self._emissions: Emissions = Emissions(
|
|
381
|
-
self._data_source, self.
|
|
409
|
+
self._data_source, self._electricitymaps_api_token
|
|
382
410
|
)
|
|
383
411
|
self._init_output_methods(api_key=self._api_key)
|
|
384
412
|
|
|
@@ -956,7 +984,7 @@ class OfflineEmissionsTracker(BaseEmissionsTracker):
|
|
|
956
984
|
See https://github.com/mlco2/codecarbon/
|
|
957
985
|
blob/master/codecarbon/data/cloud/impact.csv
|
|
958
986
|
for a list of cloud regions.
|
|
959
|
-
:param country_2letter_iso_code: For use with the
|
|
987
|
+
:param country_2letter_iso_code: For use with the Electricity Maps emissions API.
|
|
960
988
|
See http://api.electricitymap.org/v3/zones for
|
|
961
989
|
a list of codes and their corresponding
|
|
962
990
|
locations.
|
|
@@ -1095,7 +1123,10 @@ def track_emissions(
|
|
|
1095
1123
|
emissions_endpoint: Optional[str] = _sentinel,
|
|
1096
1124
|
experiment_id: Optional[str] = _sentinel,
|
|
1097
1125
|
experiment_name: Optional[str] = _sentinel,
|
|
1098
|
-
|
|
1126
|
+
electricitymaps_api_token: Optional[str] = _sentinel,
|
|
1127
|
+
co2_signal_api_token: Optional[
|
|
1128
|
+
str
|
|
1129
|
+
] = _sentinel, # Deprecated, use electricitymaps_api_token
|
|
1099
1130
|
tracking_mode: Optional[str] = _sentinel,
|
|
1100
1131
|
log_level: Optional[Union[int, str]] = _sentinel,
|
|
1101
1132
|
on_csv_write: Optional[str] = _sentinel,
|
|
@@ -1150,8 +1181,9 @@ def track_emissions(
|
|
|
1150
1181
|
data.
|
|
1151
1182
|
:param experiment_id: Id of the experiment.
|
|
1152
1183
|
:param experiment_name: Label of the experiment
|
|
1153
|
-
:param
|
|
1154
|
-
|
|
1184
|
+
:param electricitymaps_api_token: API token for electricitymaps.com (formerly co2signal.com)
|
|
1185
|
+
:param co2_signal_api_token: [DEPRECATED] Use electricitymaps_api_token instead.
|
|
1186
|
+
Old parameter name for backward compatibility.
|
|
1155
1187
|
:param tracking_mode: One of "process" or "machine" in order to measure the
|
|
1156
1188
|
power consumption due to the entire machine or to try and
|
|
1157
1189
|
isolate the tracked processe's in isolation.
|
|
@@ -1180,7 +1212,7 @@ def track_emissions(
|
|
|
1180
1212
|
See https://github.com/mlco2/codecarbon/
|
|
1181
1213
|
blob/master/codecarbon/data/cloud/impact.csv
|
|
1182
1214
|
for a list of cloud regions.
|
|
1183
|
-
:param country_2letter_iso_code: For use with the
|
|
1215
|
+
:param country_2letter_iso_code: For use with the Electricity Maps emissions API.
|
|
1184
1216
|
See http://api.electricitymap.org/v3/zones for
|
|
1185
1217
|
a list of codes and their corresponding
|
|
1186
1218
|
locations.
|
|
@@ -1199,6 +1231,17 @@ def track_emissions(
|
|
|
1199
1231
|
@wraps(fn)
|
|
1200
1232
|
def wrapped_fn(*args, **kwargs):
|
|
1201
1233
|
fn_result = None
|
|
1234
|
+
|
|
1235
|
+
# Handle backward compatibility for co2_signal_api_token
|
|
1236
|
+
_electricitymaps_token = electricitymaps_api_token
|
|
1237
|
+
if co2_signal_api_token is not _sentinel:
|
|
1238
|
+
logger.warning(
|
|
1239
|
+
"Parameter 'co2_signal_api_token' is deprecated and will be removed in a future version. "
|
|
1240
|
+
"Please use 'electricitymaps_api_token' instead."
|
|
1241
|
+
)
|
|
1242
|
+
if electricitymaps_api_token is _sentinel:
|
|
1243
|
+
_electricitymaps_token = co2_signal_api_token
|
|
1244
|
+
|
|
1202
1245
|
if offline and offline is not _sentinel:
|
|
1203
1246
|
if (country_iso_code is None or country_iso_code is _sentinel) and (
|
|
1204
1247
|
cloud_provider is None or cloud_provider is _sentinel
|
|
@@ -1217,7 +1260,7 @@ def track_emissions(
|
|
|
1217
1260
|
prometheus_url=prometheus_url,
|
|
1218
1261
|
output_handlers=output_handlers,
|
|
1219
1262
|
gpu_ids=gpu_ids,
|
|
1220
|
-
|
|
1263
|
+
electricitymaps_api_token=_electricitymaps_token,
|
|
1221
1264
|
tracking_mode=tracking_mode,
|
|
1222
1265
|
log_level=log_level,
|
|
1223
1266
|
on_csv_write=on_csv_write,
|
|
@@ -1256,7 +1299,7 @@ def track_emissions(
|
|
|
1256
1299
|
emissions_endpoint=emissions_endpoint,
|
|
1257
1300
|
experiment_id=experiment_id,
|
|
1258
1301
|
experiment_name=experiment_name,
|
|
1259
|
-
|
|
1302
|
+
electricitymaps_api_token=_electricitymaps_token,
|
|
1260
1303
|
tracking_mode=tracking_mode,
|
|
1261
1304
|
log_level=log_level,
|
|
1262
1305
|
on_csv_write=on_csv_write,
|
|
@@ -13,11 +13,29 @@ from codecarbon.output_methods.emissions_data import EmissionsData, TaskEmission
|
|
|
13
13
|
class FileOutput(BaseOutput):
|
|
14
14
|
"""
|
|
15
15
|
Saves experiment artifacts to a file
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
output_file_name: str, name of file to write to.
|
|
19
|
+
output_dir: str, path to directory to write to.
|
|
20
|
+
save_file_path: str, path to file to write to.
|
|
21
|
+
on_csv_write: str, "append" or "update", whether or not to append or overwrite a file if it exists.
|
|
16
22
|
"""
|
|
17
23
|
|
|
18
24
|
def __init__(
|
|
19
25
|
self, output_file_name: str, output_dir: str, on_csv_write: str = "append"
|
|
20
26
|
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the FileOutput object.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
output_file_name: name of file to write to.
|
|
32
|
+
output_dir: path to directory to write to.
|
|
33
|
+
on_csv_write: "append" or "update", whether or not to append or overwrite a file if it exists
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If the on_csv_write value is invalid.
|
|
37
|
+
OSError: If the output directory does not exist.
|
|
38
|
+
"""
|
|
21
39
|
if on_csv_write not in {"append", "update"}:
|
|
22
40
|
raise ValueError(
|
|
23
41
|
f"Unknown `on_csv_write` value: {on_csv_write}"
|
|
@@ -33,7 +51,16 @@ class FileOutput(BaseOutput):
|
|
|
33
51
|
f"Emissions data (if any) will be saved to file {os.path.abspath(self.save_file_path)}"
|
|
34
52
|
)
|
|
35
53
|
|
|
36
|
-
def has_valid_headers(self, data: EmissionsData):
|
|
54
|
+
def has_valid_headers(self, data: EmissionsData) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Checks self.save_file_path has headers matching those from passed data.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
data: EmissionsData object with valid headers.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if the file has valid headers, False otherwise.
|
|
63
|
+
"""
|
|
37
64
|
with open(self.save_file_path) as csv_file:
|
|
38
65
|
csv_reader = csv.DictReader(csv_file)
|
|
39
66
|
csv_entries_list = list(csv_reader)
|
|
@@ -44,11 +71,21 @@ class FileOutput(BaseOutput):
|
|
|
44
71
|
list_of_column_names = list(dict_from_csv.keys())
|
|
45
72
|
return list(data.values.keys()) == list_of_column_names
|
|
46
73
|
|
|
47
|
-
def out(self, total: EmissionsData,
|
|
74
|
+
def out(self, total: EmissionsData, _: EmissionsData):
|
|
48
75
|
"""
|
|
49
|
-
Save the emissions data to a CSV file.
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
Save the emissions data from a whole run to a CSV file.
|
|
77
|
+
|
|
78
|
+
* If the file does not exist, then create it.
|
|
79
|
+
* If the file already exists but has invalid headers, then back it up and replace with new data.
|
|
80
|
+
* If the file already exists and has valid headers:
|
|
81
|
+
* If it has no rows with a matching run ID, append the new data.
|
|
82
|
+
* If it has one row with a matching run ID, then replace that row with the new data.
|
|
83
|
+
* If it has > one row with a matching run ID, append the new data
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
total: data to save.
|
|
87
|
+
|
|
88
|
+
|
|
52
89
|
"""
|
|
53
90
|
file_exists: bool = os.path.isfile(self.save_file_path)
|
|
54
91
|
if file_exists and not self.has_valid_headers(total):
|
|
@@ -60,6 +97,10 @@ class FileOutput(BaseOutput):
|
|
|
60
97
|
df = new_df
|
|
61
98
|
elif self.on_csv_write == "append":
|
|
62
99
|
df = pd.read_csv(self.save_file_path)
|
|
100
|
+
# Filter out empty or all-NA columns, to avoid warnings from Pandas,
|
|
101
|
+
# see https://github.com/pandas-dev/pandas/issues/55928
|
|
102
|
+
df = df.dropna(axis=1, how="all")
|
|
103
|
+
new_df = new_df.dropna(axis=1, how="all")
|
|
63
104
|
df = pd.concat([df, new_df])
|
|
64
105
|
else:
|
|
65
106
|
df = pd.read_csv(self.save_file_path)
|
|
@@ -74,13 +115,22 @@ class FileOutput(BaseOutput):
|
|
|
74
115
|
)
|
|
75
116
|
df = pd.concat([df, new_df])
|
|
76
117
|
else:
|
|
77
|
-
|
|
78
|
-
|
|
118
|
+
update_values = {}
|
|
119
|
+
for col, val in dict(total.values).items():
|
|
120
|
+
# Explicitly cast new values to prevent warnings about incompatible dtypes.
|
|
121
|
+
update_values[col] = df[col].dtype.type(val)
|
|
122
|
+
df.loc[df.run_id == total.run_id, update_values.keys()] = (
|
|
123
|
+
update_values.values()
|
|
79
124
|
)
|
|
80
125
|
|
|
81
126
|
df.to_csv(self.save_file_path, index=False)
|
|
82
127
|
|
|
83
128
|
def task_out(self, data: List[TaskEmissionsData], experiment_name: str):
|
|
129
|
+
"""
|
|
130
|
+
Save the emissions data from a single task in an experiment run to a CSV file.
|
|
131
|
+
|
|
132
|
+
Does not attempt to backup existing files or prevent ovewritting them.
|
|
133
|
+
"""
|
|
84
134
|
run_id = data[0].run_id
|
|
85
135
|
save_task_file_path = os.path.join(
|
|
86
136
|
self.output_dir, "emissions_" + experiment_name + "_" + run_id + ".csv"
|
|
@@ -90,6 +140,8 @@ class FileOutput(BaseOutput):
|
|
|
90
140
|
[dict(data_point.values) for data_point in data]
|
|
91
141
|
)
|
|
92
142
|
# Filter out empty or all-NA columns, to avoid warnings from Pandas
|
|
143
|
+
# see https://github.com/pandas-dev/pandas/issues/55928
|
|
144
|
+
df = df.dropna(axis=1, how="all")
|
|
93
145
|
new_df = new_df.dropna(axis=1, how="all")
|
|
94
146
|
df = pd.concat([df, new_df], ignore_index=True)
|
|
95
147
|
df.to_csv(save_task_file_path, index=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://codecarbon.io/
|
|
@@ -220,7 +220,7 @@ In order to contribute a change to our code base, please submit a pull request (
|
|
|
220
220
|
|
|
221
221
|
Check out our [contribution guidelines :arrow_upper_right:](https://github.com/mlco2/codecarbon/blob/master/CONTRIBUTING.md)
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
224
224
|
|
|
225
225
|
# How To Cite 📝
|
|
226
226
|
|
|
@@ -271,7 +271,11 @@ Here is a sample for BibTeX:
|
|
|
271
271
|
|
|
272
272
|
# Contact 📝
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR).
|
|
275
|
+
|
|
276
|
+
Codecarbon was formerly developed by volunteers from [**Mila**](http://mila.quebec) and the [**DataForGoodFR**](https://twitter.com/dataforgood_fr) community alongside donated professional time of engineers at [**Comet.ml**](https://comet.ml) and [**BCG GAMMA**](https://www.bcg.com/en-nl/beyond-consulting/bcg-gamma/default).
|
|
277
|
+
|
|
278
|
+
Now CodeCarbon is supported by [**Code Carbon**](https://www.helloasso.com/associations/code-carbon), a French non-profit organization whose mission is to accelerate the development and adoption of CodeCarbon.
|
|
275
279
|
|
|
276
280
|
## Star History
|
|
277
281
|
|
|
@@ -19,9 +19,9 @@ codecarbon/cli/main.py
|
|
|
19
19
|
codecarbon/core/__init__.py
|
|
20
20
|
codecarbon/core/api_client.py
|
|
21
21
|
codecarbon/core/cloud.py
|
|
22
|
-
codecarbon/core/co2_signal.py
|
|
23
22
|
codecarbon/core/config.py
|
|
24
23
|
codecarbon/core/cpu.py
|
|
24
|
+
codecarbon/core/electricitymaps_api.py
|
|
25
25
|
codecarbon/core/emissions.py
|
|
26
26
|
codecarbon/core/gpu.py
|
|
27
27
|
codecarbon/core/measure.py
|
|
@@ -71,12 +71,14 @@ codecarbon/viz/assets/tv_icon.png
|
|
|
71
71
|
tests/test_api_call.py
|
|
72
72
|
tests/test_cli.py
|
|
73
73
|
tests/test_cloud.py
|
|
74
|
-
tests/test_co2_signal.py
|
|
75
74
|
tests/test_config.py
|
|
76
75
|
tests/test_core_util.py
|
|
77
76
|
tests/test_cpu.py
|
|
78
77
|
tests/test_cpu_load.py
|
|
79
78
|
tests/test_custom_handler.py
|
|
79
|
+
tests/test_electricitymaps_api.py
|
|
80
|
+
tests/test_electricitymaps_backward_compatibility.py
|
|
81
|
+
tests/test_electricitymaps_config_backward_compatibility.py
|
|
80
82
|
tests/test_emissions.py
|
|
81
83
|
tests/test_emissions_tracker.py
|
|
82
84
|
tests/test_emissions_tracker_constant.py
|
|
@@ -165,7 +165,7 @@ docs = "cd docs/edit && make docs"
|
|
|
165
165
|
carbonboard = "python codecarbon/viz/carbonboard.py"
|
|
166
166
|
|
|
167
167
|
[tool.bumpver]
|
|
168
|
-
current_version = "3.1.
|
|
168
|
+
current_version = "3.1.1"
|
|
169
169
|
version_pattern = "MAJOR.MINOR.PATCH[_TAGNUM]"
|
|
170
170
|
|
|
171
171
|
[tool.bumpver.file_patterns]
|
|
@@ -183,7 +183,8 @@ class TestConfig(unittest.TestCase):
|
|
|
183
183
|
):
|
|
184
184
|
with patch("os.path.exists", return_value=True):
|
|
185
185
|
tracker = EmissionsTracker(
|
|
186
|
-
project_name="test-project",
|
|
186
|
+
project_name="test-project",
|
|
187
|
+
electricitymaps_api_token="signal-token",
|
|
187
188
|
)
|
|
188
189
|
self.assertEqual(tracker._measure_power_secs, 10)
|
|
189
190
|
self.assertEqual(tracker._force_cpu_power, None)
|
|
@@ -191,7 +192,7 @@ class TestConfig(unittest.TestCase):
|
|
|
191
192
|
self.assertEqual(tracker._output_dir, "/success/overwritten")
|
|
192
193
|
self.assertEqual(tracker._emissions_endpoint, "http://testhost:2000")
|
|
193
194
|
self.assertEqual(tracker._gpu_ids, ["0", "1"])
|
|
194
|
-
self.assertEqual(tracker.
|
|
195
|
+
self.assertEqual(tracker._electricitymaps_api_token, "signal-token")
|
|
195
196
|
self.assertEqual(tracker._project_name, "test-project")
|
|
196
197
|
self.assertTrue(tracker._save_to_file)
|
|
197
198
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import responses
|
|
5
|
+
|
|
6
|
+
from codecarbon.core import electricitymaps_api
|
|
7
|
+
from codecarbon.core.units import Energy
|
|
8
|
+
from codecarbon.external.geography import GeoMetadata
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestElectricityMapsAPI(unittest.TestCase):
|
|
12
|
+
def setUp(self) -> None:
|
|
13
|
+
# GIVEN
|
|
14
|
+
self._energy = Energy.from_energy(kWh=10)
|
|
15
|
+
self._geo = GeoMetadata(
|
|
16
|
+
country_iso_code="FRA",
|
|
17
|
+
country_name="France",
|
|
18
|
+
region=None,
|
|
19
|
+
country_2letter_iso_code="FR",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@responses.activate
|
|
23
|
+
def test_get_emissions_RUNS(self):
|
|
24
|
+
responses.add(
|
|
25
|
+
responses.GET,
|
|
26
|
+
electricitymaps_api.URL,
|
|
27
|
+
json={
|
|
28
|
+
"zone": "FR",
|
|
29
|
+
"carbonIntensity": 58.765447504659946,
|
|
30
|
+
"datetime": "2025-11-21T19:00:00.000Z",
|
|
31
|
+
"updatedAt": "2025-11-21T19:09:54.950Z",
|
|
32
|
+
},
|
|
33
|
+
status=200,
|
|
34
|
+
)
|
|
35
|
+
result = electricitymaps_api.get_emissions(self._energy, self._geo)
|
|
36
|
+
assert round(result, 5) == 0.58765
|
|
37
|
+
|
|
38
|
+
@pytest.mark.integ_test
|
|
39
|
+
@unittest.skip("Skip real API call in regular test runs")
|
|
40
|
+
def test_get_emissions_with_api_key(self):
|
|
41
|
+
"""Test with real API call using provided API key"""
|
|
42
|
+
api_key = "YOUR_REAL_API_KEY"
|
|
43
|
+
result = electricitymaps_api.get_emissions(self._energy, self._geo, api_key)
|
|
44
|
+
# Should return a positive emissions value
|
|
45
|
+
assert result > 0
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for backward compatibility with deprecated parameter names.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
from codecarbon.core.emissions import Emissions
|
|
9
|
+
from codecarbon.emissions_tracker import EmissionsTracker
|
|
10
|
+
from codecarbon.input import DataSource
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestElectricityMapsBackwardCompatibility(unittest.TestCase):
|
|
14
|
+
"""Test that old parameter names still work with deprecation warnings."""
|
|
15
|
+
|
|
16
|
+
def test_emissions_co2_signal_api_token_deprecated(self):
|
|
17
|
+
"""Test that co2_signal_api_token still works in Emissions class."""
|
|
18
|
+
data_source = DataSource()
|
|
19
|
+
|
|
20
|
+
# Using the old parameter name should emit a warning
|
|
21
|
+
with self.assertLogs("codecarbon", level="WARNING") as log:
|
|
22
|
+
emissions = Emissions(data_source, co2_signal_api_token="test-token")
|
|
23
|
+
|
|
24
|
+
# Check that the token was set correctly
|
|
25
|
+
self.assertEqual(emissions._electricitymaps_api_token, "test-token")
|
|
26
|
+
|
|
27
|
+
# Check that a deprecation warning was logged
|
|
28
|
+
self.assertTrue(
|
|
29
|
+
any("deprecated" in message.lower() for message in log.output),
|
|
30
|
+
"Expected deprecation warning not found",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def test_emissions_new_parameter_takes_precedence(self):
|
|
34
|
+
"""Test that new parameter takes precedence over old one."""
|
|
35
|
+
data_source = DataSource()
|
|
36
|
+
|
|
37
|
+
with self.assertLogs("codecarbon", level="WARNING"):
|
|
38
|
+
emissions = Emissions(
|
|
39
|
+
data_source,
|
|
40
|
+
electricitymaps_api_token="new-token",
|
|
41
|
+
co2_signal_api_token="old-token",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# New parameter should take precedence
|
|
45
|
+
self.assertEqual(emissions._electricitymaps_api_token, "new-token")
|
|
46
|
+
|
|
47
|
+
@patch("os.path.exists", return_value=True)
|
|
48
|
+
def test_tracker_co2_signal_api_token_deprecated(self, mock_exists):
|
|
49
|
+
"""Test that co2_signal_api_token parameter works in EmissionsTracker."""
|
|
50
|
+
|
|
51
|
+
with self.assertLogs("codecarbon", level="WARNING") as log:
|
|
52
|
+
tracker = EmissionsTracker(co2_signal_api_token="test-token")
|
|
53
|
+
|
|
54
|
+
# Check that the token was set correctly
|
|
55
|
+
self.assertEqual(tracker._electricitymaps_api_token, "test-token")
|
|
56
|
+
|
|
57
|
+
# Check that a deprecation warning was logged
|
|
58
|
+
self.assertTrue(
|
|
59
|
+
any("deprecated" in message.lower() for message in log.output),
|
|
60
|
+
"Expected deprecation warning not found",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@patch("os.path.exists", return_value=True)
|
|
64
|
+
def test_tracker_new_parameter_takes_precedence(self, mock_exists):
|
|
65
|
+
"""Test that new parameter takes precedence in EmissionsTracker."""
|
|
66
|
+
|
|
67
|
+
with self.assertLogs("codecarbon", level="WARNING") as log:
|
|
68
|
+
tracker = EmissionsTracker(
|
|
69
|
+
electricitymaps_api_token="new-token", co2_signal_api_token="old-token"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# New parameter should take precedence
|
|
73
|
+
self.assertEqual(tracker._electricitymaps_api_token, "new-token")
|
|
74
|
+
|
|
75
|
+
# Still should warn about using deprecated parameter
|
|
76
|
+
self.assertTrue(any("deprecated" in message.lower() for message in log.output))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
unittest.main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test backward compatibility with configuration files using old parameter names.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
from codecarbon.emissions_tracker import EmissionsTracker
|
|
10
|
+
from tests.testutils import get_custom_mock_open
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestConfigBackwardCompatibility(unittest.TestCase):
|
|
14
|
+
"""Test that old config parameter names still work."""
|
|
15
|
+
|
|
16
|
+
@patch("os.path.exists", return_value=True)
|
|
17
|
+
def test_old_config_parameter_name(self, mock_exists):
|
|
18
|
+
"""Test that co2_signal_api_token in config file still works."""
|
|
19
|
+
config_with_old_name = dedent(
|
|
20
|
+
"""\
|
|
21
|
+
[codecarbon]
|
|
22
|
+
co2_signal_api_token=old-config-token
|
|
23
|
+
"""
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
with patch(
|
|
27
|
+
"builtins.open", new_callable=get_custom_mock_open(config_with_old_name, "")
|
|
28
|
+
):
|
|
29
|
+
with self.assertLogs("codecarbon", level="WARNING") as log:
|
|
30
|
+
tracker = EmissionsTracker()
|
|
31
|
+
|
|
32
|
+
# Should use the token from config
|
|
33
|
+
self.assertEqual(tracker._electricitymaps_api_token, "old-config-token")
|
|
34
|
+
|
|
35
|
+
# Should warn about deprecated config parameter
|
|
36
|
+
self.assertTrue(
|
|
37
|
+
any("deprecated" in message.lower() for message in log.output),
|
|
38
|
+
"Expected deprecation warning for config parameter",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@patch("os.path.exists", return_value=True)
|
|
42
|
+
def test_new_config_parameter_takes_precedence(self, mock_exists):
|
|
43
|
+
"""Test that new config parameter takes precedence over old one."""
|
|
44
|
+
config_with_both_names = dedent(
|
|
45
|
+
"""\
|
|
46
|
+
[codecarbon]
|
|
47
|
+
electricitymaps_api_token=new-config-token
|
|
48
|
+
co2_signal_api_token=old-config-token
|
|
49
|
+
"""
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with patch(
|
|
53
|
+
"builtins.open",
|
|
54
|
+
new_callable=get_custom_mock_open(config_with_both_names, ""),
|
|
55
|
+
):
|
|
56
|
+
tracker = EmissionsTracker()
|
|
57
|
+
|
|
58
|
+
# New parameter should take precedence
|
|
59
|
+
self.assertEqual(tracker._electricitymaps_api_token, "new-config-token")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "3.1.0"
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
import requests
|
|
5
|
-
import responses
|
|
6
|
-
|
|
7
|
-
from codecarbon.core import co2_signal
|
|
8
|
-
from codecarbon.core.units import Energy
|
|
9
|
-
from codecarbon.external.geography import GeoMetadata
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestCO2Signal(unittest.TestCase):
|
|
13
|
-
def setUp(self) -> None:
|
|
14
|
-
# GIVEN
|
|
15
|
-
self._energy = Energy.from_energy(kWh=10)
|
|
16
|
-
self._geo = GeoMetadata(
|
|
17
|
-
country_iso_code="FRA",
|
|
18
|
-
country_name="France",
|
|
19
|
-
region=None,
|
|
20
|
-
country_2letter_iso_code="FR",
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
@responses.activate
|
|
24
|
-
def test_get_emissions_RUNS(self):
|
|
25
|
-
responses.add(
|
|
26
|
-
responses.GET,
|
|
27
|
-
co2_signal.URL,
|
|
28
|
-
json={
|
|
29
|
-
"status": "ok",
|
|
30
|
-
"countryCode": "FR",
|
|
31
|
-
"data": {
|
|
32
|
-
"carbonIntensity": 58.765447504659946,
|
|
33
|
-
"fossilFuelPercentage": 8.464454194205029,
|
|
34
|
-
},
|
|
35
|
-
"units": {"carbonIntensity": "gCO2eq/kWh"},
|
|
36
|
-
},
|
|
37
|
-
status=200,
|
|
38
|
-
)
|
|
39
|
-
result = co2_signal.get_emissions(self._energy, self._geo)
|
|
40
|
-
assert round(result, 5) == 0.58765
|
|
41
|
-
|
|
42
|
-
@pytest.mark.integ_test
|
|
43
|
-
def test_get_emissions_TIMEOUT(self):
|
|
44
|
-
with self.assertRaises(
|
|
45
|
-
(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout)
|
|
46
|
-
):
|
|
47
|
-
co2_signal.get_emissions(self._energy, self._geo)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/2023/canada_energy_mix.json
RENAMED
|
File without changes
|
{codecarbon-3.1.0 → codecarbon-3.1.1}/codecarbon/data/private_infra/carbon_intensity_per_source.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|