qarnot 2.16.0__tar.gz → 2.18.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 (87) hide show
  1. {qarnot-2.16.0/qarnot.egg-info → qarnot-2.18.0}/PKG-INFO +3 -2
  2. qarnot-2.18.0/doc/source/api/compute/carbon_facts.rst +8 -0
  3. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/computeindex.rst +2 -0
  4. qarnot-2.18.0/doc/source/api/compute/computing_quotas.rst +8 -0
  5. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/__init__.py +11 -1
  6. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/_version.py +3 -3
  7. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/advanced_bucket.py +1 -1
  8. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/bucket.py +13 -13
  9. qarnot-2.18.0/qarnot/carbon_facts.py +208 -0
  10. qarnot-2.18.0/qarnot/computing_quotas.py +328 -0
  11. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/connection.py +47 -30
  12. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/error.py +3 -3
  13. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/forced_network_rule.py +11 -11
  14. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/hardware_constraint.py +9 -9
  15. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/helper.py +4 -3
  16. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/job.py +18 -18
  17. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/pool.py +76 -34
  18. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/privileges.py +1 -1
  19. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/retry_settings.py +2 -2
  20. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/scheduling_type.py +3 -3
  21. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/secrets.py +4 -4
  22. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/status.py +66 -66
  23. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/task.py +91 -49
  24. {qarnot-2.16.0 → qarnot-2.18.0/qarnot.egg-info}/PKG-INFO +3 -2
  25. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot.egg-info/SOURCES.txt +5 -0
  26. qarnot-2.18.0/requirements.txt +6 -0
  27. qarnot-2.18.0/test/test_carbon_facts.py +80 -0
  28. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_connection.py +148 -3
  29. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_pool.py +29 -2
  30. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_task.py +33 -3
  31. qarnot-2.16.0/requirements.txt +0 -6
  32. {qarnot-2.16.0 → qarnot-2.18.0}/LICENSE +0 -0
  33. {qarnot-2.16.0 → qarnot-2.18.0}/MANIFEST.in +0 -0
  34. {qarnot-2.16.0 → qarnot-2.18.0}/README.rst +0 -0
  35. {qarnot-2.16.0 → qarnot-2.18.0}/doc/Makefile +0 -0
  36. {qarnot-2.16.0 → qarnot-2.18.0}/doc/make.bat +0 -0
  37. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/_static/qarnot.png +0 -0
  38. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/forced_network_rule.rst +0 -0
  39. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/hardware_constraint.rst +0 -0
  40. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/job.rst +0 -0
  41. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/paginate.rst +0 -0
  42. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/pool.rst +0 -0
  43. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/privileges.rst +0 -0
  44. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/retry_settings.rst +0 -0
  45. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/scheduling_type.rst +0 -0
  46. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/secrets.rst +0 -0
  47. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/status.rst +0 -0
  48. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/compute/task.rst +0 -0
  49. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/connection.rst +0 -0
  50. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/exceptions.rst +0 -0
  51. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/storage/advanced_bucket.rst +0 -0
  52. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/storage/bucket.rst +0 -0
  53. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/storage/storage.rst +0 -0
  54. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/api/storage/storageindex.rst +0 -0
  55. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/basic.rst +0 -0
  56. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/conf.py +0 -0
  57. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/index.rst +0 -0
  58. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/installation.rst +0 -0
  59. {qarnot-2.16.0 → qarnot-2.18.0}/doc/source/qarnot.rst +0 -0
  60. {qarnot-2.16.0 → qarnot-2.18.0}/pyproject.toml +0 -0
  61. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/_filter.py +0 -0
  62. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/_retry.py +0 -0
  63. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/_util.py +0 -0
  64. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/exceptions.py +0 -0
  65. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/forced_constant.py +0 -0
  66. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/paginate.py +0 -0
  67. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot/storage.py +0 -0
  68. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot.egg-info/dependency_links.txt +0 -0
  69. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot.egg-info/requires.txt +0 -0
  70. {qarnot-2.16.0 → qarnot-2.18.0}/qarnot.egg-info/top_level.txt +0 -0
  71. {qarnot-2.16.0 → qarnot-2.18.0}/requirements-doc.txt +0 -0
  72. {qarnot-2.16.0 → qarnot-2.18.0}/requirements-lint.txt +0 -0
  73. {qarnot-2.16.0 → qarnot-2.18.0}/requirements-optional.txt +0 -0
  74. {qarnot-2.16.0 → qarnot-2.18.0}/requirements-test.txt +0 -0
  75. {qarnot-2.16.0 → qarnot-2.18.0}/setup.cfg +0 -0
  76. {qarnot-2.16.0 → qarnot-2.18.0}/setup.py +0 -0
  77. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_advanced_bucket.py +0 -0
  78. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_bucket.py +0 -0
  79. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_hardware_constraints.py +0 -0
  80. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_import.py +0 -0
  81. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_job.py +0 -0
  82. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_paginate.py +0 -0
  83. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_retry.py +0 -0
  84. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_secrets.py +0 -0
  85. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_status.py +0 -0
  86. {qarnot-2.16.0 → qarnot-2.18.0}/test/test_util.py +0 -0
  87. {qarnot-2.16.0 → qarnot-2.18.0}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.16.0
3
+ Version: 2.18.0
4
4
  Summary: Qarnot Computing SDK
5
5
  Home-page: https://computing.qarnot.com
6
6
  Author: Qarnot computing
@@ -27,6 +27,7 @@ Dynamic: classifier
27
27
  Dynamic: description
28
28
  Dynamic: home-page
29
29
  Dynamic: license
30
+ Dynamic: license-file
30
31
  Dynamic: requires-dist
31
32
  Dynamic: requires-python
32
33
  Dynamic: summary
@@ -0,0 +1,8 @@
1
+ Carbon Facts
2
+ ------------
3
+
4
+ .. automodule:: qarnot.carbon_facts
5
+ :members:
6
+ :show-inheritance:
7
+ :special-members:
8
+ :exclude-members: __dict__,__weakref__,__eq__,__str__,__repr__
@@ -14,4 +14,6 @@ Compute
14
14
  retry_settings
15
15
  scheduling_type
16
16
  secrets
17
+ carbon_facts
18
+ computing_quotas
17
19
 
@@ -0,0 +1,8 @@
1
+ Computing Quotas
2
+ ----------------
3
+
4
+ .. automodule:: qarnot.computing_quotas
5
+ :members:
6
+ :show-inheritance:
7
+ :special-members:
8
+ :exclude-members: __dict__,__weakref__
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
 
18
18
 
19
+ from typing import Dict
19
20
  from .exceptions import QarnotGenericException, SecretConflictException, SecretNotFoundException, UnauthorizedException
20
21
  from ._util import get_error_message_from_http_response
21
22
 
@@ -65,7 +66,8 @@ def get_url(key, **kwargs):
65
66
  'task stderr': '/tasks/{uuid}/stderr', # GET -> task stderr
66
67
  'task instance stdout': '/tasks/{uuid}/stdout/{instanceId}', # GET -> task instance stdout
67
68
  'task instance stderr': '/tasks/{uuid}/stderr/{instanceId}', # GET -> task instance stderr
68
- 'task abort': '/tasks/{uuid}/abort', # GET -> task
69
+ 'task abort': '/tasks/{uuid}/abort', # POST -> abort task
70
+ 'task carbon facts': '/carbon/v1/tasks/{uuid}/carbon-facts', # GET -> task carbon facts
69
71
  'pools': '/pools', # POST -> submit pool
70
72
  'paginate pools': '/pools/paginate', # GET -> paginate pools
71
73
  'paginate pools summaries': '/pools/summaries/paginate', # GET -> paginate pools summaries
@@ -76,6 +78,7 @@ def get_url(key, **kwargs):
76
78
  'pool stderr': '/pools/{uuid}/stderr', # GET -> pool stderr
77
79
  'pool instance stdout': '/pools/{uuid}/stdout/{instanceId}', # GET -> pool instance stdout
78
80
  'pool instance stderr': '/pools/{uuid}/stderr/{instanceId}', # GET -> pool instance stderr
81
+ 'pool carbon facts': '/carbon/v1/pools/{uuid}/carbon-facts', # GET -> pool carbon facts
79
82
  'secrets data': '/secrets-manager/data/{secret_key}', # GET -> get secret , PUT -> create secret, PATCH -> update secret, DELETE -> delete secret
80
83
  'secrets search': '/secrets-manager/search/{secret_prefix}', # GET -> lists secrets starting with prefix
81
84
  'user': '/info', # GET -> user info
@@ -88,6 +91,13 @@ def get_url(key, **kwargs):
88
91
  return urls[key].format(**kwargs)
89
92
 
90
93
 
94
+ def get_url_with_param(key, params: Dict[str, str], **kwargs):
95
+ if (params is None or len(params) == 0):
96
+ return get_url(key, **kwargs)
97
+ param_string = "&".join(f'{key}={value}' for key, value in params.items())
98
+ return "{url}?{param}".format(url=get_url(key, **kwargs), param=param_string)
99
+
100
+
91
101
  from ._version import get_versions # noqa
92
102
  __version__ = get_versions()['version'] # type: ignore
93
103
  del get_versions
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-02-07T16:27:29+0100",
11
+ "date": "2025-05-22T14:41:22+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "23daecdc71207c207e56afae3e55190f0734bd0d",
15
- "version": "v2.16.0"
14
+ "full-revisionid": "9361585630d3dbcc8fa50ecbb9d20d592234a808",
15
+ "version": "v2.18.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -180,7 +180,7 @@ class PrefixResourcesTransformation(AbstractResourcesTransformation):
180
180
  :return: The PrefixResourcesTransformation new object
181
181
  :rtype: :class:`PrefixResourcesTransformation`
182
182
  """
183
- return PrefixResourcesTransformation(json["prefix"])
183
+ return PrefixResourcesTransformation(json.get("prefix"))
184
184
 
185
185
  def to_json(self) -> object:
186
186
  """Get a dict ready to be json packed.
@@ -107,14 +107,14 @@ class Bucket(Storage): # pylint: disable=W0223
107
107
  :returns: The created :class:`~qarnot.bucket.Bucket`.
108
108
  """
109
109
  filtering = None
110
- if "filtering" in json_bucket and json_bucket['filtering']:
111
- filtering = Filtering.from_json(json_bucket['filtering'])
110
+ if "filtering" in json_bucket and json_bucket.get('filtering'):
111
+ filtering = Filtering.from_json(json_bucket.get('filtering'))
112
112
 
113
113
  resource_transformation = None
114
- if "resourcesTransformation" in json_bucket and json_bucket['resourcesTransformation']:
115
- resource_transformation = ResourcesTransformation.from_json(json_bucket['resourcesTransformation'])
114
+ if "resourcesTransformation" in json_bucket and json_bucket.get('resourcesTransformation'):
115
+ resource_transformation = ResourcesTransformation.from_json(json_bucket.get('resourcesTransformation'))
116
116
 
117
- bucket = Bucket(connection, json_bucket['bucketName'], create=False, filtering=filtering, resources_transformation=resource_transformation, cacheTTLSec=json_bucket['cacheTTLSec'])
117
+ bucket = Bucket(connection, json_bucket.get('bucketName'), create=False, filtering=filtering, resources_transformation=resource_transformation, cacheTTLSec=json_bucket.get('cacheTTLSec'))
118
118
  return bucket
119
119
 
120
120
  def with_filtering(self, filtering):
@@ -218,7 +218,7 @@ class Bucket(Storage): # pylint: disable=W0223
218
218
  )
219
219
  self._connection.s3client.delete_bucket(Bucket=self._uuid)
220
220
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
221
- raise MissingBucketException("Cannot delete {}. Bucket not found.".format(err.response['Error']['BucketName'])) from err
221
+ raise MissingBucketException("Cannot delete {}. Bucket not found.".format(err.response.get('Error').get('BucketName'))) from err
222
222
 
223
223
  def list_files(self):
224
224
  """List files in the bucket
@@ -231,7 +231,7 @@ class Bucket(Storage): # pylint: disable=W0223
231
231
  bucket = self._connection.s3resource.Bucket(self._uuid)
232
232
  return [b for b in bucket.objects.all() if b.key is not None]
233
233
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
234
- raise MissingBucketException("Cannot list files. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
234
+ raise MissingBucketException("Cannot list files. Bucket {} not found.".format(err.response.get('Error').get('BucketName'))) from err
235
235
 
236
236
  def directory(self, directory=''):
237
237
  """List files in a directory of the bucket according to prefix.
@@ -281,7 +281,7 @@ class Bucket(Storage): # pylint: disable=W0223
281
281
  list_files_only = [x for x in entries if not x.key.endswith('/')]
282
282
  list_directories_only = [x for x in entries if x.key.endswith('/')]
283
283
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
284
- raise MissingBucketException("Cannot synchronize. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
284
+ raise MissingBucketException("Cannot synchronize. Bucket {} not found.".format(err.response.get('Error').get('BucketName'))) from err
285
285
 
286
286
  for directory in list_directories_only:
287
287
  if not os.path.isdir(os.path.join(local_directoy, get_key_for_local(directory.key))):
@@ -459,7 +459,7 @@ class Bucket(Storage): # pylint: disable=W0223
459
459
  self._connection.logger.info("Copy %s to %s" % (entry[0].name, link.name))
460
460
  self.copy_file(entry[0].name, link.name)
461
461
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
462
- raise MissingBucketException("Cannot sync files. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
462
+ raise MissingBucketException("Cannot sync files. Bucket {} not found.".format(err.response.get('Error').get('BucketName'))) from err
463
463
 
464
464
  def add_string(self, string, remote):
465
465
  """Add a string on the storage.
@@ -484,7 +484,7 @@ class Bucket(Storage): # pylint: disable=W0223
484
484
  try:
485
485
  self._connection.s3client.upload_fileobj(file_, self._uuid, dest, Config=s3_multipart_config)
486
486
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
487
- raise MissingBucketException("Cannot add string. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
487
+ raise MissingBucketException("Cannot add string. Bucket {} not found.".format(err.response.get('Error').get('BucketName'))) from err
488
488
  finally:
489
489
  if tobeclosed:
490
490
  file_.close()
@@ -523,7 +523,7 @@ class Bucket(Storage): # pylint: disable=W0223
523
523
  }
524
524
  return self._connection.s3client.copy_object(CopySource=copy_source, Bucket=self._uuid, Key=dest)
525
525
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
526
- raise MissingBucketException("Cannot copy file {} to {} from bucket {}. Bucket not found.".format(source, dest, err.response['Error']['BucketName'])) from err
526
+ raise MissingBucketException("Cannot copy file {} to {} from bucket {}. Bucket not found.".format(source, dest, err.response.get('Error').get('BucketName'))) from err
527
527
 
528
528
  @deprecation.deprecated(deprecated_in="2.6.0", removed_in="3.0",
529
529
  current_version=__version__, # type: ignore
@@ -547,7 +547,7 @@ class Bucket(Storage): # pylint: disable=W0223
547
547
  try:
548
548
  self._connection.s3client.download_fileobj(self._uuid, remote, data)
549
549
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
550
- raise MissingBucketException("Cannot download file {} from bucket {}. Bucket not found.".format(remote, err.response['Error']['BucketName'])) from err
550
+ raise MissingBucketException("Cannot download file {} from bucket {}. Bucket not found.".format(remote, err.response.get('Error').get('BucketName'))) from err
551
551
  return local
552
552
 
553
553
  @_util.copy_docs(Storage.delete_file)
@@ -557,7 +557,7 @@ class Bucket(Storage): # pylint: disable=W0223
557
557
  remote = _util.get_sanitized_bucket_path(remote, self._connection._show_bucket_warnings)
558
558
  self._connection.s3client.delete_object(Bucket=self._uuid, Key=remote)
559
559
  except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
560
- raise MissingBucketException("Cannot delete file {} from bucket {}. Bucket not found.".format(remote, err.response['Error']['BucketName'])) from err
560
+ raise MissingBucketException("Cannot delete file {} from bucket {}. Bucket not found.".format(remote, err.response.get('Error').get('BucketName'))) from err
561
561
 
562
562
  @property
563
563
  def uuid(self):
@@ -0,0 +1,208 @@
1
+ """Carbon facts prototype"""
2
+
3
+ from typing import Any, Dict
4
+ from requests import Response
5
+ from .exceptions import MissingPoolException, MissingTaskException
6
+ from . import get_url, get_url_with_param, raise_on_error, _util
7
+
8
+
9
+ class CarbonFacts(object):
10
+ """
11
+ Carbon facts details of a computed element
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ energy_consumption: float = 0,
17
+ energy_it: float = 0,
18
+ energy_reuse: float = 0,
19
+ carbon_footprint: float = 0,
20
+ equivalent_dc_name: str = "",
21
+ equivalent_dc_carbon_footprint: float = 0,
22
+ saved_carbon_footprint_compute: float = 0,
23
+ saved_carbon_footprint_heat: float = 0,
24
+ saved_carbon_footprint_compute_heat: float = 0,
25
+ saved_carbon_footprint_percent: float = 0,
26
+ PUE: float = 0,
27
+ ERE: float = 0,
28
+ ERF: float = 0,
29
+ WUE: float = 0):
30
+ """The CarbonFacts constructor
31
+
32
+ :param energy_consumption: the total energy consumed, in W.h
33
+ :type energy_consumption: `float`
34
+ :param energy_it: the energy consumed by IT, in W.h
35
+ :type energy_it: `float`
36
+ :param energy_reuse: the reuse heat, in W.h
37
+ :type energy_reuse: `float`
38
+ :param carbon_footprint: the actual carbon footprint of the computation, in gCO2eq
39
+ :type carbon_footprint: `float`
40
+ :param equivalent_dc_name: the name of the equivalent datacenter used for comparison
41
+ :type equivalent_dc_name: `str`
42
+ :param equivalent_dc_carbon_footprint: the carbon footprint the computation would generate in an equivalent DC, in gCO2eq
43
+ :type equivalent_dc_carbon_footprint: `float`
44
+ :param saved_carbon_footprint_compute: the carbon footprint saved by the computation part by using Qarnot instead of the equivalent DC, in gCO2eq
45
+ :type saved_carbon_footprint_compute: `float`
46
+ :param saved_carbon_footprint_heat: the carbon footprint saved by the heating part by using Qarnot instead of the equivalent DC, in gCO2eq
47
+ :type saved_carbon_footprint_heat: `float`
48
+ :param saved_carbon_footprint_compute_heat: the total carbon footprint saved by using Qarnot instead of the equivalent DC, gCO2eq
49
+ :type saved_carbon_footprint_compute_heat: `float`
50
+ :param saved_carbon_footprint_percent: the percentage of carbon footprint saved by using Qarnot instead of the equivalent DC, in %
51
+ :type saved_carbon_footprint_percent: `float`
52
+ :param PUE: the energy efficiency of the computation site
53
+ :type PUE: `float`
54
+ :param ERE: the energy reuse ratio of the computation site
55
+ :type ERE: `float`
56
+ :param ERF: the heat reuse ratio of the computation site
57
+ :type ERF: `float`
58
+ :param WUE: the water consumption of the computation site, in L/kWh
59
+ :type WUE: `float`
60
+ """
61
+ self.energy_consumption_Wh: float = energy_consumption
62
+ self.energy_it_Wh: float = energy_it
63
+ self.energy_reuse_Wh: float = energy_reuse
64
+ self.carbon_footprint_gC02eq: float = carbon_footprint
65
+ self.equivalent_datacenter_name: str = equivalent_dc_name
66
+ self.equivalent_dc_carbon_footprint_gC02eq: float = equivalent_dc_carbon_footprint
67
+ self.saved_carbon_footprint_compute_gC02eq: float = saved_carbon_footprint_compute
68
+ self.saved_carbon_footprint_heat_gC02eq: float = saved_carbon_footprint_heat
69
+ self.saved_carbon_footprint_compute_heat_gC02eq: float = saved_carbon_footprint_compute_heat
70
+ self.saved_carbon_footprint_percent: float = saved_carbon_footprint_percent
71
+ self.PUE: float = PUE
72
+ self.ERE: float = ERE
73
+ self.ERF: float = ERF
74
+ self.WUE: float = WUE
75
+
76
+ @classmethod
77
+ def from_json(cls, json: Dict[str, Any]):
78
+ """Create a CarbonFacts from a json representation
79
+
80
+ :param json: the json to use to create the SecretsAccessRights object.
81
+ :type json: `Dict[str, float]`
82
+ :returns: The created :class:`~qarnot.carbon_facts.CarbonFacts`.
83
+ """
84
+ if json is None:
85
+ return None
86
+
87
+ energy_consumption = json.get("total_consumed_energy_Wh")
88
+ energy_it = json.get("total_energy_it_Wh")
89
+ energy_reuse = json.get("total_reused_energy_Wh")
90
+ carbon_footprint = json.get("qarnot_carbon_footprint")
91
+ equivalent_datacenter_name = json.get("equivalent_datacenter_name")
92
+ equivalent_dc_carbon_footprint = json.get("equivalent_DC_carbon_footprint")
93
+ saved_carbon_footprint_compute = json.get("saved_carbon_footprint_compute")
94
+ saved_carbon_footprint_heat = json.get("saved_carbon_footprint_heat")
95
+ saved_carbon_footprint_compute_heat = json.get("saved_carbon_footprint_compute_heat")
96
+ saved_carbon_footprint_percent = json.get("saved_carbon_footprint_percent")
97
+ PUE = json.get("PUE")
98
+ ERE = json.get("ERE")
99
+ ERF = json.get("ERF")
100
+ WUE = json.get("WUE")
101
+
102
+ return CarbonFacts(energy_consumption, energy_it, energy_reuse, carbon_footprint, equivalent_datacenter_name,
103
+ equivalent_dc_carbon_footprint, saved_carbon_footprint_compute, saved_carbon_footprint_heat,
104
+ saved_carbon_footprint_compute_heat, saved_carbon_footprint_percent, PUE, ERE, ERF, WUE)
105
+
106
+ def to_json(self) -> object:
107
+ """Get a dict ready to be json packed.
108
+
109
+ :return: the json elements of the class.
110
+ :rtype: `dict`
111
+
112
+ """
113
+ return {
114
+ "total_consumed_energy_Wh": self.energy_consumption_Wh,
115
+ "total_energy_it_Wh": self.energy_it_Wh,
116
+ "total_reused_energy_Wh": self.energy_reuse_Wh,
117
+ "qarnot_carbon_footprint": self.carbon_footprint_gC02eq,
118
+ "equivalent_datacenter_name": self.equivalent_datacenter_name,
119
+ "equivalent_DC_carbon_footprint": self.equivalent_dc_carbon_footprint_gC02eq,
120
+ "saved_carbon_footprint_compute": self.saved_carbon_footprint_compute_gC02eq,
121
+ "saved_carbon_footprint_heat": self.saved_carbon_footprint_heat_gC02eq,
122
+ "saved_carbon_footprint_compute_heat": self.saved_carbon_footprint_compute_heat_gC02eq,
123
+ "saved_carbon_footprint_percent": self.saved_carbon_footprint_percent,
124
+ "PUE": self.PUE,
125
+ "ERE": self.ERE,
126
+ "ERF": self.ERF,
127
+ "WUE": self.WUE
128
+ }
129
+
130
+
131
+ class CarbonClient(object):
132
+ """
133
+ Client used to interact with the Qarnot carbon API.
134
+ """
135
+
136
+ def __init__(self, connection, datacenter_name: str = None):
137
+ """The CarbonClient constructor.
138
+
139
+ :param connection: the cluster from where carbon facts are retrieved.
140
+ :type connection: `qarnot.connection.Connection`
141
+ :param datacenter_name: the name of the datacenter used as reference to compare carbon facts.
142
+ :type datacenter_name: `str`
143
+ """
144
+ self._connection = connection
145
+ self.reference_datacenter_name = datacenter_name
146
+
147
+ def _get_carbon_facts_url(self, resource_type: str, uuid: str):
148
+ if self.reference_datacenter_name is None or self.reference_datacenter_name == "":
149
+ return get_url('%s carbon facts' % resource_type, uuid=uuid)
150
+ return get_url_with_param('%s carbon facts' % resource_type, {'comparisonDatacenter': self.reference_datacenter_name}, uuid=uuid)
151
+
152
+ def _get_pool_carbon_facts_raw(self, pool_id: str) -> Response:
153
+ """Requests the carbon facts for the pool `pool_id`.
154
+
155
+ :param pool_id: the uuid of the pool
156
+ :type pool_id: `str`
157
+ :rtype: `requests.Response`
158
+ :raises ~qarnot.exceptions.MissingPoolException: Pool was not found.
159
+ :raises ~qarnot.exceptions.UnauthorizedException: Unauthorized.
160
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
161
+ """
162
+ response = self._connection._get(self._get_carbon_facts_url('pool', pool_id))
163
+ if response.status_code == 404:
164
+ raise MissingPoolException(_util.get_error_message_from_http_response(response))
165
+ raise_on_error(response)
166
+ return response
167
+
168
+ def get_pool_carbon_facts(self, pool_id: str) -> CarbonFacts:
169
+ """Retrieves the carbon facts of the pool `pool_id` and parses it to a CarbonFacts object.
170
+
171
+ :param pool_id: the uuid of the pool
172
+ :type pool_id: `str`
173
+ :rtype: CarbonFacts
174
+ :raises ~qarnot.exceptions.MissingPoolException: Pool was not found.
175
+ :raises ~qarnot.exceptions.UnauthorizedException: Unauthorized.
176
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
177
+ """
178
+ raw_carbon_facts = self._get_pool_carbon_facts_raw(pool_id)
179
+ return CarbonFacts.from_json(raw_carbon_facts.json())
180
+
181
+ def _get_task_carbon_facts_raw(self, task_id: str) -> Response:
182
+ """Requests the carbon facts for the task `task_id`.
183
+
184
+ :param task_id: the uuid of the task
185
+ :type task_id: `str`
186
+ :rtype: `requests.Response`
187
+ :raises ~qarnot.exceptions.MissingTaskException: Task was not found.
188
+ :raises ~qarnot.exceptions.UnauthorizedException: Unauthorized.
189
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
190
+ """
191
+ response = self._connection._get(self._get_carbon_facts_url('task', task_id))
192
+ if response.status_code == 404:
193
+ raise MissingTaskException(_util.get_error_message_from_http_response(response))
194
+ raise_on_error(response)
195
+ return response
196
+
197
+ def get_task_carbon_facts(self, task_id: str) -> CarbonFacts:
198
+ """Retrieves the carbon facts of the task `task_id` and parses it to a CarbonFacts object.
199
+
200
+ :param task_id: the uuid of the task
201
+ :type task_id: `str`
202
+ :rtype: CarbonFacts
203
+ :raises ~qarnot.exceptions.MissingTaskException: Task was not found.
204
+ :raises ~qarnot.exceptions.UnauthorizedException: Unauthorized.
205
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
206
+ """
207
+ raw_carbon_facts = self._get_task_carbon_facts_raw(task_id)
208
+ return CarbonFacts.from_json(raw_carbon_facts.json())