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.
Files changed (122) hide show
  1. {codecarbon-3.2.2 → codecarbon-3.2.4}/PKG-INFO +20 -39
  2. {codecarbon-3.2.2 → codecarbon-3.2.4}/README.md +13 -8
  3. codecarbon-3.2.4/codecarbon/_version.py +1 -0
  4. codecarbon-3.2.4/codecarbon/cli/auth.py +225 -0
  5. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/main.py +47 -57
  6. codecarbon-3.2.4/codecarbon/cli/monitor.py +127 -0
  7. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/config.py +38 -1
  8. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/cpu.py +22 -7
  9. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/emissions.py +57 -1
  10. codecarbon-3.2.4/codecarbon/core/gpu.py +166 -0
  11. codecarbon-3.2.4/codecarbon/core/gpu_amd.py +272 -0
  12. codecarbon-3.2.4/codecarbon/core/gpu_device.py +116 -0
  13. codecarbon-3.2.4/codecarbon/core/gpu_nvidia.py +130 -0
  14. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/measure.py +5 -1
  15. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/resource_tracker.py +42 -24
  16. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/util.py +38 -6
  17. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_power.csv +1 -0
  18. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/emissions_tracker.py +99 -51
  19. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/geography.py +25 -17
  20. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/hardware.py +95 -12
  21. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/logger.py +6 -3
  22. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/input.py +14 -9
  23. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/file.py +4 -7
  24. codecarbon-3.2.4/codecarbon/viz/assets/__init__.py +0 -0
  25. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/PKG-INFO +20 -39
  26. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/SOURCES.txt +10 -1
  27. codecarbon-3.2.4/codecarbon.egg-info/requires.txt +33 -0
  28. {codecarbon-3.2.2 → codecarbon-3.2.4}/pyproject.toml +37 -47
  29. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_config.py +43 -0
  30. codecarbon-3.2.4/tests/test_core_util.py +137 -0
  31. codecarbon-3.2.4/tests/test_cpu.py +660 -0
  32. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions.py +103 -0
  33. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker.py +118 -3
  34. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker_constant.py +6 -4
  35. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_geography.py +16 -4
  36. codecarbon-3.2.4/tests/test_gpu.py +222 -0
  37. codecarbon-3.2.4/tests/test_gpu_amd.py +499 -0
  38. codecarbon-3.2.2/tests/test_gpu.py → codecarbon-3.2.4/tests/test_gpu_nvidia.py +62 -16
  39. codecarbon-3.2.4/tests/test_logger.py +59 -0
  40. codecarbon-3.2.4/tests/test_resource_tracker.py +43 -0
  41. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/testdata.py +6 -30
  42. codecarbon-3.2.2/codecarbon/_version.py +0 -1
  43. codecarbon-3.2.2/codecarbon/core/gpu.py +0 -309
  44. codecarbon-3.2.2/codecarbon.egg-info/requires.txt +0 -56
  45. codecarbon-3.2.2/tests/test_cli.py +0 -190
  46. codecarbon-3.2.2/tests/test_core_util.py +0 -44
  47. codecarbon-3.2.2/tests/test_cpu.py +0 -329
  48. {codecarbon-3.2.2 → codecarbon-3.2.4}/LICENSE +0 -0
  49. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/__init__.py +0 -0
  50. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/__init__.py +0 -0
  51. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/cli/cli_utils.py +0 -0
  52. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/__init__.py +0 -0
  53. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/api_client.py +0 -0
  54. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/cloud.py +0 -0
  55. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/electricitymaps_api.py +0 -0
  56. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/powermetrics.py +0 -0
  57. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/rapl.py +0 -0
  58. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/schemas.py +0 -0
  59. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/core/units.py +0 -0
  60. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/canada_provinces.geojson +0 -0
  61. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/cloud/impact.csv +0 -0
  62. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
  63. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
  64. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
  65. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  66. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
  67. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  68. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
  69. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/__init__.py +0 -0
  70. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/ram.py +0 -0
  71. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/scheduler.py +0 -0
  72. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/external/task.py +0 -0
  73. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/lock.py +0 -0
  74. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output.py +0 -0
  75. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/__init__.py +0 -0
  76. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/base_output.py +0 -0
  77. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/emissions_data.py +0 -0
  78. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/http.py +0 -0
  79. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/logger.py +0 -0
  80. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/__init__.py +0 -0
  81. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/logfire.py +0 -0
  82. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
  83. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/output_methods/metrics/prometheus.py +0 -0
  84. /codecarbon-3.2.2/codecarbon/viz/__init__.py → /codecarbon-3.2.4/codecarbon/py.typed +0 -0
  85. {codecarbon-3.2.2/codecarbon/viz/assets → codecarbon-3.2.4/codecarbon/viz}/__init__.py +0 -0
  86. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/car_icon.png +0 -0
  87. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/house_icon.png +0 -0
  88. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/assets/tv_icon.png +0 -0
  89. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/carbonboard.py +0 -0
  90. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/carbonboard_on_api.py +0 -0
  91. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/components.py +0 -0
  92. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/data.py +0 -0
  93. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon/viz/units.py +0 -0
  94. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/dependency_links.txt +0 -0
  95. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/entry_points.txt +0 -0
  96. {codecarbon-3.2.2 → codecarbon-3.2.4}/codecarbon.egg-info/top_level.txt +0 -0
  97. {codecarbon-3.2.2 → codecarbon-3.2.4}/setup.cfg +0 -0
  98. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_api_call.py +0 -0
  99. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_cloud.py +0 -0
  100. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_cpu_load.py +0 -0
  101. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_custom_handler.py +0 -0
  102. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_api.py +0 -0
  103. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_backward_compatibility.py +0 -0
  104. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
  105. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_emissions_tracker_flush.py +0 -0
  106. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_energy.py +0 -0
  107. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_input.py +0 -0
  108. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_lock.py +0 -0
  109. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_logging_output.py +0 -0
  110. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_offline_emissions_tracker.py +0 -0
  111. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_package_integrity.py +0 -0
  112. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_powermetrics.py +0 -0
  113. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_ram.py +0 -0
  114. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_mmio_scanning.py +0 -0
  115. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_parameters.py +0 -0
  116. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_rapl_permissions.py +0 -0
  117. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_tracking_inference.py +0 -0
  118. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_unsupported_gpu.py +0 -0
  119. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_utilization_tracking.py +0 -0
  120. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_viz_data.py +0 -0
  121. {codecarbon-3.2.2 → codecarbon-3.2.4}/tests/test_viz_units.py +0 -0
  122. {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.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://mlco2.github.io/codecarbon/
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.7
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: api
48
- Requires-Dist: alembic<2.0.0; extra == "api"
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
- ![banner](docs/edit/images/banner.png)
49
+ ![banner](docs/images/banner.png)
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
  [![DOI](https://zenodo.org/badge/263364731.svg)](https://zenodo.org/badge/latestdoi/263364731)
90
66
  <!-- [![Downloads](https://static.pepy.tech/badge/codecarbon/month)](https://pepy.tech/project/codecarbon) -->
91
67
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/mlco2/codecarbon/badge)](https://scorecard.dev/viewer/?uri=github.com/mlco2/codecarbon)
68
+ [![codecov](https://codecov.io/gh/mlco2/codecarbon/graph/badge.svg)](https://codecov.io/gh/mlco2/codecarbon)
92
69
 
93
70
 
94
71
  - [About CodeCarbon 💡](#about-codecarbon-)
95
72
  - [Quickstart 🚀](#quickstart-)
96
- - [Installation 🔧](#installation-)
97
- - [Start to estimate your impact 📏](#start-to-estimate-your-impact-)
98
- - [Monitoring your whole machine](#monitoring-your-machine-)
99
- - [In your python code](#in-your-python-code-)
100
- - [Visualize](#visualize-)
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
- ![calculation Summary](docs/edit/images/calculation.png)
100
+ ![calculation Summary](docs/images/calculation.png)
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
- ![dashboard](docs/edit/images/dashboard.png)
183
+ ![dashboard](docs/images/dashboard.png)
203
184
 
204
185
 
205
186
  > Hope you enjoy your first steps monitoring your carbon computing impact!
@@ -1,4 +1,4 @@
1
- ![banner](docs/edit/images/banner.png)
1
+ ![banner](docs/images/banner.png)
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
  [![DOI](https://zenodo.org/badge/263364731.svg)](https://zenodo.org/badge/latestdoi/263364731)
18
18
  <!-- [![Downloads](https://static.pepy.tech/badge/codecarbon/month)](https://pepy.tech/project/codecarbon) -->
19
19
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/mlco2/codecarbon/badge)](https://scorecard.dev/viewer/?uri=github.com/mlco2/codecarbon)
20
+ [![codecov](https://codecov.io/gh/mlco2/codecarbon/graph/badge.svg)](https://codecov.io/gh/mlco2/codecarbon)
20
21
 
21
22
 
22
23
  - [About CodeCarbon 💡](#about-codecarbon-)
23
24
  - [Quickstart 🚀](#quickstart-)
24
- - [Installation 🔧](#installation-)
25
- - [Start to estimate your impact 📏](#start-to-estimate-your-impact-)
26
- - [Monitoring your whole machine](#monitoring-your-machine-)
27
- - [In your python code](#in-your-python-code-)
28
- - [Visualize](#visualize-)
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
- ![calculation Summary](docs/edit/images/calculation.png)
52
+ ![calculation Summary](docs/images/calculation.png)
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
- ![dashboard](docs/edit/images/dashboard.png)
135
+ ![dashboard](docs/images/dashboard.png)
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
- raise ValueError(
113
- f"Your configuration is invalid, please verify your configuration file at {path}. To start from scratch, run `codecarbon config` and overwrite your configuration file. (error: {e})"
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(_get_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
- get_fief_auth().authorize()
125
+ authorize()
155
126
  api = ApiClient(endpoint_url=API_URL) # TODO: get endpoint from config
156
- access_token = _get_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 {_get_access_token()}"},
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(_get_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(_get_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("monitor", short_help="Monitor your machine's carbon emissions.")
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.Argument(help="Interval between two measures.")
321
+ int, typer.Option(help="Interval between two measures.")
346
322
  ] = 10,
347
323
  api_call_interval: Annotated[
348
- int, typer.Argument(help="Number of measures between API calls.")
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: country_iso_code is required for offline mode", file=sys.stderr
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
- tracker = OfflineEmissionsTracker(
370
- measure_power_secs=measure_power_secs,
371
- country_iso_code=country_iso_code,
372
- region=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
- tracker = EmissionsTracker(
384
- measure_power_secs=measure_power_secs,
385
- api_call_interval=api_call_interval,
386
- save_to_api=api,
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...")