codecarbon 2.5.0__tar.gz → 2.6.0__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 (72) hide show
  1. {codecarbon-2.5.0 → codecarbon-2.6.0}/.gitignore +3 -0
  2. {codecarbon-2.5.0 → codecarbon-2.6.0}/PKG-INFO +5 -2
  3. {codecarbon-2.5.0 → codecarbon-2.6.0}/README.md +1 -1
  4. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/__init__.py +1 -0
  5. codecarbon-2.6.0/codecarbon/_version.py +1 -0
  6. codecarbon-2.6.0/codecarbon/cli/cli_utils.py +103 -0
  7. codecarbon-2.6.0/codecarbon/cli/main.py +283 -0
  8. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/api_client.py +144 -10
  9. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/gpu.py +11 -2
  10. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/schemas.py +29 -0
  11. codecarbon-2.6.0/codecarbon/data/cloud/impact.csv +41 -0
  12. codecarbon-2.6.0/codecarbon/data/private_infra/global_energy_mix.json +1640 -0
  13. codecarbon-2.5.0/codecarbon/data/private_infra/our_world_in_data-2022_data.ipynb → codecarbon-2.6.0/codecarbon/data/private_infra/our_world_in_data.ipynb +420 -419
  14. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/emissions_tracker.py +10 -10
  15. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/input.py +25 -9
  16. {codecarbon-2.5.0 → codecarbon-2.6.0}/pyproject.toml +12 -2
  17. codecarbon-2.5.0/codecarbon/_version.py +0 -1
  18. codecarbon-2.5.0/codecarbon/cli/cli_utils.py +0 -39
  19. codecarbon-2.5.0/codecarbon/cli/main.py +0 -89
  20. codecarbon-2.5.0/codecarbon/data/cloud/impact.csv +0 -37
  21. codecarbon-2.5.0/codecarbon/data/private_infra/global_energy_mix.json +0 -1703
  22. {codecarbon-2.5.0 → codecarbon-2.6.0}/LICENSE +0 -0
  23. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/cli/__init__.py +0 -0
  24. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/__init__.py +0 -0
  25. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/cloud.py +0 -0
  26. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/co2_signal.py +0 -0
  27. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/config.py +0 -0
  28. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/cpu.py +0 -0
  29. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/emissions.py +0 -0
  30. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/measure.py +0 -0
  31. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/powermetrics.py +0 -0
  32. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/rapl.py +0 -0
  33. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/units.py +0 -0
  34. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/util.py +0 -0
  35. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/canada_provinces.geojson +0 -0
  36. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/hardware/cpu_power.csv +0 -0
  37. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/canada_energy_mix.json +0 -0
  38. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/global_energy_mix-old.json +0 -0
  39. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  40. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/01_get_world_carbon_intensity.ipynb +0 -0
  41. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/02_convert_csv_to_json.ipynb +0 -0
  42. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/03_add_eu_data.ipynb +0 -0
  43. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/eu-carbon-intensity-electricity.csv +0 -0
  44. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2023-07-07-22-40-48.png +0 -0
  45. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  46. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/world_energy_mix.csv +0 -0
  47. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/__init__.py +0 -0
  48. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/geography.py +0 -0
  49. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/hardware.py +0 -0
  50. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/logger.py +0 -0
  51. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/scheduler.py +0 -0
  52. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/task.py +0 -0
  53. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output.py +0 -0
  54. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/__init__.py +0 -0
  55. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/base_output.py +0 -0
  56. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/emissions_data.py +0 -0
  57. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/file.py +0 -0
  58. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/http.py +0 -0
  59. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/logger.py +0 -0
  60. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/__init__.py +0 -0
  61. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/logfire.py +0 -0
  62. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
  63. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/prometheus.py +0 -0
  64. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/__init__.py +0 -0
  65. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/__init__.py +0 -0
  66. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/car_icon.png +0 -0
  67. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/house_icon.png +0 -0
  68. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/tv_icon.png +0 -0
  69. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/carbonboard.py +0 -0
  70. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/carbonboard_on_api.py +0 -0
  71. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/components.py +0 -0
  72. {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/data.py +0 -0
@@ -126,3 +126,6 @@ code_carbon.db
126
126
  # Local file
127
127
  emissions*.csv*
128
128
  tests/test_data/rapl/*
129
+
130
+ #asciinema
131
+ *.cast
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: codecarbon
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Project-URL: Homepage, https://codecarbon.io/
5
5
  Project-URL: Repository, https://github.com/mlco2/codecarbon
6
6
  Project-URL: Dashboard, http://dashboard.codecarbon.io/
@@ -25,8 +25,11 @@ Requires-Dist: prometheus-client
25
25
  Requires-Dist: psutil
26
26
  Requires-Dist: py-cpuinfo
27
27
  Requires-Dist: pynvml
28
+ Requires-Dist: questionary
28
29
  Requires-Dist: rapidfuzz
29
30
  Requires-Dist: requests
31
+ Requires-Dist: rich
32
+ Requires-Dist: typer
30
33
  Provides-Extra: viz
31
34
  Requires-Dist: dash; extra == 'viz'
32
35
  Requires-Dist: dash-bootstrap-components<1.0.0; extra == 'viz'
@@ -190,7 +193,7 @@ Here is a sample for BibTeX:
190
193
  Marc Alencon and
191
194
  Michał Stęchły and
192
195
  Christian Bauer and
193
- Lucas-Otavio and
196
+ Lucas Otávio N. de Araújo and
194
197
  JPW and
195
198
  MinervaBooks},
196
199
  title = {mlco2/codecarbon: v2.4.1},
@@ -155,7 +155,7 @@ Here is a sample for BibTeX:
155
155
  Marc Alencon and
156
156
  Michał Stęchły and
157
157
  Christian Bauer and
158
- Lucas-Otavio and
158
+ Lucas Otávio N. de Araújo and
159
159
  JPW and
160
160
  MinervaBooks},
161
161
  title = {mlco2/codecarbon: v2.4.1},
@@ -10,3 +10,4 @@ from .emissions_tracker import (
10
10
  )
11
11
 
12
12
  __all__ = ["EmissionsTracker", "OfflineEmissionsTracker", "track_emissions"]
13
+ __app_name__ = "codecarbon"
@@ -0,0 +1 @@
1
+ __version__ = "2.6.0"
@@ -0,0 +1,103 @@
1
+ import configparser
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import typer
6
+ from rich.prompt import Confirm
7
+
8
+
9
+ def get_config(path: Optional[Path] = None):
10
+ p = path or Path.cwd().resolve() / ".codecarbon.config"
11
+
12
+ if p.exists():
13
+ config = configparser.ConfigParser()
14
+ config.read(str(p))
15
+ if "codecarbon" in config.sections():
16
+ d = dict(config["codecarbon"])
17
+ return d
18
+
19
+ else:
20
+ raise FileNotFoundError(
21
+ "No .codecarbon.config file found in the current directory."
22
+ )
23
+
24
+
25
+ def get_api_endpoint(path: Optional[Path] = None):
26
+ p = path or Path.cwd().resolve() / ".codecarbon.config"
27
+ if p.exists():
28
+ config = configparser.ConfigParser()
29
+ config.read(str(p))
30
+ if "codecarbon" in config.sections():
31
+ d = dict(config["codecarbon"])
32
+ if "api_endpoint" in d:
33
+ return d["api_endpoint"]
34
+ else:
35
+ with p.open("a") as f:
36
+ f.write("api_endpoint=https://api.codecarbon.io\n")
37
+ return "https://api.codecarbon.io"
38
+
39
+
40
+ def get_existing_local_exp_id(path: Optional[Path] = None):
41
+ p = path or Path.cwd().resolve() / ".codecarbon.config"
42
+ if p.exists():
43
+ config = configparser.ConfigParser()
44
+ config.read(str(p))
45
+ if "codecarbon" in config.sections():
46
+ d = dict(config["codecarbon"])
47
+ if "experiment_id" in d:
48
+ return d["experiment_id"]
49
+
50
+
51
+ def write_local_exp_id(exp_id, path: Optional[Path] = None):
52
+ p = path or Path.cwd().resolve() / ".codecarbon.config"
53
+
54
+ config = configparser.ConfigParser()
55
+ if p.exists():
56
+ config.read(str(p))
57
+ if "codecarbon" not in config.sections():
58
+ config.add_section("codecarbon")
59
+
60
+ config["codecarbon"]["experiment_id"] = exp_id
61
+
62
+ with p.open("w") as f:
63
+ config.write(f)
64
+
65
+
66
+ def overwrite_local_config(config_name, value, path: Optional[Path] = None):
67
+ p = path or Path.cwd().resolve() / ".codecarbon.config"
68
+
69
+ config = configparser.ConfigParser()
70
+ if p.exists():
71
+ config.read(str(p))
72
+ if "codecarbon" not in config.sections():
73
+ config.add_section("codecarbon")
74
+
75
+ config["codecarbon"][config_name] = value
76
+ with p.open("w") as f:
77
+ config.write(f)
78
+
79
+
80
+ def create_new_config_file():
81
+ typer.echo("Creating new config file")
82
+ file_path = typer.prompt(
83
+ "Where do you want to put your config file ?",
84
+ type=str,
85
+ default="~/.codecarbon.config",
86
+ )
87
+ if file_path[0] == "~":
88
+ file_path = Path.home() / file_path[2:]
89
+ else:
90
+ file_path = Path(file_path)
91
+
92
+ if not file_path.parent.exists():
93
+ create = Confirm.ask(
94
+ "Parent folder does not exist do you want to create it (and parents) ?"
95
+ )
96
+ if create:
97
+ file_path.parent.mkdir(parents=True, exist_ok=True)
98
+
99
+ file_path.touch()
100
+ with open(file_path, "w") as f:
101
+ f.write("[codecarbon]\n")
102
+ typer.echo(f"Config file created at {file_path}")
103
+ return file_path
@@ -0,0 +1,283 @@
1
+ import time
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import questionary
6
+ import typer
7
+ from rich import print
8
+ from rich.prompt import Confirm
9
+ from typing_extensions import Annotated
10
+
11
+ from codecarbon import __app_name__, __version__
12
+ from codecarbon.cli.cli_utils import (
13
+ create_new_config_file,
14
+ get_api_endpoint,
15
+ get_config,
16
+ get_existing_local_exp_id,
17
+ overwrite_local_config,
18
+ )
19
+ from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone
20
+ from codecarbon.core.schemas import ExperimentCreate, OrganizationCreate, ProjectCreate
21
+ from codecarbon.emissions_tracker import EmissionsTracker
22
+
23
+ DEFAULT_PROJECT_ID = "e60afa92-17b7-4720-91a0-1ae91e409ba1"
24
+ DEFAULT_ORGANIzATION_ID = "e60afa92-17b7-4720-91a0-1ae91e409ba1"
25
+
26
+ codecarbon = typer.Typer()
27
+
28
+
29
+ def _version_callback(value: bool) -> None:
30
+ if value:
31
+ print(f"{__app_name__} v{__version__}")
32
+ raise typer.Exit()
33
+
34
+
35
+ @codecarbon.callback()
36
+ def main(
37
+ version: Optional[bool] = typer.Option(
38
+ None,
39
+ "--version",
40
+ "-v",
41
+ help="Show the application's version and exit.",
42
+ callback=_version_callback,
43
+ is_eager=True,
44
+ ),
45
+ ) -> None:
46
+ return
47
+
48
+
49
+ def show_config(path: Path = Path("./.codecarbon.config")) -> None:
50
+ d = get_config(path)
51
+ api_endpoint = get_api_endpoint(path)
52
+ api = ApiClient(endpoint_url=api_endpoint)
53
+ print("Current configuration : \n")
54
+ print("Config file content : ")
55
+ print(d)
56
+ try:
57
+ if "organization_id" not in d:
58
+ print(
59
+ "No organization_id in config, follow setup instruction to complete your configuration file!",
60
+ )
61
+ else:
62
+ org = api.get_organization(d["organization_id"])
63
+
64
+ if "project_id" not in d:
65
+ print(
66
+ "No project_id in config, follow setup instruction to complete your configuration file!",
67
+ )
68
+ else:
69
+ project = api.get_project(d["project_id"])
70
+ if "experiment_id" not in d:
71
+ print(
72
+ "No experiment_id in config, follow setup instruction to complete your configuration file!",
73
+ )
74
+ else:
75
+ experiment = api.get_experiment(d["experiment_id"])
76
+ print("\nExperiment :")
77
+ print(experiment)
78
+ print("\nProject :")
79
+ print(project)
80
+ print("\nOrganization :")
81
+ print(org)
82
+ except Exception as e:
83
+ raise ValueError(
84
+ f"Your configuration is invalid, please run `codecarbon config --init` first! (error: {e})"
85
+ )
86
+
87
+
88
+ @codecarbon.command("config", short_help="Generate or show config")
89
+ def config():
90
+ """
91
+ Initialize CodeCarbon, this will prompt you for configuration of Organisation/Team/Project/Experiment.
92
+ """
93
+
94
+ print("Welcome to CodeCarbon configuration wizard")
95
+ home = Path.home()
96
+ global_path = (home / ".codecarbon.config").expanduser().resolve()
97
+
98
+ if global_path.exists():
99
+ print("Existing global config file found :")
100
+ show_config(global_path)
101
+
102
+ use_config = questionary_prompt(
103
+ "Use existing global ~/.codecarbon.config to configure or create a new file somewhere else ?",
104
+ ["~/.codecarbon.config", "Create New Config"],
105
+ default="~/.codecarbon.config",
106
+ )
107
+
108
+ if use_config == "~/.codecarbon.config":
109
+ modify = Confirm.ask("Do you want to modify the existing config file ?")
110
+ if modify:
111
+ print(f"Modifying existing config file {global_path}:")
112
+ file_path = global_path
113
+ else:
114
+ print(f"Using already existing global config file {global_path}")
115
+
116
+ return
117
+ else:
118
+ file_path = create_new_config_file()
119
+ else:
120
+ file_path = create_new_config_file()
121
+
122
+ api_endpoint = get_api_endpoint(file_path)
123
+ api_endpoint = typer.prompt(
124
+ f"Current API endpoint is {api_endpoint}. Press enter to continue or input other url",
125
+ type=str,
126
+ default=api_endpoint,
127
+ )
128
+ overwrite_local_config("api_endpoint", api_endpoint, path=file_path)
129
+ api = ApiClient(endpoint_url=api_endpoint)
130
+ organizations = api.get_list_organizations()
131
+ org = questionary_prompt(
132
+ "Pick existing organization from list or Create new organization ?",
133
+ [org["name"] for org in organizations] + ["Create New Organization"],
134
+ default="Create New Organization",
135
+ )
136
+
137
+ if org == "Create New Organization":
138
+ org_name = typer.prompt("Organization name", default="Code Carbon user test")
139
+ org_description = typer.prompt(
140
+ "Organization description", default="Code Carbon user test"
141
+ )
142
+
143
+ organization_create = OrganizationCreate(
144
+ name=org_name,
145
+ description=org_description,
146
+ )
147
+ organization = api.create_organization(organization=organization_create)
148
+ print(f"Created organization : {organization}")
149
+ else:
150
+ organization = [orga for orga in organizations if orga["name"] == org][0]
151
+ org_id = organization["id"]
152
+ overwrite_local_config("organization_id", org_id, path=file_path)
153
+
154
+ projects = api.list_projects_from_organization(org_id)
155
+ project_names = [project["name"] for project in projects] if projects else []
156
+ project = questionary_prompt(
157
+ "Pick existing project from list or Create new project ?",
158
+ project_names + ["Create New Project"],
159
+ default="Create New Project",
160
+ )
161
+ if project == "Create New Project":
162
+ project_name = typer.prompt("Project name", default="Code Carbon user test")
163
+ project_description = typer.prompt(
164
+ "Project description", default="Code Carbon user test"
165
+ )
166
+ project_create = ProjectCreate(
167
+ name=project_name,
168
+ description=project_description,
169
+ organization_id=org_id,
170
+ )
171
+ project = api.create_project(project=project_create)
172
+ print(f"Created project : {project}")
173
+ else:
174
+ project = [p for p in projects if p["name"] == project][0]
175
+ project_id = project["id"]
176
+ overwrite_local_config("project_id", project_id, path=file_path)
177
+
178
+ experiments = api.list_experiments_from_project(project_id)
179
+ experiments_names = (
180
+ [experiment["name"] for experiment in experiments] if experiments else []
181
+ )
182
+
183
+ experiment = questionary_prompt(
184
+ "Pick existing experiment from list or Create new experiment ?",
185
+ experiments_names + ["Create New Experiment"],
186
+ default="Create New Experiment",
187
+ )
188
+ if experiment == "Create New Experiment":
189
+ print("Creating new experiment")
190
+ exp_name = typer.prompt("Experiment name :", default="Code Carbon user test")
191
+ exp_description = typer.prompt(
192
+ "Experiment description :",
193
+ default="Code Carbon user test ",
194
+ )
195
+
196
+ exp_on_cloud = Confirm.ask("Is this experiment running on the cloud ?")
197
+ if exp_on_cloud is True:
198
+ cloud_provider = typer.prompt(
199
+ "Cloud provider (AWS, GCP, Azure, ...)", default="AWS"
200
+ )
201
+ cloud_region = typer.prompt(
202
+ "Cloud region (eu-west-1, us-east-1, ...)", default="eu-west-1"
203
+ )
204
+ else:
205
+ cloud_provider = None
206
+ cloud_region = None
207
+ country_name = typer.prompt("Country name :", default="Auto")
208
+ country_iso_code = typer.prompt("Country ISO code :", default="Auto")
209
+ region = typer.prompt("Region :", default="Auto")
210
+ if country_name == "Auto":
211
+ country_name = None
212
+ if country_iso_code == "Auto":
213
+ country_iso_code = None
214
+ if region == "Auto":
215
+ region = None
216
+ experiment_create = ExperimentCreate(
217
+ timestamp=get_datetime_with_timezone(),
218
+ name=exp_name,
219
+ description=exp_description,
220
+ on_cloud=exp_on_cloud,
221
+ project_id=project["id"],
222
+ country_name=country_name,
223
+ country_iso_code=country_iso_code,
224
+ region=region,
225
+ cloud_provider=cloud_provider,
226
+ cloud_region=cloud_region,
227
+ )
228
+ experiment = api.add_experiment(experiment=experiment_create)
229
+
230
+ else:
231
+ experiment = [e for e in experiments if e["name"] == experiment][0]
232
+
233
+ overwrite_local_config("experiment_id", experiment["id"], path=file_path)
234
+ show_config(file_path)
235
+ print(
236
+ "Consult [link=https://mlco2.github.io/codecarbon/usage.html#configuration]configuration documentation[/link] for more configuration options"
237
+ )
238
+
239
+
240
+ @codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.")
241
+ def monitor(
242
+ measure_power_secs: Annotated[
243
+ int, typer.Argument(help="Interval between two measures.")
244
+ ] = 10,
245
+ api_call_interval: Annotated[
246
+ int, typer.Argument(help="Number of measures between API calls.")
247
+ ] = 30,
248
+ api: Annotated[
249
+ bool, typer.Option(help="Choose to call Code Carbon API or not")
250
+ ] = True,
251
+ ):
252
+ """Monitor your machine's carbon emissions.
253
+
254
+ Args:
255
+ measure_power_secs (Annotated[int, typer.Argument, optional): Interval between two measures. Defaults to 10.
256
+ api_call_interval (Annotated[int, typer.Argument, optional): Number of measures before calling API. Defaults to 30.
257
+ api (Annotated[bool, typer.Option, optional): Choose to call Code Carbon API or not. Defaults to True.
258
+ """
259
+ experiment_id = get_existing_local_exp_id()
260
+ if api and experiment_id is None:
261
+ print("ERROR: No experiment id, call 'codecarbon init' first.", err=True)
262
+ print("CodeCarbon is going in an infinite loop to monitor this machine.")
263
+ with EmissionsTracker(
264
+ measure_power_secs=measure_power_secs,
265
+ api_call_interval=api_call_interval,
266
+ save_to_api=api,
267
+ ):
268
+ # Infinite loop
269
+ while True:
270
+ time.sleep(300)
271
+
272
+
273
+ def questionary_prompt(prompt, list_options, default):
274
+ value = questionary.select(
275
+ prompt,
276
+ list_options,
277
+ default,
278
+ ).ask()
279
+ return value
280
+
281
+
282
+ if __name__ == "__main__":
283
+ codecarbon()
@@ -14,7 +14,13 @@ from datetime import timedelta, tzinfo
14
14
  import arrow
15
15
  import requests
16
16
 
17
- from codecarbon.core.schemas import EmissionCreate, ExperimentCreate, RunCreate
17
+ from codecarbon.core.schemas import (
18
+ EmissionCreate,
19
+ ExperimentCreate,
20
+ OrganizationCreate,
21
+ ProjectCreate,
22
+ RunCreate,
23
+ )
18
24
  from codecarbon.external.logger import logger
19
25
 
20
26
  # from codecarbon.output import EmissionsData
@@ -28,7 +34,6 @@ def get_datetime_with_timezone():
28
34
  class ApiClient: # (AsyncClient)
29
35
  """
30
36
  This class call the Code Carbon API
31
- Note : The project, team and organization must have been created in the interface.
32
37
  """
33
38
 
34
39
  run_id = None
@@ -61,6 +66,105 @@ class ApiClient: # (AsyncClient)
61
66
  stacklevel=2,
62
67
  )
63
68
 
69
+ def get_list_organizations(self):
70
+ """
71
+ List all organizations
72
+ """
73
+ url = self.url + "/organizations"
74
+ r = requests.get(url=url, timeout=2)
75
+ if r.status_code != 200:
76
+ self._log_error(url, {}, r)
77
+ return None
78
+ return r.json()
79
+
80
+ def check_organization_exists(self, organization_name: str):
81
+ """
82
+ Check if an organization exists
83
+ """
84
+ organizations = self.get_list_organizations()
85
+ if organizations is None:
86
+ return False
87
+ for organization in organizations:
88
+ if organization["name"] == organization_name:
89
+ return organization
90
+ return False
91
+
92
+ def create_organization(self, organization: OrganizationCreate):
93
+ """
94
+ Create an organization
95
+ """
96
+ payload = dataclasses.asdict(organization)
97
+ url = self.url + "/organizations"
98
+ if organization := self.check_organization_exists(organization.name):
99
+ logger.warning(
100
+ f"Organization {organization['name']} already exists. Skipping creation."
101
+ )
102
+ return organization
103
+ else:
104
+ r = requests.post(url=url, json=payload, timeout=2)
105
+ if r.status_code != 201:
106
+ self._log_error(url, payload, r)
107
+ return None
108
+ return r.json()
109
+
110
+ def get_organization(self, organization_id):
111
+ """
112
+ Get an organization
113
+ """
114
+ url = self.url + "/organizations/" + organization_id
115
+ r = requests.get(url=url, timeout=2)
116
+ if r.status_code != 200:
117
+ self._log_error(url, {}, r)
118
+ return None
119
+ return r.json()
120
+
121
+ def update_organization(self, organization: OrganizationCreate):
122
+ """
123
+ Update an organization
124
+ """
125
+ payload = dataclasses.asdict(organization)
126
+ url = self.url + "/organizations/" + organization.id
127
+ r = requests.patch(url=url, json=payload, timeout=2)
128
+ if r.status_code != 200:
129
+ self._log_error(url, payload, r)
130
+ return None
131
+ return r.json()
132
+
133
+ def list_projects_from_organization(self, organization_id):
134
+ """
135
+ List all projects
136
+ """
137
+ url = self.url + "/organizations/" + organization_id + "/projects"
138
+
139
+ r = requests.get(url=url, timeout=2)
140
+ if r.status_code != 200:
141
+ self._log_error(url, {}, r)
142
+ return None
143
+ return r.json()
144
+
145
+ def create_project(self, project: ProjectCreate):
146
+ """
147
+ Create a project
148
+ """
149
+ payload = dataclasses.asdict(project)
150
+ url = self.url + "/projects"
151
+ r = requests.post(url=url, json=payload, timeout=2)
152
+ if r.status_code != 201:
153
+ self._log_error(url, payload, r)
154
+ return None
155
+ return r.json()
156
+
157
+ def get_project(self, project_id):
158
+ """
159
+ Get a project
160
+ """
161
+ url = self.url + "/projects/" + project_id
162
+ r = requests.get(url=url, timeout=2)
163
+ if r.status_code != 200:
164
+ self._log_error(url, {}, r)
165
+ return None
166
+ return r.json()
167
+
64
168
  def add_emission(self, carbon_emission: dict):
65
169
  assert self.experiment_id is not None
66
170
  self._previous_call = time.time()
@@ -97,7 +201,7 @@ class ApiClient: # (AsyncClient)
97
201
  )
98
202
  try:
99
203
  payload = dataclasses.asdict(emission)
100
- url = self.url + "/emission"
204
+ url = self.url + "/emissions"
101
205
  r = requests.post(url=url, json=payload, timeout=2)
102
206
  if r.status_code != 201:
103
207
  self._log_error(url, payload, r)
@@ -137,7 +241,7 @@ class ApiClient: # (AsyncClient)
137
241
  tracking_mode=self.conf.get("tracking_mode"),
138
242
  )
139
243
  payload = dataclasses.asdict(run)
140
- url = self.url + "/run"
244
+ url = self.url + "/runs"
141
245
  r = requests.post(url=url, json=payload, timeout=2)
142
246
  if r.status_code != 201:
143
247
  self._log_error(url, payload, r)
@@ -157,24 +261,54 @@ class ApiClient: # (AsyncClient)
157
261
  except Exception as e:
158
262
  logger.error(e, exc_info=True)
159
263
 
264
+ def list_experiments_from_project(self, project_id: str):
265
+ """
266
+ List all experiments for a project
267
+ """
268
+ url = self.url + "/projects/" + project_id + "/experiments"
269
+ r = requests.get(url=url, timeout=2)
270
+ if r.status_code != 200:
271
+ self._log_error(url, {}, r)
272
+ return []
273
+ return r.json()
274
+
275
+ def set_experiment(self, experiment_id: str):
276
+ """
277
+ Set the experiment id
278
+ """
279
+ self.experiment_id = experiment_id
280
+
160
281
  def add_experiment(self, experiment: ExperimentCreate):
161
282
  """
162
283
  Create an experiment, used by the CLI, not the package.
163
284
  ::experiment:: The experiment to create.
164
285
  """
165
286
  payload = dataclasses.asdict(experiment)
166
- url = self.url + "/experiment"
287
+ url = self.url + "/experiments"
167
288
  r = requests.post(url=url, json=payload, timeout=2)
168
289
  if r.status_code != 201:
169
290
  self._log_error(url, payload, r)
170
291
  return None
171
- self.experiment_id = r.json()["id"]
172
- return self.experiment_id
292
+ return r.json()
293
+
294
+ def get_experiment(self, experiment_id):
295
+ """
296
+ Get an experiment by id
297
+ """
298
+ url = self.url + "/experiments/" + experiment_id
299
+ r = requests.get(url=url, timeout=2)
300
+ if r.status_code != 200:
301
+ self._log_error(url, {}, r)
302
+ return None
303
+ return r.json()
173
304
 
174
305
  def _log_error(self, url, payload, response):
175
- logger.error(
176
- f"ApiClient Error when calling the API on {url} with : {json.dumps(payload)}"
177
- )
306
+ if len(payload) > 0:
307
+ logger.error(
308
+ f"ApiClient Error when calling the API on {url} with : {json.dumps(payload)}"
309
+ )
310
+ else:
311
+ logger.error(f"ApiClient Error when calling the API on {url}")
178
312
  logger.error(
179
313
  f"ApiClient API return http code {response.status_code} and answer : {response.text}"
180
314
  )
@@ -25,7 +25,10 @@ class GPUDevice:
25
25
  self._init_static_details()
26
26
 
27
27
  def _get_energy_kwh(self):
28
- return Energy.from_millijoules(self._get_total_energy_consumption())
28
+ total_energy_consumption = self._get_total_energy_consumption()
29
+ if total_energy_consumption is None:
30
+ return self.last_energy
31
+ return Energy.from_millijoules(total_energy_consumption)
29
32
 
30
33
  def delta(self, duration: Time) -> dict:
31
34
  """
@@ -92,7 +95,13 @@ class GPUDevice:
92
95
  """Returns total energy consumption for this GPU in millijoules (mJ) since the driver was last reloaded
93
96
  https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries_1g732ab899b5bd18ac4bfb93c02de4900a
94
97
  """
95
- return pynvml.nvmlDeviceGetTotalEnergyConsumption(self.handle)
98
+ try:
99
+ return pynvml.nvmlDeviceGetTotalEnergyConsumption(self.handle)
100
+ except pynvml.NVMLError:
101
+ logger.warning(
102
+ "Failed to retrieve gpu total energy consumption", exc_info=True
103
+ )
104
+ return None
96
105
 
97
106
  def _get_gpu_name(self):
98
107
  """Returns the name of the GPU device