codecarbon 3.2.2__tar.gz → 3.2.4__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.2.2 → codecarbon-3.2.4}/PKG-INFO +20 -39
- {codecarbon-3.2.2 → codecarbon-3.2.4}/README.md +13 -8
- codecarbon-3.2.4/codecarbon/_version.py +1 -0
- codecarbon-3.2.4/codecarbon/cli/auth.py +225 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/main.py +47 -57
- codecarbon-3.2.4/codecarbon/cli/monitor.py +127 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/config.py +38 -1
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/cpu.py +22 -7
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/emissions.py +57 -1
- codecarbon-3.2.4/codecarbon/core/gpu.py +166 -0
- codecarbon-3.2.4/codecarbon/core/gpu_amd.py +272 -0
- codecarbon-3.2.4/codecarbon/core/gpu_device.py +116 -0
- codecarbon-3.2.4/codecarbon/core/gpu_nvidia.py +130 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/measure.py +5 -1
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/resource_tracker.py +42 -24
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/util.py +38 -6
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_power.csv +1 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/emissions_tracker.py +99 -51
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/geography.py +25 -17
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/hardware.py +95 -12
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/logger.py +6 -3
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/input.py +14 -9
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/file.py +4 -7
- codecarbon-3.2.4/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/PKG-INFO +20 -39
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/SOURCES.txt +10 -1
- codecarbon-3.2.4/codecarbon.egg-info/requires.txt +33 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/pyproject.toml +37 -47
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_config.py +43 -0
- codecarbon-3.2.4/tests/test_core_util.py +137 -0
- codecarbon-3.2.4/tests/test_cpu.py +660 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions.py +103 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker.py +118 -3
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker_constant.py +6 -4
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_geography.py +16 -4
- codecarbon-3.2.4/tests/test_gpu.py +222 -0
- codecarbon-3.2.4/tests/test_gpu_amd.py +499 -0
- codecarbon-3.2.2/tests/test_gpu.py → codecarbon-3.2.4/tests/test_gpu_nvidia.py +62 -16
- codecarbon-3.2.4/tests/test_logger.py +59 -0
- codecarbon-3.2.4/tests/test_resource_tracker.py +43 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/testdata.py +6 -30
- codecarbon-3.2.2/codecarbon/_version.py +0 -1
- codecarbon-3.2.2/codecarbon/core/gpu.py +0 -309
- codecarbon-3.2.2/codecarbon.egg-info/requires.txt +0 -56
- codecarbon-3.2.2/tests/test_cli.py +0 -190
- codecarbon-3.2.2/tests/test_core_util.py +0 -44
- codecarbon-3.2.2/tests/test_cpu.py +0 -329
- {codecarbon-3.2.2 → codecarbon-3.2.4}/LICENSE +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/api_client.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/electricitymaps_api.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/powermetrics.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/rapl.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/schemas.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/units.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/ram.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/task.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/lock.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/base_output.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- /codecarbon-3.2.2/codecarbon/viz/__init__.py → /codecarbon-3.2.4/codecarbon/py.typed +0 -0
- {codecarbon-3.2.2/codecarbon/viz/assets → codecarbon-3.2.4/codecarbon/viz}/__init__.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/carbonboard.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/setup.cfg +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_api_call.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_cloud.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_cpu_load.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_custom_handler.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_api.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_backward_compatibility.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker_flush.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_energy.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_input.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_lock.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_logging_output.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_offline_emissions_tracker.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_powermetrics.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_ram.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_mmio_scanning.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_permissions.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_utilization_tracking.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_viz_data.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_viz_units.py +0 -0
- {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/testutils.py +0 -0
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://codecarbon.io/
|
|
7
7
|
Project-URL: Repository, https://github.com/mlco2/codecarbon
|
|
8
8
|
Project-URL: Dashboard, http://dashboard.codecarbon.io/
|
|
9
|
-
Project-URL: Documentation, https://
|
|
9
|
+
Project-URL: Documentation, https://docs.codecarbon.io/
|
|
10
10
|
Project-URL: Issues, https://github.com/mlco2/codecarbon/issues
|
|
11
11
|
Project-URL: Changelog, https://github.com/mlco2/codecarbon/releases
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.10
|
|
22
19
|
Description-Content-Type: text/markdown
|
|
23
20
|
License-File: LICENSE
|
|
24
21
|
Requires-Dist: arrow
|
|
22
|
+
Requires-Dist: authlib>=1.2.1
|
|
25
23
|
Requires-Dist: click
|
|
26
|
-
Requires-Dist: fief-client[cli]
|
|
27
24
|
Requires-Dist: pandas>=2.3.3; python_version >= "3.14"
|
|
28
25
|
Requires-Dist: pandas; python_version < "3.14"
|
|
29
26
|
Requires-Dist: prometheus_client
|
|
@@ -36,6 +33,7 @@ Requires-Dist: requests
|
|
|
36
33
|
Requires-Dist: questionary
|
|
37
34
|
Requires-Dist: rich
|
|
38
35
|
Requires-Dist: typer
|
|
36
|
+
Requires-Dist: pycountry
|
|
39
37
|
Provides-Extra: carbonboard
|
|
40
38
|
Requires-Dist: dash; extra == "carbonboard"
|
|
41
39
|
Requires-Dist: dash_bootstrap_components>1.0.0; extra == "carbonboard"
|
|
@@ -44,33 +42,11 @@ Provides-Extra: viz-legacy
|
|
|
44
42
|
Requires-Dist: dash; extra == "viz-legacy"
|
|
45
43
|
Requires-Dist: dash_bootstrap_components>1.0.0; extra == "viz-legacy"
|
|
46
44
|
Requires-Dist: fire; extra == "viz-legacy"
|
|
47
|
-
Provides-Extra:
|
|
48
|
-
Requires-Dist:
|
|
49
|
-
Requires-Dist: bcrypt<5.0.0; extra == "api"
|
|
50
|
-
Requires-Dist: python-dateutil<3.0.0; extra == "api"
|
|
51
|
-
Requires-Dist: dependency-injector<5.0.0; extra == "api"
|
|
52
|
-
Requires-Dist: fastapi<1.0.0; extra == "api"
|
|
53
|
-
Requires-Dist: fief-client[fastapi]; extra == "api"
|
|
54
|
-
Requires-Dist: httpx; extra == "api"
|
|
55
|
-
Requires-Dist: pydantic[email]<2.0.0; extra == "api"
|
|
56
|
-
Requires-Dist: psycopg2-binary<3.0.0; extra == "api"
|
|
57
|
-
Requires-Dist: requests<3.0.0; extra == "api"
|
|
58
|
-
Requires-Dist: sqlalchemy<2.0.0; extra == "api"
|
|
59
|
-
Requires-Dist: uvicorn[standard]<1.0.0; extra == "api"
|
|
60
|
-
Requires-Dist: fastapi-pagination<1.0.0; extra == "api"
|
|
61
|
-
Requires-Dist: pytest; extra == "api"
|
|
62
|
-
Requires-Dist: mock; extra == "api"
|
|
63
|
-
Requires-Dist: responses; extra == "api"
|
|
64
|
-
Requires-Dist: fastapi-oidc; python_version >= "3.10" and extra == "api"
|
|
65
|
-
Requires-Dist: numpy; extra == "api"
|
|
66
|
-
Requires-Dist: psutil; extra == "api"
|
|
67
|
-
Requires-Dist: requests-mock; extra == "api"
|
|
68
|
-
Requires-Dist: rapidfuzz; extra == "api"
|
|
69
|
-
Requires-Dist: PyJWT; extra == "api"
|
|
70
|
-
Requires-Dist: logfire[fastapi]>=1.0.1; extra == "api"
|
|
45
|
+
Provides-Extra: amdsmi
|
|
46
|
+
Requires-Dist: amdsmi>=6.0.0; extra == "amdsmi"
|
|
71
47
|
Dynamic: license-file
|
|
72
48
|
|
|
73
|
-

|
|
74
50
|
|
|
75
51
|
Estimate and track carbon emissions from your computer, quantify and analyze their impact.
|
|
76
52
|
|
|
@@ -89,18 +65,23 @@ CodeCarbon websites:
|
|
|
89
65
|
[](https://zenodo.org/badge/latestdoi/263364731)
|
|
90
66
|
<!-- [](https://pepy.tech/project/codecarbon) -->
|
|
91
67
|
[](https://scorecard.dev/viewer/?uri=github.com/mlco2/codecarbon)
|
|
68
|
+
[](https://codecov.io/gh/mlco2/codecarbon)
|
|
92
69
|
|
|
93
70
|
|
|
94
71
|
- [About CodeCarbon 💡](#about-codecarbon-)
|
|
95
72
|
- [Quickstart 🚀](#quickstart-)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
73
|
+
- [Installation 🔧](#installation-)
|
|
74
|
+
- [Start to estimate your impact 📏](#start-to-estimate-your-impact-)
|
|
75
|
+
- [Without using the online dashboard](#without-using-the-online-dashboard)
|
|
76
|
+
- [With the online dashboard](#with-the-online-dashboard)
|
|
77
|
+
- [Monitoring your machine 💻](#monitoring-your-machine-)
|
|
78
|
+
- [Detecting your hardware 🔍](#detecting-your-hardware-)
|
|
79
|
+
- [In your Python code 🐍](#in-your-python-code-)
|
|
80
|
+
- [Visualize 📊](#visualize-)
|
|
101
81
|
- [Contributing 🤝](#contributing-)
|
|
102
82
|
- [How To Cite 📝](#how-to-cite-)
|
|
103
83
|
- [Contact 📝](#contact-)
|
|
84
|
+
- [Star History](#star-history)
|
|
104
85
|
|
|
105
86
|
# About CodeCarbon 💡
|
|
106
87
|
|
|
@@ -116,7 +97,7 @@ At **CodeCarbon**, we believe, along with Niels Bohr, that "Nothing exists until
|
|
|
116
97
|
|
|
117
98
|
We created a Python package that estimates your hardware electricity power consumption (GPU + CPU + RAM) and we apply to it the carbon intensity of the region where the computing is done.
|
|
118
99
|
|
|
119
|
-

|
|
120
101
|
|
|
121
102
|
We explain more about this calculation in the [**Methodology**](https://mlco2.github.io/codecarbon/methodology.html#) section of the documentation.
|
|
122
103
|
|
|
@@ -199,7 +180,7 @@ There is other ways to use **codecarbon** package, please refer to the documenta
|
|
|
199
180
|
## Visualize 📊
|
|
200
181
|
|
|
201
182
|
You can now visualize your experiment emissions on the [dashboard](https://dashboard.codecarbon.io/).
|
|
202
|
-

|
|
203
184
|
|
|
204
185
|
|
|
205
186
|
> Hope you enjoy your first steps monitoring your carbon computing impact!
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
Estimate and track carbon emissions from your computer, quantify and analyze their impact.
|
|
4
4
|
|
|
@@ -17,18 +17,23 @@ CodeCarbon websites:
|
|
|
17
17
|
[](https://zenodo.org/badge/latestdoi/263364731)
|
|
18
18
|
<!-- [](https://pepy.tech/project/codecarbon) -->
|
|
19
19
|
[](https://scorecard.dev/viewer/?uri=github.com/mlco2/codecarbon)
|
|
20
|
+
[](https://codecov.io/gh/mlco2/codecarbon)
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
- [About CodeCarbon 💡](#about-codecarbon-)
|
|
23
24
|
- [Quickstart 🚀](#quickstart-)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
- [Installation 🔧](#installation-)
|
|
26
|
+
- [Start to estimate your impact 📏](#start-to-estimate-your-impact-)
|
|
27
|
+
- [Without using the online dashboard](#without-using-the-online-dashboard)
|
|
28
|
+
- [With the online dashboard](#with-the-online-dashboard)
|
|
29
|
+
- [Monitoring your machine 💻](#monitoring-your-machine-)
|
|
30
|
+
- [Detecting your hardware 🔍](#detecting-your-hardware-)
|
|
31
|
+
- [In your Python code 🐍](#in-your-python-code-)
|
|
32
|
+
- [Visualize 📊](#visualize-)
|
|
29
33
|
- [Contributing 🤝](#contributing-)
|
|
30
34
|
- [How To Cite 📝](#how-to-cite-)
|
|
31
35
|
- [Contact 📝](#contact-)
|
|
36
|
+
- [Star History](#star-history)
|
|
32
37
|
|
|
33
38
|
# About CodeCarbon 💡
|
|
34
39
|
|
|
@@ -44,7 +49,7 @@ At **CodeCarbon**, we believe, along with Niels Bohr, that "Nothing exists until
|
|
|
44
49
|
|
|
45
50
|
We created a Python package that estimates your hardware electricity power consumption (GPU + CPU + RAM) and we apply to it the carbon intensity of the region where the computing is done.
|
|
46
51
|
|
|
47
|
-

|
|
48
53
|
|
|
49
54
|
We explain more about this calculation in the [**Methodology**](https://mlco2.github.io/codecarbon/methodology.html#) section of the documentation.
|
|
50
55
|
|
|
@@ -127,7 +132,7 @@ There is other ways to use **codecarbon** package, please refer to the documenta
|
|
|
127
132
|
## Visualize 📊
|
|
128
133
|
|
|
129
134
|
You can now visualize your experiment emissions on the [dashboard](https://dashboard.codecarbon.io/).
|
|
130
|
-

|
|
131
136
|
|
|
132
137
|
|
|
133
138
|
> Hope you enjoy your first steps monitoring your carbon computing impact!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.4"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OIDC Authentication helpers for the CodeCarbon CLI.
|
|
3
|
+
|
|
4
|
+
Handles the full token lifecycle: browser-based login (Authorization Code +
|
|
5
|
+
PKCE), credential storage, JWKS validation, and transparent refresh.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import webbrowser
|
|
11
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from urllib.parse import parse_qs, urlparse
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
from authlib.common.security import generate_token
|
|
17
|
+
from authlib.integrations.requests_client import OAuth2Session
|
|
18
|
+
from authlib.jose import JsonWebKey
|
|
19
|
+
from authlib.jose import jwt as jose_jwt
|
|
20
|
+
from authlib.oauth2.rfc7636 import create_s256_code_challenge
|
|
21
|
+
|
|
22
|
+
AUTH_CLIENT_ID = os.environ.get(
|
|
23
|
+
"AUTH_CLIENT_ID",
|
|
24
|
+
"codecarbon-cli",
|
|
25
|
+
)
|
|
26
|
+
AUTH_SERVER_WELL_KNOWN = os.environ.get(
|
|
27
|
+
"AUTH_SERVER_WELL_KNOWN",
|
|
28
|
+
"https://authentication.codecarbon.io/realms/codecarbon/.well-known/openid-configuration",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_REDIRECT_PORT = 8090
|
|
32
|
+
_REDIRECT_URI = f"http://localhost:{_REDIRECT_PORT}/callback"
|
|
33
|
+
_CREDENTIALS_FILE = Path("./credentials.json")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── OAuth callback server ───────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _CallbackHandler(BaseHTTPRequestHandler):
|
|
40
|
+
"""HTTP handler that captures the OAuth2 authorization callback."""
|
|
41
|
+
|
|
42
|
+
callback_url = None
|
|
43
|
+
error = None
|
|
44
|
+
|
|
45
|
+
def do_GET(self):
|
|
46
|
+
_CallbackHandler.callback_url = f"http://localhost:{_REDIRECT_PORT}{self.path}"
|
|
47
|
+
parsed = urlparse(self.path)
|
|
48
|
+
params = parse_qs(parsed.query)
|
|
49
|
+
|
|
50
|
+
if "error" in params:
|
|
51
|
+
_CallbackHandler.error = params["error"][0]
|
|
52
|
+
self.send_response(400)
|
|
53
|
+
self.send_header("Content-Type", "text/html")
|
|
54
|
+
self.end_headers()
|
|
55
|
+
msg = params.get("error_description", [params["error"][0]])[0]
|
|
56
|
+
self.wfile.write(
|
|
57
|
+
f"<html><body><h1>Login failed</h1><p>{msg}</p></body></html>".encode()
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
self.send_response(200)
|
|
61
|
+
self.send_header("Content-Type", "text/html")
|
|
62
|
+
self.end_headers()
|
|
63
|
+
self.wfile.write(
|
|
64
|
+
b"<html><body><h1>Login successful!</h1>"
|
|
65
|
+
b"<p>You can close this window.</p></body></html>"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def log_message(self, format, *args):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ── OIDC discovery ──────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _discover_endpoints():
|
|
76
|
+
"""Fetch OpenID Connect discovery document."""
|
|
77
|
+
resp = requests.get(AUTH_SERVER_WELL_KNOWN)
|
|
78
|
+
resp.raise_for_status()
|
|
79
|
+
return resp.json()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── Credential storage ──────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _save_credentials(tokens):
|
|
86
|
+
"""Save OAuth tokens to the local credentials file."""
|
|
87
|
+
with open(_CREDENTIALS_FILE, "w") as f:
|
|
88
|
+
json.dump(tokens, f)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _load_credentials():
|
|
92
|
+
"""Load OAuth tokens from the local credentials file."""
|
|
93
|
+
with open(_CREDENTIALS_FILE, "r") as f:
|
|
94
|
+
return json.load(f)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ── Token validation & refresh ──────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _validate_access_token(access_token: str) -> bool:
|
|
101
|
+
"""Validate access token against the current OIDC provider's JWKS.
|
|
102
|
+
|
|
103
|
+
Returns False when the signature or expiry check fails (wrong provider,
|
|
104
|
+
expired, tampered). Returns True on network errors so the caller can
|
|
105
|
+
fall through to the API and let the server decide.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
discovery = _discover_endpoints()
|
|
109
|
+
jwks_resp = requests.get(discovery["jwks_uri"])
|
|
110
|
+
jwks_resp.raise_for_status()
|
|
111
|
+
keyset = JsonWebKey.import_key_set(jwks_resp.json())
|
|
112
|
+
claims = jose_jwt.decode(access_token, keyset)
|
|
113
|
+
claims.validate()
|
|
114
|
+
return True
|
|
115
|
+
except requests.RequestException:
|
|
116
|
+
return True # Can't reach auth server — let the API handle it
|
|
117
|
+
except Exception:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _refresh_tokens(refresh_token: str) -> dict:
|
|
122
|
+
"""Exchange a refresh token for a new token set via the OIDC token endpoint."""
|
|
123
|
+
discovery = _discover_endpoints()
|
|
124
|
+
resp = requests.post(
|
|
125
|
+
discovery["token_endpoint"],
|
|
126
|
+
data={
|
|
127
|
+
"grant_type": "refresh_token",
|
|
128
|
+
"refresh_token": refresh_token,
|
|
129
|
+
"client_id": AUTH_CLIENT_ID,
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
resp.raise_for_status()
|
|
133
|
+
return resp.json()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── Public API ──────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def authorize():
|
|
140
|
+
"""Run the OAuth2 Authorization Code flow with PKCE."""
|
|
141
|
+
discovery = _discover_endpoints()
|
|
142
|
+
|
|
143
|
+
session = OAuth2Session(
|
|
144
|
+
client_id=AUTH_CLIENT_ID,
|
|
145
|
+
redirect_uri=_REDIRECT_URI,
|
|
146
|
+
scope="openid offline_access",
|
|
147
|
+
token_endpoint_auth_method="none",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
code_verifier = generate_token(48)
|
|
151
|
+
code_challenge = create_s256_code_challenge(code_verifier)
|
|
152
|
+
|
|
153
|
+
uri, state = session.create_authorization_url(
|
|
154
|
+
discovery["authorization_endpoint"],
|
|
155
|
+
code_challenge=code_challenge,
|
|
156
|
+
code_challenge_method="S256",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
_CallbackHandler.callback_url = None
|
|
160
|
+
_CallbackHandler.error = None
|
|
161
|
+
|
|
162
|
+
server = HTTPServer(("localhost", _REDIRECT_PORT), _CallbackHandler)
|
|
163
|
+
|
|
164
|
+
print("Opening browser for authentication...")
|
|
165
|
+
webbrowser.open(uri)
|
|
166
|
+
|
|
167
|
+
server.handle_request()
|
|
168
|
+
server.server_close()
|
|
169
|
+
|
|
170
|
+
if _CallbackHandler.error:
|
|
171
|
+
raise ValueError(f"Authorization failed: {_CallbackHandler.error}")
|
|
172
|
+
|
|
173
|
+
if not _CallbackHandler.callback_url:
|
|
174
|
+
raise ValueError("Authorization failed: no callback received")
|
|
175
|
+
|
|
176
|
+
token = session.fetch_token(
|
|
177
|
+
discovery["token_endpoint"],
|
|
178
|
+
authorization_response=_CallbackHandler.callback_url,
|
|
179
|
+
code_verifier=code_verifier,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
_save_credentials(token)
|
|
183
|
+
return token
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_access_token() -> str:
|
|
187
|
+
"""Return a valid access token, refreshing or failing with a clear message."""
|
|
188
|
+
try:
|
|
189
|
+
creds = _load_credentials()
|
|
190
|
+
except Exception as e:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
"Not able to retrieve the access token, "
|
|
193
|
+
f"please run `codecarbon login` first! (error: {e})"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
access_token = creds.get("access_token")
|
|
197
|
+
if not access_token:
|
|
198
|
+
raise ValueError("No access token found. Please run `codecarbon login` first.")
|
|
199
|
+
|
|
200
|
+
# Fast path: token is still valid for the current OIDC provider
|
|
201
|
+
if _validate_access_token(access_token):
|
|
202
|
+
return access_token
|
|
203
|
+
|
|
204
|
+
# Token is expired or was issued by a different provider — try refresh
|
|
205
|
+
refresh_token = creds.get("refresh_token")
|
|
206
|
+
if refresh_token:
|
|
207
|
+
try:
|
|
208
|
+
new_tokens = _refresh_tokens(refresh_token)
|
|
209
|
+
_save_credentials(new_tokens)
|
|
210
|
+
return new_tokens["access_token"]
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
# Refresh failed — credentials are stale (e.g. auth provider migrated)
|
|
215
|
+
_CREDENTIALS_FILE.unlink(missing_ok=True)
|
|
216
|
+
raise ValueError(
|
|
217
|
+
"Your session has expired or the authentication provider has changed. "
|
|
218
|
+
"Please run `codecarbon login` again."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_id_token() -> str:
|
|
223
|
+
"""Return the stored OIDC id_token."""
|
|
224
|
+
creds = _load_credentials()
|
|
225
|
+
return creds["id_token"]
|
|
@@ -8,13 +8,12 @@ from typing import Optional
|
|
|
8
8
|
import questionary
|
|
9
9
|
import requests
|
|
10
10
|
import typer
|
|
11
|
-
from fief_client import Fief
|
|
12
|
-
from fief_client.integrations.cli import FiefAuth
|
|
13
11
|
from rich import print
|
|
14
12
|
from rich.prompt import Confirm
|
|
15
13
|
from typing_extensions import Annotated
|
|
16
14
|
|
|
17
15
|
from codecarbon import __app_name__, __version__
|
|
16
|
+
from codecarbon.cli.auth import authorize, get_access_token
|
|
18
17
|
from codecarbon.cli.cli_utils import (
|
|
19
18
|
create_new_config_file,
|
|
20
19
|
get_api_endpoint,
|
|
@@ -22,17 +21,11 @@ from codecarbon.cli.cli_utils import (
|
|
|
22
21
|
get_existing_local_exp_id,
|
|
23
22
|
overwrite_local_config,
|
|
24
23
|
)
|
|
24
|
+
from codecarbon.cli.monitor import run_and_monitor
|
|
25
25
|
from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone
|
|
26
26
|
from codecarbon.core.schemas import ExperimentCreate, OrganizationCreate, ProjectCreate
|
|
27
27
|
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
28
28
|
|
|
29
|
-
AUTH_CLIENT_ID = os.environ.get(
|
|
30
|
-
"AUTH_CLIENT_ID",
|
|
31
|
-
"jsUPWIcUECQFE_ouanUuVhXx52TTjEVcVNNtNGeyAtU",
|
|
32
|
-
)
|
|
33
|
-
AUTH_SERVER_URL = os.environ.get(
|
|
34
|
-
"AUTH_SERVER_URL", "https://auth.codecarbon.io/codecarbon"
|
|
35
|
-
)
|
|
36
29
|
API_URL = os.environ.get("API_URL", "https://dashboard.codecarbon.io/api")
|
|
37
30
|
|
|
38
31
|
DEFAULT_PROJECT_ID = "e60afa92-17b7-4720-91a0-1ae91e409ba1"
|
|
@@ -76,13 +69,13 @@ def version(
|
|
|
76
69
|
|
|
77
70
|
def show_config(path: Path = Path("./.codecarbon.config")) -> None:
|
|
78
71
|
d = get_config(path)
|
|
79
|
-
api_endpoint = get_api_endpoint(path)
|
|
80
|
-
api = ApiClient(endpoint_url=api_endpoint)
|
|
81
|
-
api.set_access_token(_get_access_token())
|
|
82
72
|
print("Current configuration : \n")
|
|
83
73
|
print("Config file content : ")
|
|
84
74
|
print(d)
|
|
85
75
|
try:
|
|
76
|
+
api_endpoint = get_api_endpoint(path)
|
|
77
|
+
api = ApiClient(endpoint_url=api_endpoint)
|
|
78
|
+
api.set_access_token(get_access_token())
|
|
86
79
|
if "organization_id" not in d:
|
|
87
80
|
print(
|
|
88
81
|
"No organization_id in config, follow setup instruction to complete your configuration file!",
|
|
@@ -109,33 +102,11 @@ def show_config(path: Path = Path("./.codecarbon.config")) -> None:
|
|
|
109
102
|
print("\nOrganization :")
|
|
110
103
|
print(org)
|
|
111
104
|
except Exception as e:
|
|
112
|
-
|
|
113
|
-
f"
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def get_fief_auth():
|
|
118
|
-
fief = Fief(AUTH_SERVER_URL, AUTH_CLIENT_ID)
|
|
119
|
-
fief_auth = FiefAuth(fief, "./credentials.json")
|
|
120
|
-
return fief_auth
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _get_access_token():
|
|
124
|
-
try:
|
|
125
|
-
access_token_info = get_fief_auth().access_token_info()
|
|
126
|
-
access_token = access_token_info["access_token"]
|
|
127
|
-
return access_token
|
|
128
|
-
except Exception as e:
|
|
129
|
-
raise ValueError(
|
|
130
|
-
f"Not able to retrieve the access token, please run `codecarbon login` first! (error: {e})"
|
|
105
|
+
print(
|
|
106
|
+
f"[yellow]Could not validate remote configuration details[/yellow]. You can continue with local configuration setup. (error: {e})"
|
|
131
107
|
)
|
|
132
108
|
|
|
133
109
|
|
|
134
|
-
def _get_id_token():
|
|
135
|
-
id_token = get_fief_auth()._tokens["id_token"]
|
|
136
|
-
return id_token
|
|
137
|
-
|
|
138
|
-
|
|
139
110
|
@codecarbon.command(
|
|
140
111
|
"test-api", short_help="Make an authenticated GET request to an API endpoint"
|
|
141
112
|
)
|
|
@@ -144,16 +115,16 @@ def api_get():
|
|
|
144
115
|
ex: test-api
|
|
145
116
|
"""
|
|
146
117
|
api = ApiClient(endpoint_url=API_URL) # TODO: get endpoint from config
|
|
147
|
-
api.set_access_token(
|
|
118
|
+
api.set_access_token(get_access_token())
|
|
148
119
|
organizations = api.get_list_organizations()
|
|
149
120
|
print(organizations)
|
|
150
121
|
|
|
151
122
|
|
|
152
123
|
@codecarbon.command("login", short_help="Login to CodeCarbon")
|
|
153
124
|
def login():
|
|
154
|
-
|
|
125
|
+
authorize()
|
|
155
126
|
api = ApiClient(endpoint_url=API_URL) # TODO: get endpoint from config
|
|
156
|
-
access_token =
|
|
127
|
+
access_token = get_access_token()
|
|
157
128
|
api.set_access_token(access_token)
|
|
158
129
|
api.check_auth()
|
|
159
130
|
|
|
@@ -166,7 +137,7 @@ def get_api_key(project_id: str):
|
|
|
166
137
|
"name": "api token",
|
|
167
138
|
"x_token": "???",
|
|
168
139
|
},
|
|
169
|
-
headers={"Authorization": f"Bearer {
|
|
140
|
+
headers={"Authorization": f"Bearer {get_access_token()}"},
|
|
170
141
|
)
|
|
171
142
|
api_key = req.json()["token"]
|
|
172
143
|
return api_key
|
|
@@ -175,7 +146,7 @@ def get_api_key(project_id: str):
|
|
|
175
146
|
@codecarbon.command("get-token", short_help="Get project token")
|
|
176
147
|
def get_token(project_id: str):
|
|
177
148
|
# api = ApiClient(endpoint_url=API_URL) # TODO: get endpoint from config
|
|
178
|
-
# api.set_access_token(
|
|
149
|
+
# api.set_access_token(get_access_token())
|
|
179
150
|
token = get_api_key(project_id)
|
|
180
151
|
print("Your token: " + token)
|
|
181
152
|
print("Add it to the api_key field in your configuration file")
|
|
@@ -223,7 +194,7 @@ def config():
|
|
|
223
194
|
)
|
|
224
195
|
overwrite_local_config("api_endpoint", api_endpoint, path=file_path)
|
|
225
196
|
api = ApiClient(endpoint_url=api_endpoint)
|
|
226
|
-
api.set_access_token(
|
|
197
|
+
api.set_access_token(get_access_token())
|
|
227
198
|
organizations = api.get_list_organizations()
|
|
228
199
|
org = questionary_prompt(
|
|
229
200
|
"Pick existing organization from list or Create new organization ?",
|
|
@@ -339,13 +310,18 @@ def config():
|
|
|
339
310
|
)
|
|
340
311
|
|
|
341
312
|
|
|
342
|
-
@codecarbon.command(
|
|
313
|
+
@codecarbon.command(
|
|
314
|
+
"monitor",
|
|
315
|
+
short_help="Monitor your machine's carbon emissions.",
|
|
316
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
317
|
+
)
|
|
343
318
|
def monitor(
|
|
319
|
+
ctx: typer.Context,
|
|
344
320
|
measure_power_secs: Annotated[
|
|
345
|
-
int, typer.
|
|
321
|
+
int, typer.Option(help="Interval between two measures.")
|
|
346
322
|
] = 10,
|
|
347
323
|
api_call_interval: Annotated[
|
|
348
|
-
int, typer.
|
|
324
|
+
int, typer.Option(help="Number of measures between API calls.")
|
|
349
325
|
] = 30,
|
|
350
326
|
api: Annotated[
|
|
351
327
|
bool, typer.Option(help="Choose to call Code Carbon API or not")
|
|
@@ -359,32 +335,46 @@ def monitor(
|
|
|
359
335
|
] = None,
|
|
360
336
|
):
|
|
361
337
|
"""Monitor your machine's carbon emissions."""
|
|
338
|
+
|
|
339
|
+
# Shared tracker args so monitor and run_and_monitor behave the same
|
|
340
|
+
tracker_args = {
|
|
341
|
+
"measure_power_secs": measure_power_secs,
|
|
342
|
+
"api_call_interval": api_call_interval,
|
|
343
|
+
}
|
|
344
|
+
# Set up the tracker arguments based on mode (offline vs online) and validate required args for each mode
|
|
362
345
|
if offline:
|
|
363
346
|
if not country_iso_code:
|
|
364
347
|
print(
|
|
365
|
-
"ERROR:
|
|
348
|
+
"ERROR: Country ISO code is required for offline mode. Add it to your configuration or provide it via the command line: `--country-iso-code FRA`",
|
|
349
|
+
file=sys.stderr,
|
|
366
350
|
)
|
|
367
351
|
raise typer.Exit(1)
|
|
368
352
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
country_iso_code
|
|
372
|
-
region
|
|
373
|
-
|
|
353
|
+
tracker_args = {
|
|
354
|
+
**tracker_args,
|
|
355
|
+
"country_iso_code": country_iso_code,
|
|
356
|
+
"region": region,
|
|
357
|
+
}
|
|
374
358
|
else:
|
|
375
359
|
experiment_id = get_existing_local_exp_id()
|
|
376
360
|
if api and experiment_id is None:
|
|
377
361
|
print(
|
|
378
|
-
"ERROR: No experiment id, call 'codecarbon config' first.",
|
|
362
|
+
"ERROR: No experiment id, call 'codecarbon config' first. Or run in offline mode with `--offline --country-iso-code FRA` flag if you don't want to connect to the API.",
|
|
379
363
|
file=sys.stderr,
|
|
380
364
|
)
|
|
381
365
|
raise typer.Exit(1)
|
|
382
366
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
367
|
+
tracker_args = {**tracker_args, "save_to_api": api}
|
|
368
|
+
|
|
369
|
+
# If extra args are provided (e.g. `codecarbon monitor -- my_script.py`), delegate to `run_and_monitor`
|
|
370
|
+
if getattr(ctx, "args", None):
|
|
371
|
+
return run_and_monitor(ctx, **tracker_args)
|
|
372
|
+
|
|
373
|
+
# Instantiate the tracker
|
|
374
|
+
if offline:
|
|
375
|
+
tracker = OfflineEmissionsTracker(**tracker_args)
|
|
376
|
+
else:
|
|
377
|
+
tracker = EmissionsTracker(**tracker_args)
|
|
388
378
|
|
|
389
379
|
def signal_handler(signum, frame):
|
|
390
380
|
print("\nReceived signal to stop. Saving emissions data...")
|