ciocore 8.0.1__py2.py3-none-any.whl → 8.1.0b3__py2.py3-none-any.whl

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.

Potentially problematic release.


This version of ciocore might be problematic. Click here for more details.

ciocore/VERSION CHANGED
@@ -1 +1 @@
1
- 8.0.1
1
+ 8.1.0-beta.3
ciocore/api_client.py CHANGED
@@ -3,18 +3,21 @@ The api_client module is used to make requests to the Conductor API.
3
3
  """
4
4
 
5
5
  import base64
6
+ import collections
7
+ import datetime
8
+ import hashlib
6
9
  import importlib
7
10
  import json
8
11
  import jwt
9
12
  import logging
10
13
  import os
11
14
  import platform
15
+ import datetime
12
16
  import requests
13
17
  import socket
14
18
  import time
15
19
  import sys
16
20
  import platform
17
- import hashlib
18
21
 
19
22
  from urllib import parse
20
23
 
@@ -486,6 +489,30 @@ def account_name_from_jwt(token):
486
489
  return None
487
490
 
488
491
 
492
+ def get_account_data():
493
+ """
494
+ Get the authenticated account's data.
495
+
496
+ Returns:
497
+ dict: The account data.
498
+ """
499
+ token = read_conductor_credentials(True)
500
+ if not token:
501
+ return None
502
+
503
+ payload = jwt.decode(token, verify=False)
504
+ return {
505
+ "email": payload.get("email"),
506
+ "domain": ".".join(payload.get("aud").split(".")[1:]),
507
+ "account_id" : payload.get("account"),
508
+ "user_id" : payload.get("id"),
509
+ "role": payload.get("role"),
510
+ "issued": datetime.datetime.fromtimestamp(payload.get("iat")),
511
+ "expiry": datetime.datetime.fromtimestamp(payload.get("exp")),
512
+ "token": token,
513
+ }
514
+
515
+
489
516
  def request_instance_types(as_dict=False):
490
517
  """
491
518
  Get the list of available instances types.
@@ -786,3 +813,223 @@ def kill_tasks(job_id, *task_ids):
786
813
  raise Exception(msg)
787
814
 
788
815
  return json.loads(response)
816
+
817
+ def _get_compute_usage(start_time, end_time):
818
+ """
819
+ Query the account usage to get the raw compute data. Private method.
820
+
821
+ Compute includes licenses, instances and Conductor cost. Everything involved
822
+ with running a job.
823
+
824
+ Please use the public method api_client.get_compute_usage() instead.
825
+
826
+ Args:
827
+ start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
828
+ end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
829
+
830
+ Returns:
831
+ list: A list of billing entries
832
+
833
+ Examples:
834
+ >>> from ciocore import api_client
835
+ >>> api_client._get_compute_usage(start_time, end_time)
836
+ [
837
+ {
838
+ "cores": 0.5,
839
+ "instance_cost": 0.019999999552965164,
840
+ "license_cost": 0.019999999552965164,
841
+ "minutes": 6.9700000286102295,
842
+ "self_link": 0,
843
+ "start_time": "Tue, 09 Jan 2024 18:00:00 GMT"
844
+ },
845
+ {
846
+ "cores": 0.4,
847
+ "instance_cost": 0.019999999552965164,
848
+ "license_cost": 0.019999999552965164,
849
+ "minutes": 6.960000038146973,
850
+ "self_link": 1,
851
+ "start_time": "Tue, 09 Jan 2024 19:00:00 GMT"
852
+ }]
853
+ """
854
+
855
+ api = ApiClient()
856
+
857
+ payload = {
858
+ "filter": json.dumps(
859
+ [{"field": "start_time", "op": ">=", "value": start_time.date().isoformat()},
860
+ {"field": "start_time", "op": "<", "value": end_time.date().isoformat()}]),
861
+ "group_by": json.dumps(["start_time"]),
862
+ "order_by": "start_time"
863
+ }
864
+
865
+ response, response_code = api.make_request(
866
+ uri_path="billing/get_usage",
867
+ verb="GET",
868
+ raise_on_error=False,
869
+ use_api_key=True,
870
+ params=payload
871
+ )
872
+
873
+ if response_code not in [200]:
874
+ msg = f"Failed to query compute usage"
875
+ msg += "\nError %s ...\n%s" % (response_code, response)
876
+ raise Exception(msg)
877
+
878
+ return json.loads(response)['data']
879
+
880
+ def _get_storage_usage(start_time, end_time):
881
+ """
882
+ Query the account usage to get the raw storage data. Private method.
883
+
884
+ Please use the public method api_client.get_storage_usage() instead.
885
+
886
+ Args:
887
+ start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
888
+ end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
889
+
890
+ Returns:
891
+ dict: A dict of billing details related to storage
892
+
893
+ Examples:
894
+ >>> from ciocore import api_client
895
+ >>> api_client._get_storage_usage(start_time, end_time)
896
+ {
897
+ "cost": "28.96",
898
+ "cost_per_day": [
899
+ 4.022,
900
+ 4.502,
901
+ 4.502,
902
+ 5.102,
903
+ 5.102,
904
+ 5.732
905
+ ],
906
+ "currency": "USD",
907
+ "daily_price": "0.006",
908
+ "end_date": "2024-01-07",
909
+ "gibs_per_day": [
910
+ 679.714,
911
+ 750.34,
912
+ 750.34,
913
+ 850.36,
914
+ 850.35,
915
+ 955.32
916
+ ],
917
+ "gibs_used": "806.07",
918
+ "monthly_price": "0.18",
919
+ "start_date": "2024-01-01",
920
+ "storage_unit": "GiB"
921
+ }
922
+ ]
923
+ }
924
+ """
925
+
926
+ api = ApiClient()
927
+
928
+ # Add one day to the end time as the query is exclusive of the last day but
929
+ # we want consistency with _get_compute_usage()
930
+ payload = {
931
+ "start": start_time.date().isoformat(),
932
+ "end": (end_time.date() + datetime.timedelta(days=1)).isoformat()
933
+ }
934
+
935
+ response, response_code = api.make_request(
936
+ uri_path="billing/get_storage_usage",
937
+ verb="GET",
938
+ raise_on_error=False,
939
+ use_api_key=True,
940
+ params=payload
941
+ )
942
+
943
+ if response_code not in [200]:
944
+ msg = f"Failed to query storage usage"
945
+ msg += "\nError %s ...\n%s" % (response_code, response)
946
+ raise Exception(msg)
947
+
948
+ return json.loads(response)['data'][0]
949
+
950
+ def get_compute_usage(start_time, end_time):
951
+ '''
952
+ Query the compute usage for an account.
953
+
954
+ Compute includes licenses, instances and Conductor cost. Everything involved
955
+ with running a job.
956
+
957
+ Args:
958
+ start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
959
+ end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
960
+
961
+ Returns:
962
+ dict: Each key is a date (UTC). The value is a dict with values for:
963
+ - cost: The total accumulated compute cost for the day
964
+ - corehours: The total accumulated core hours for the day
965
+ - walltime: The number of minutes that instances (regardless of type) were running
966
+
967
+ Examples:
968
+ >>> from ciocore import api_client
969
+ >>> api_client.get_compute_usage(start_time, end_time)
970
+ { '2024-01-09': { 'cost': 0.08,
971
+ 'corehours': 0.9,
972
+ 'walltime': 13.93},
973
+ '2024-01-16': { 'cost': 0.12,
974
+ 'corehours': 0.9613,
975
+ 'walltime': 7.21}}
976
+ '''
977
+ date_format = "%a, %d %b %Y %H:%M:%S %Z"
978
+ data = _get_compute_usage(start_time, end_time)
979
+
980
+ # Create a nested default dictionary with initial float values of 0.0
981
+ results = collections.defaultdict(lambda: collections.defaultdict(float))
982
+
983
+ for entry in data:
984
+ entry_start_date = datetime.datetime.strptime(entry['start_time'], date_format).date().isoformat()
985
+
986
+ results[entry_start_date]['walltime'] += entry['minutes']
987
+ results[entry_start_date]['corehours'] += entry['cores']
988
+ results[entry_start_date]['cost'] += entry['license_cost'] + entry['instance_cost']
989
+
990
+ # Round the data to avoid FP errors
991
+ results[entry_start_date]['walltime'] = round(results[entry_start_date]['walltime'], 4)
992
+ results[entry_start_date]['corehours'] = round(results[entry_start_date]['corehours'], 4)
993
+ results[entry_start_date]['cost'] = round(results[entry_start_date]['cost'], 4)
994
+
995
+ return results
996
+
997
+ def get_storage_usage(start_time, end_time):
998
+ '''
999
+ Query the storage usage for an account.
1000
+
1001
+ Storage is calculated twice a day (UTC) and the average is used.
1002
+
1003
+ Args:
1004
+ start_time (datetime.datetime): The first day to include in the report. Only the date is considered and it's assumed to be in UTC.
1005
+ end_time (datetime.datetime): The last day to include in the report. Only the date is considered and it's assumed to be in UTC.
1006
+
1007
+ Returns:
1008
+ dict: Each key is a date (UTC). The value is a dict with values for:
1009
+ - cost: The cost of accumulated storage for that one day
1010
+ - GiB: The total amount of storage used on that day
1011
+
1012
+ Examples:
1013
+ >>> from ciocore import api_client
1014
+ >>> api_client.get_storage_usage(start_time, end_time)
1015
+ { '2024-01-01': {'cost': 4.022, 'GiB': 679.714},
1016
+ '2024-01-02': {'cost': 4.502, 'GiB': 750.34},
1017
+ '2024-01-03': {'cost': 4.502, 'GiB': 750.34}}
1018
+ '''
1019
+ one_day = datetime.timedelta(days=1)
1020
+
1021
+ data = _get_storage_usage(start_time, end_time)
1022
+
1023
+ results = {}
1024
+
1025
+ entry_date = datetime.date.fromisoformat(data['start_date'])
1026
+
1027
+ for cnt, entry in enumerate(data["cost_per_day"]):
1028
+
1029
+ entry_start_date = entry_date.isoformat()
1030
+ results[entry_start_date] = {'cost': float(entry), 'GiB': float(data['gibs_per_day'][cnt])}
1031
+ entry_date += one_day
1032
+
1033
+ return results
1034
+
1035
+
ciocore/data.py CHANGED
@@ -53,7 +53,7 @@ def init(*products, **kwargs):
53
53
 
54
54
  def data(force=False):
55
55
  """
56
- Provide projects, instance types, and software package data.
56
+ Provide projects, instance types, software package, and account data.
57
57
 
58
58
  Keyword Args:
59
59
  force: (bool) If `True`, then force the system to fetch fresh data -- Defaults to `False`.
@@ -62,10 +62,10 @@ def data(force=False):
62
62
  ValueError: Module was not initialized with [init()](/data/#ciocore.data.init).
63
63
 
64
64
  Returns:
65
- dict: Keys are `projects`, `instance_types`, `software`.
65
+ dict: Keys are `projects`, `instance_types`, `software`, `account`.
66
66
 
67
67
  When you access the data, if it has already been fetched, it will be returned. Otherwise,
68
- requests will be made to fetch the data. You may need to authenticate in order to access the
68
+ requests will be made to fetch fresh data. You may need to authenticate in order to access the
69
69
  data.
70
70
 
71
71
  The set of instance types and software can be pruned to match the available platforms
@@ -84,7 +84,8 @@ def data(force=False):
84
84
 
85
85
  * **software** is a PackageTree object containing either all
86
86
  the software available at Conductor, or a subset based on specified products.
87
-
87
+
88
+ * **account** is a dictionary containing account information gained from the JWT token.
88
89
 
89
90
  Examples:
90
91
  >>> from ciocore import data as coredata
@@ -98,6 +99,10 @@ def data(force=False):
98
99
 
99
100
  >>> coredata.data()["instance_types"]
100
101
  <ciocore.hardware_set.HardwareSet object at 0x0000028941CD9DC0>
102
+
103
+ >>> coredata.data()["account"]["account_id"]
104
+ 123456789012
105
+
101
106
  """
102
107
 
103
108
  global __data__
@@ -129,6 +134,9 @@ def data(force=False):
129
134
  except Exception:
130
135
  pass
131
136
  __data__["extra_environment"] = extra_env_vars
137
+
138
+ __data__["account"] = api_client.get_account_data()
139
+
132
140
 
133
141
  # PLATFORMS
134
142
  it_platforms = set([it["operating_system"] for it in instance_types])