cgcsdk 1.0.14__py3-none-any.whl → 1.0.16__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.
cgc/.env CHANGED
@@ -6,4 +6,4 @@ CONFIG_FILE_NAME = cfg.json
6
6
  TMP_DIR = .tmp
7
7
  RELEASE = 1
8
8
  MAJOR_VERSION = 0
9
- MINOR_VERSION = 14
9
+ MINOR_VERSION = 16
cgc/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.16
4
+
5
+ Release on Aug XX, 2024
6
+
7
+ * dynamic storage classes for volumes
8
+ * dynamic templates available for compute
9
+ * dynamic templates available for databases
10
+ * fixed bug: context switched to cfg.json after CGC update
11
+ * updated command: `cgc status`, now it is fully dynamic
12
+ * add flag `-y` to `cgc compute port` group commands
13
+ * `active_deadline_seconds` to `cgc job create` command
14
+ * `cgc db create` now have option `-d` available
15
+ * updated displayed values with `cgc job list`
16
+ * updated version control messages
17
+
18
+ ## 1.0.15
19
+
20
+ Release on June 27, 2024
21
+
22
+ * added new compute template: `unsloth-cu121`
23
+
3
24
  ## 1.0.14
4
25
 
5
26
  Release on June 13, 2024
@@ -3,8 +3,13 @@ import os
3
3
 
4
4
  from cgc.commands.auth import auth_utils
5
5
  from cgc.commands.auth import auth_logic
6
- from cgc.utils.consts.env_consts import TMP_DIR, get_config_file_name
7
- from cgc.utils.config_utils import get_config_path, save_to_config
6
+ from cgc.utils.consts.env_consts import TMP_DIR
7
+ from cgc.utils.config_utils import (
8
+ get_config_path,
9
+ save_to_config,
10
+ get_config_file_name,
11
+ save_to_local_config_context,
12
+ )
8
13
  from cgc.utils.message_utils import key_error_decorator_for_helpers
9
14
 
10
15
 
@@ -17,6 +22,7 @@ def auth_register_response(
17
22
  aes_key, password = auth_utils.get_aes_key_and_password(unzip_dir, priv_key_bytes)
18
23
 
19
24
  os.environ["CONFIG_FILE_NAME"] = config_filename
25
+ save_to_local_config_context(config_filename)
20
26
  save_to_config(
21
27
  user_id=user_id,
22
28
  password=password,
@@ -27,8 +33,8 @@ def auth_register_response(
27
33
  )
28
34
  auth_logic.auth_create_api_key_with_save()
29
35
  shutil.rmtree(TMP_DIR_PATH)
30
- # config.json
31
- if config_filename == "config.json":
36
+ # cfg.json
37
+ if config_filename == "cfg.json":
32
38
  return f"Register successful! You can now use the CLI. Saved data to:{os.path.join(get_config_path(),config_filename)}\n\
33
39
  Consider backup this file. It stores data accessible only to you with which you can access CGC platform."
34
40
  return f"New context created successfully! \nNew config file saved to: {os.path.join(get_config_path(),config_filename)}\n\
cgc/commands/cgc_cmd.py CHANGED
@@ -12,10 +12,11 @@ from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
12
12
  from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
13
13
  from cgc.telemetry.basic import telemetry_permission_set
14
14
  from cgc.commands.compute.compute_responses import compute_logs_response
15
+
15
16
  # from cgc.commands.auth.auth_cmd import auth_register
16
17
  from cgc.utils import set_environment_data, check_if_config_exist, list_all_config_files
17
18
  from cgc.commands.cgc_helpers import table_of_user_context_files
18
- from cgc.utils.config_utils import config_path
19
+ from cgc.utils.config_utils import config_path, save_to_local_config_context
19
20
  from cgc.utils.consts.env_consts import ENV_FILE_PATH
20
21
 
21
22
 
@@ -100,6 +101,7 @@ def switch_context(number: int):
100
101
  # config_filename=file_name,
101
102
  # )
102
103
  set_environment_data("CONFIG_FILE_NAME", file_name)
104
+ save_to_local_config_context(file_name)
103
105
  click.echo(f"Context file changed to: {file_name}")
104
106
 
105
107
 
@@ -1,7 +1,59 @@
1
+ from enum import Enum
2
+ from typing import Union
1
3
  from tabulate import tabulate
2
4
  from cgc.utils.message_utils import key_error_decorator_for_helpers
3
5
 
4
6
 
7
+ def _resource_match(resource: str) -> Union[str, None]:
8
+ if resource == "requests.cpu":
9
+ return "Total CPU"
10
+ elif resource == "requests.memory":
11
+ return "Total RAM"
12
+ elif resource == "requests.nvidia.com/gpu":
13
+ return "Total GPU"
14
+ elif resource.startswith("requests.comtegra.cloud/"):
15
+ gpu_type = resource.split("/")[-1]
16
+ return f"GPU {gpu_type.upper()}"
17
+ elif resource == "persistentvolumeclaims":
18
+ return "Volume Count"
19
+ elif resource == "requests.storage":
20
+ return "Total Storage"
21
+ elif resource.endswith(".storageclass.storage.k8s.io/requests.storage"):
22
+ storage_class = resource.split(".")[0]
23
+ return f"Storage ({storage_class})"
24
+ else:
25
+ return None
26
+
27
+
28
+ class ResourceOrder(Enum):
29
+ CPU = 1
30
+ MEMORY = 2
31
+ GPU = 3
32
+ STORAGE = 4
33
+ VOLUME = 5
34
+ GPU_TYPE = 6
35
+ OTHER = 7
36
+
37
+
38
+ def _resource_order(resource: str) -> ResourceOrder:
39
+ if resource == "requests.cpu":
40
+ return ResourceOrder.CPU
41
+ elif resource == "requests.memory":
42
+ return ResourceOrder.MEMORY
43
+ elif resource == "requests.nvidia.com/gpu":
44
+ return ResourceOrder.GPU
45
+ elif resource.startswith("requests.comtegra.cloud/"):
46
+ return ResourceOrder.GPU_TYPE
47
+ elif resource == "persistentvolumeclaims":
48
+ return ResourceOrder.VOLUME
49
+ elif resource == "requests.storage":
50
+ return ResourceOrder.STORAGE
51
+ elif resource.endswith(".storageclass.storage.k8s.io/requests.storage"):
52
+ return ResourceOrder.STORAGE
53
+ else:
54
+ return ResourceOrder.OTHER
55
+
56
+
5
57
  @key_error_decorator_for_helpers
6
58
  def cgc_status_response(data: dict):
7
59
  """Generates and prints resource limits and available resources in a pretty format
@@ -12,54 +64,31 @@ def cgc_status_response(data: dict):
12
64
  :type metric_error: str
13
65
  """
14
66
 
15
- resources_available = data["details"]["available_resources"]
16
- resources_limits = data["details"]["limits_resources"]
17
- list_headers = ["Resource", "Type", "Available", "Limit"]
18
- resource_names = [
19
- "CPU",
20
- "RAM",
21
- "GPU",
22
- "-",
23
- "-",
24
- # "-",
25
- "Volume",
26
- "Storage",
27
- "-",
28
- "-",
29
- ]
30
- types = [
31
- "",
32
- "",
33
- "",
34
- "A100",
35
- "A5000",
36
- # "V100",
37
- "Count",
38
- "",
39
- "nvme",
40
- "ssd",
41
- ]
42
- resource_keys = [
43
- "requests.cpu",
44
- "requests.memory",
45
- "requests.nvidia.com/gpu",
46
- "requests.comtegra.cloud/a100",
47
- "requests.comtegra.cloud/a5000",
48
- # "requests.comtegra.cloud/v100",
49
- "persistentvolumeclaims",
50
- "requests.storage",
51
- "nvme-rwx.storageclass.storage.k8s.io/requests.storage",
52
- "ssd-rwx.storageclass.storage.k8s.io/requests.storage",
53
- ]
67
+ resources_available: dict = data["details"]["available_resources"]
68
+ resources_limits: dict = data["details"]["limits_resources"]
69
+ list_headers = ["Resource", "Available", "Limit"]
70
+ resource_names = []
54
71
 
55
72
  resources_available_list = []
56
73
  resources_limits_list = []
57
- for key in resource_keys:
58
- available = resources_available[key]
59
- limit = resources_limits[key]
60
- if "storage" in key or "memory" in key:
61
- available = f"{available} Gb"
62
- limit = f"{limit} Gb"
74
+ resources_available = {
75
+ key: value
76
+ for key, value in sorted(
77
+ resources_available.items(),
78
+ key=lambda x: (_resource_order(x[0]).value, x[1]),
79
+ )
80
+ }
81
+ for resource, available in resources_available.items():
82
+ if resource not in resources_limits.keys():
83
+ continue
84
+ resource_parsed = _resource_match(resource)
85
+ if resource_parsed is None:
86
+ continue
87
+ resource_names.append(resource_parsed)
88
+ limit = resources_limits[resource]
89
+ if "storage" in resource or "memory" in resource:
90
+ available = f"{available} GB"
91
+ limit = f"{limit} GB"
63
92
  else:
64
93
  available = int(available)
65
94
  limit = int(limit)
@@ -67,9 +96,7 @@ def cgc_status_response(data: dict):
67
96
  resources_limits_list.append(limit)
68
97
 
69
98
  return tabulate(
70
- list(
71
- zip(resource_names, types, resources_available_list, resources_limits_list)
72
- ),
99
+ list(zip(resource_names, resources_available_list, resources_limits_list)),
73
100
  headers=list_headers,
74
101
  )
75
102
 
@@ -1,9 +1,17 @@
1
- from enum import Enum
1
+ from abc import ABC, abstractmethod
2
2
 
3
3
 
4
- class CGCEntityList(Enum):
4
+ class CGCEntityList(ABC):
5
5
  """Base class for other lists"""
6
6
 
7
+ @staticmethod
8
+ @abstractmethod
9
+ def load_data() -> list[str]:
10
+ pass
11
+
7
12
  @classmethod
8
13
  def get_list(cls) -> list[str]:
9
- return [el.value for el in cls]
14
+ try:
15
+ return [el for el in cls.load_data()]
16
+ except TypeError:
17
+ return []
@@ -96,9 +96,21 @@ def compute_filebrowser_delete():
96
96
  default=True,
97
97
  help="If set, port will NOT be exposed to the internet. By default port is exposed to the internet.",
98
98
  )
99
- def compute_port_add(app_name: str, port_name: str, port: int, ingress: bool):
99
+ @click.option(
100
+ "-y",
101
+ "--yes",
102
+ "yes",
103
+ is_flag=True,
104
+ type=click.BOOL,
105
+ required=False,
106
+ default=False,
107
+ help="If set, command will not ask for confirmation",
108
+ )
109
+ def compute_port_add(
110
+ app_name: str, port_name: str, port: int, ingress: bool, yes: bool
111
+ ):
100
112
  """Add a port to a running resource"""
101
- while True:
113
+ while True and not yes:
102
114
  click.echo(
103
115
  'Adding a port to a running resource will expose it to the internet. If you want to add a port without exposing it to the internet, use the "--no-ingress" flag.'
104
116
  )
@@ -161,9 +173,21 @@ def compute_port_add(app_name: str, port_name: str, port: int, ingress: bool):
161
173
  default=True,
162
174
  help="If set, port will NOT be exposed to the internet. By default port is exposed to the internet.",
163
175
  )
164
- def compute_port_update(app_name: str, port_name: str, port: int, ingress: bool):
176
+ @click.option(
177
+ "-y",
178
+ "--yes",
179
+ "yes",
180
+ is_flag=True,
181
+ type=click.BOOL,
182
+ required=False,
183
+ default=False,
184
+ help="If set, command will not ask for confirmation",
185
+ )
186
+ def compute_port_update(
187
+ app_name: str, port_name: str, port: int, ingress: bool, yes: bool
188
+ ):
165
189
  """Update a port in a running resource"""
166
- while True:
190
+ while True and not yes:
167
191
  click.echo(
168
192
  'Updating a port in a running resource will expose it to the internet. If you want to update a port without exposing it to the internet, use the "--no-ingress" flag.'
169
193
  )
@@ -204,9 +228,19 @@ def compute_port_update(app_name: str, port_name: str, port: int, ingress: bool)
204
228
  required=True,
205
229
  help="Name of port",
206
230
  )
207
- def compute_port_delete(app_name: str, port_name: str):
231
+ @click.option(
232
+ "-y",
233
+ "--yes",
234
+ "yes",
235
+ is_flag=True,
236
+ type=click.BOOL,
237
+ required=False,
238
+ default=False,
239
+ help="If set, command will not ask for confirmation",
240
+ )
241
+ def compute_port_delete(app_name: str, port_name: str, yes: bool):
208
242
  """Delete a port from a running resource"""
209
- while True:
243
+ while True and not yes:
210
244
  click.echo(
211
245
  'Deleting a port from a running resource will expose it to the internet. If you want to delete a port without exposing it to the internet, use the "--no-ingress" flag.'
212
246
  )
@@ -1,27 +1,23 @@
1
+ from enum import Enum
2
+
1
3
  from cgc.commands.cgc_models import CGCEntityList
4
+ from cgc.utils.config_utils import read_from_local_cfg
5
+ import cgc.utils.version_control as version_control
2
6
 
3
7
 
4
8
  class ComputesList(CGCEntityList):
5
- """List of templates in cgc-server
9
+ """List of compute templates in cgc-server"""
6
10
 
7
- :param Enum: name of template
8
- :type Enum: str
9
- """
10
-
11
- NVIDIA_TENSORFLOW = "nvidia-tensorflow"
12
- NVIDIA_RAPIDS = "nvidia-rapids"
13
- NVIDIA_PYTORCH = "nvidia-pytorch"
14
- NVIDIA_TRITON = "nvidia-triton"
15
- NGINX = "nginx"
16
- LABEL_STUDIO = "label-studio"
17
- COMFY_UI = "comfy-ui"
18
- RAG = "rag"
19
- DEEPSTREAM = "deepstream"
20
- T2V_TRANSFORMERS = "t2v-transformers"
21
- CUSTOM = "custom"
11
+ @staticmethod
12
+ def load_data() -> list[str]:
13
+ try:
14
+ return read_from_local_cfg("compute_templates")
15
+ except FileNotFoundError:
16
+ version_control.get_server_version() # possible loop on error
17
+ return ComputesList.load_data()
22
18
 
23
19
 
24
- class GPUsList(CGCEntityList):
20
+ class GPUsList(Enum):
25
21
  """List of templates in cgc-server
26
22
 
27
23
  :param Enum: name of template
@@ -34,3 +30,6 @@ class GPUsList(CGCEntityList):
34
30
  H100 = "H100"
35
31
  P40 = "P40"
36
32
  P100 = "P100"
33
+
34
+ def get_list() -> list[str]:
35
+ return [el.value for el in GPUsList]
cgc/commands/db/db_cmd.py CHANGED
@@ -46,11 +46,19 @@ def db_group():
46
46
  multiple=True,
47
47
  help="Volume name to be mounted with default mount path",
48
48
  )
49
+ @click.option(
50
+ "-d",
51
+ "--resource-data",
52
+ "resource_data",
53
+ multiple=True,
54
+ help="List of optional arguments to be passed to the app, key=value format",
55
+ )
49
56
  def db_create(
50
57
  entity: str,
51
58
  cpu: int,
52
59
  memory: int,
53
60
  volumes: list[str],
61
+ resource_data: list[str],
54
62
  name: str,
55
63
  ):
56
64
  """
@@ -81,6 +89,7 @@ def db_create(
81
89
  cpu=cpu,
82
90
  memory=memory,
83
91
  volumes=volumes,
92
+ resource_data=resource_data,
84
93
  )
85
94
  __res = call_api(
86
95
  request=EndpointTypes.post,
@@ -1,19 +1,21 @@
1
1
  from cgc.commands.cgc_models import CGCEntityList
2
2
  from cgc.commands.exceptions import DatabaseCreationException
3
+ from cgc.utils.config_utils import read_from_local_cfg
4
+ import cgc.utils.version_control as version_control
3
5
 
4
6
 
5
7
  class DatabasesList(CGCEntityList):
6
- """List of templates in cgc-server
8
+ """List of database templates in cgc-server"""
7
9
 
8
- :param Enum: name of template
9
- :type Enum: str
10
- """
11
-
12
- POSTGRESQL = "postgresql"
13
- WEAVIATE = "weaviate"
14
- # MINIO = "minio"
15
- MONGODB = "mongodb"
16
- REDIS = "redis"
10
+ @staticmethod
11
+ def load_data() -> list[str]:
12
+ try:
13
+ list_of_templates = read_from_local_cfg("database_templates")
14
+ except FileNotFoundError:
15
+ version_control.get_server_version() # possible loop on error
16
+ return DatabasesList.load_data()
17
+ list_of_templates.extend(["mongodb", "redis"])
18
+ return list_of_templates
17
19
 
18
20
  @staticmethod
19
21
  def verify(entity: str) -> str:
@@ -28,6 +28,7 @@ def job_create_payload(
28
28
  startup_command: str = "",
29
29
  repository_secret: str = "",
30
30
  ttl_seconds_after_finished: Optional[int] = None,
31
+ active_deadline_seconds: Optional[int] = None,
31
32
  ):
32
33
  """
33
34
  Create payload for app creation.
@@ -38,6 +39,8 @@ def job_create_payload(
38
39
 
39
40
  if ttl_seconds_after_finished is not None:
40
41
  extra_payload["ttl_seconds_after_finished"] = ttl_seconds_after_finished
42
+ if active_deadline_seconds is not None:
43
+ extra_payload["active_deadline_seconds"] = active_deadline_seconds
41
44
 
42
45
  payload = {
43
46
  "resource_data": {
@@ -111,13 +114,17 @@ def get_job_list(job_list: list, job_pod_list: list):
111
114
 
112
115
  for i, job_data in enumerate(job_list):
113
116
  list_of_json_job_data[i]["name"] = job_data.get("name", "")
114
- list_of_json_job_data[i]["ttl_seconds_after_finished"] = job_data.get(
117
+ list_of_json_job_data[i]["ttl"] = job_data.get(
115
118
  "ttl_seconds_after_finished", "N/A"
116
119
  )
120
+ list_of_json_job_data[i]["ads"] = job_data.get("active_deadline_seconds", "N/A")
117
121
  for job in list_of_json_job_data:
118
122
  for job_pod in job_pod_list:
119
- if job_pod.get("labels", {}).get("app-name") == job.get("name"):
120
- job["status"] = job_pod.get("status", "Pending")
123
+ job_pod_labels: dict = job_pod.get("labels", {})
124
+ if job_pod_labels.get("app-name", "") == job.get("name"):
125
+ job["status"] = job_pod["status"]
126
+ job["gpu-count"] = job_pod_labels.get("gpu-count", 0)
127
+ job["gpu-label"] = job_pod_labels.get("gpu-label", "N/A")
121
128
  # job["status_reason"] = [
122
129
  # x.get("reason", "N/A") for x in job_pod.get("status_reasons", [])
123
130
  # ]
@@ -159,12 +166,12 @@ def get_job_json_data(job_list: list):
159
166
 
160
167
  job_data = {
161
168
  "name": job.get("labels", {}).get("app-name"),
162
- "status": job.get("status", {}).get("phase", "Pending"),
169
+ "status": job.get("status", {}).get(
170
+ "phase", "Unknown"
171
+ ), # only happen when Pod "disappeared" from the k8s - BUG
163
172
  "volumes_mounted": volumes_mounted,
164
173
  "cpu": cpu,
165
174
  "ram": ram,
166
- "gpu-count": job.get("labels", {}).get("gpu-count", 0),
167
- "gpu-label": job.get("labels", {}).get("gpu-label", "N/A"),
168
175
  }
169
176
  # getting rid of unwanted and used values
170
177
  if "pod-template-hash" in job["labels"].keys():
@@ -155,6 +155,13 @@ def job_list():
155
155
  default=None,
156
156
  help="Time to live in seconds after app is finished",
157
157
  )
158
+ @click.option(
159
+ "--ads",
160
+ "active_deadline_seconds",
161
+ type=click.INT,
162
+ default=None,
163
+ help="Time to live in seconds after app is started",
164
+ )
158
165
  def job_create(
159
166
  gpu: int,
160
167
  gpu_type: str,
@@ -170,6 +177,7 @@ def job_create(
170
177
  startup_command: str,
171
178
  repository_secret: str,
172
179
  ttl_seconds_after_finished: int,
180
+ active_deadline_seconds: int,
173
181
  ):
174
182
  """
175
183
  Create job in user namespace.
@@ -200,6 +208,8 @@ def job_create(
200
208
  :type repository_secret: str
201
209
  :param ttl_seconds_after_finished: time to live in seconds after app is finished
202
210
  :type ttl_seconds_after_finished: int
211
+ :param active_deadline_seconds: time to live in seconds after app is started
212
+ :type active_deadline_seconds: int
203
213
  """
204
214
  api_url, headers = get_api_url_and_prepare_headers()
205
215
  url = f"{api_url}/v1/api/job/create"
@@ -226,6 +236,7 @@ def job_create(
226
236
  startup_command=cleaned_data,
227
237
  repository_secret=repository_secret,
228
238
  ttl_seconds_after_finished=ttl_seconds_after_finished,
239
+ active_deadline_seconds=active_deadline_seconds,
229
240
  )
230
241
  __res = call_api(
231
242
  request=EndpointTypes.post,
@@ -1,12 +1,11 @@
1
- def volume_create_payload_validator(name, access, size, disk_type):
1
+ def volume_create_payload_validator(name, size, storage_class):
2
2
  """
3
3
  Create payload for volume creation.
4
4
  """
5
5
  payload = {
6
6
  "name": name,
7
- "access_type": access,
7
+ "storage_class": storage_class,
8
8
  "size": size,
9
- "disks_type": disk_type,
10
9
  "auto_mount": "false",
11
10
  }
12
11
  return payload
@@ -8,7 +8,9 @@ from cgc.commands.volume.data_model import (
8
8
  volume_mount_payload_validator,
9
9
  volume_umount_payload_validator,
10
10
  )
11
+ from cgc.commands.volume.volume_models import StorageClassList
11
12
  from cgc.commands.volume.volume_responses import (
13
+ volume_storage_class_details_response,
12
14
  volume_list_response,
13
15
  volume_mount_response,
14
16
  volume_umount_response,
@@ -47,6 +49,29 @@ def volume_list():
47
49
  )
48
50
 
49
51
 
52
+ @volume_group.command("details", cls=CustomCommand)
53
+ @click.argument("storage_class")
54
+ def volume_storage_class_details(storage_class: str):
55
+ """Get details of storage class.
56
+ \f
57
+ :param storage_class: type of disk
58
+ :type storage_class: str
59
+ """
60
+ api_url, headers = get_api_url_and_prepare_headers()
61
+ url = f"{api_url}/v1/api/storage/volume/details/{storage_class}"
62
+ metric = "volume.details"
63
+ __res = call_api(
64
+ request=EndpointTypes.get,
65
+ url=url,
66
+ headers=headers,
67
+ )
68
+ click.echo(
69
+ volume_storage_class_details_response(
70
+ retrieve_and_validate_response_send_metric(__res, metric)
71
+ )
72
+ )
73
+
74
+
50
75
  @volume_group.command("create", cls=CustomCommand)
51
76
  @click.argument("name")
52
77
  @click.option(
@@ -58,38 +83,28 @@ def volume_list():
58
83
  required=True,
59
84
  )
60
85
  @click.option(
61
- "-t",
62
- "--type",
63
- "disk_type",
64
- type=click.Choice(["ssd", "nvme"]),
65
- default="ssd",
86
+ "-sc",
87
+ "--storage-class",
88
+ "storage_class",
89
+ type=click.Choice([*StorageClassList.load_data()]),
90
+ default=StorageClassList.load_default_storage_class(),
66
91
  help="Type of disk",
67
92
  )
68
- @click.option(
69
- "-a",
70
- "--access",
71
- "access",
72
- type=click.Choice(["rwx", "rwo"]),
73
- default="rwx",
74
- help="Volume access mode",
75
- )
76
- def volume_create(name: str, size: int, disk_type: str, access: str):
93
+ def volume_create(name: str, size: int, storage_class: str):
77
94
  """Create volume in user namespace.
78
95
  \f
79
96
  :param name: name of volume
80
97
  :type name: str
81
98
  :param size: size of volume in GiB
82
99
  :type size: int
83
- :param type: type of volume - HDD, SSD or NVMe
84
- :type type: str
85
- :param access: access type of volume - RWO or RWX
86
- :type access: str
100
+ :param storage_class: type of disk
101
+ :type storage_class: str
87
102
  """
88
103
  api_url, headers = get_api_url_and_prepare_headers()
89
104
  url = f"{api_url}/v1/api/storage/volume/create"
90
105
  metric = "volume.create"
91
106
  __payload = volume_create_payload_validator(
92
- name=name, access=access, size=size, disk_type=disk_type
107
+ name=name, size=size, storage_class=storage_class
93
108
  )
94
109
  __res = call_api(
95
110
  request=EndpointTypes.post,
@@ -0,0 +1,22 @@
1
+ from cgc.utils.config_utils import read_from_local_cfg
2
+ import cgc.utils.version_control as version_control
3
+
4
+
5
+ class StorageClassList:
6
+ """List of compute templates in cgc-server"""
7
+
8
+ @staticmethod
9
+ def load_data() -> list[str]:
10
+ try:
11
+ return read_from_local_cfg("storage_classes")
12
+ except FileNotFoundError:
13
+ version_control.get_server_version() # possible loop on error
14
+ return StorageClassList.load_data()
15
+
16
+ @staticmethod
17
+ def load_default_storage_class() -> str:
18
+ try:
19
+ return read_from_local_cfg("default_storage_class")
20
+ except FileNotFoundError:
21
+ version_control.get_server_version() # possible loop on error
22
+ return StorageClassList.load_default_storage_class()
@@ -33,6 +33,29 @@ def volume_list_response(data: dict) -> str:
33
33
  return tabulate(volume_list_to_print, headers=list_headers)
34
34
 
35
35
 
36
+ @key_error_decorator_for_helpers
37
+ def volume_storage_class_details_response(data: dict) -> str:
38
+ """Create response string for volume storage class details command.
39
+
40
+ :return: Response string.
41
+ :rtype: str
42
+ """
43
+ # ["details"]["storage_class"] -> storage class name
44
+ # ["details"]["storage_class_info"] -> billing_cost, storage_type, reclaim_policy, volume_binding_mode
45
+
46
+ storage_class_headers = [
47
+ "name",
48
+ *data["details"]["storage_class_info"].keys(),
49
+ ]
50
+ storage_class_data = [
51
+ (
52
+ data["details"]["storage_class"],
53
+ *data["details"]["storage_class_info"].values(),
54
+ )
55
+ ]
56
+ return tabulate(storage_class_data, headers=storage_class_headers)
57
+
58
+
36
59
  @key_error_decorator_for_helpers
37
60
  def volume_create_response(data: dict) -> str:
38
61
  """Create response string for volume create command.
@@ -48,7 +71,10 @@ def volume_create_response(data: dict) -> str:
48
71
  / 1000
49
72
  )
50
73
  access = data["details"]["volume_created"]["access_type"][0]
51
- disk_type = data["details"]["volume_created"]["disks_type"]
74
+ try:
75
+ disk_type = data["details"]["volume_created"]["disks_type"]
76
+ except KeyError:
77
+ disk_type = data["details"]["storage_class"]
52
78
  change_gauge(f"{get_namespace()}.volume.count", 1)
53
79
  change_gauge(f"{get_namespace()}.volume.totalSizeAccumulated", size)
54
80
 
cgc/telemetry/basic.py CHANGED
@@ -75,7 +75,7 @@ def increment_metric(metric, is_error: bool = False):
75
75
  if is_error:
76
76
  metric = f"{metric}.error"
77
77
  click.echo(
78
- f"If you want to open support request, attach command used, status code and error message via support system at https://support.comtegra.pl/"
78
+ "If you want to open support request, attach command used, status code and error message via support system at https://support.comtegra.pl/"
79
79
  )
80
80
  else:
81
81
  metric = f"{metric}.ok"
cgc/utils/config_utils.py CHANGED
@@ -1,11 +1,19 @@
1
1
  import json
2
2
  import os
3
3
  import sys
4
+ from typing import Union
4
5
  import click
5
6
 
6
7
  from cgc.commands.auth import NoNamespaceInConfig, NoConfigFileFound
7
8
  from cgc.utils.message_utils import prepare_error_message
8
- from cgc.utils.consts.env_consts import CGC_API_URL, CGC_SECRET, get_config_file_name
9
+ from cgc.utils.consts.env_consts import CGC_API_URL, CGC_SECRET
10
+
11
+
12
+ def get_config_file_name():
13
+ try:
14
+ return read_from_local_cfg("context_filename")
15
+ except Exception:
16
+ return os.getenv("CONFIG_FILE_NAME")
9
17
 
10
18
 
11
19
  def get_config_path():
@@ -32,6 +40,64 @@ def get_config_path():
32
40
  config_path = get_config_path()
33
41
 
34
42
 
43
+ def save_to_local_config_context(context_filename: str):
44
+ """Function allowing adding a variable number of key-value pairs to the config file.
45
+ If config file does not exist, it is created, otherwise key-value pairs are appended to existing config.
46
+ Values for existing keys are overwritten.
47
+
48
+ :param kwargs: key-value pairs to be saved in the config file
49
+ :type kwargs: dict
50
+ """
51
+ read_cfg = {}
52
+ user_config_file = os.path.join(config_path, "cgc.json")
53
+ if not os.path.isdir(config_path):
54
+ os.makedirs(config_path)
55
+ try:
56
+ with open(user_config_file, "r", encoding="UTF-8") as f:
57
+ read_cfg = json.load(f)
58
+ except FileNotFoundError:
59
+ pass
60
+ except json.decoder.JSONDecodeError:
61
+ pass
62
+
63
+ with open(user_config_file, "w", encoding="UTF-8") as f:
64
+ final_cfg = {**read_cfg, "context_filename": context_filename}
65
+ json.dump(final_cfg, f)
66
+
67
+
68
+ def save_to_local_config(**kwargs):
69
+ """Function allowing adding a variable number of key-value pairs to the config file.
70
+ If config file does not exist, it is created, otherwise key-value pairs are appended to existing config.
71
+ Values for existing keys are overwritten.
72
+
73
+ :param kwargs: key-value pairs to be saved in the config file
74
+ :type kwargs: dict
75
+ """
76
+ read_cfg = {}
77
+ user_config_file = os.path.join(config_path, "cgc.json")
78
+ if not os.path.isdir(config_path):
79
+ os.makedirs(config_path)
80
+ try:
81
+ with open(user_config_file, "r", encoding="UTF-8") as f:
82
+ read_cfg = json.load(f)
83
+ except FileNotFoundError:
84
+ pass
85
+ except json.decoder.JSONDecodeError:
86
+ pass
87
+
88
+ with open(user_config_file, "w", encoding="UTF-8") as f:
89
+ cgc_api_url = read_from_cfg("cgc_api_url")
90
+ if "context_filename" in kwargs:
91
+ kwargs.pop("context_filename")
92
+ previous_server_values: dict = read_cfg.get(cgc_api_url, {})
93
+ previous_server_values.update(**kwargs)
94
+ final_cfg = {
95
+ **read_cfg,
96
+ cgc_api_url: {**previous_server_values},
97
+ }
98
+ json.dump(final_cfg, f)
99
+
100
+
35
101
  def save_to_config(**kwargs):
36
102
  """Function allowing adding a variable number of key-value pairs to the config file.
37
103
  If config file does not exist, it is created, otherwise key-value pairs are appended to existing config.
@@ -45,8 +111,8 @@ def save_to_config(**kwargs):
45
111
  if not os.path.isdir(config_path):
46
112
  os.makedirs(config_path)
47
113
  try:
48
- f = open(user_config_file, "r+", encoding="UTF-8")
49
- read_cfg = json.load(f)
114
+ with open(user_config_file, "r", encoding="UTF-8") as f:
115
+ read_cfg = json.load(f)
50
116
  except FileNotFoundError:
51
117
  pass
52
118
 
@@ -70,6 +136,42 @@ def is_config_file_present():
70
136
  return False
71
137
 
72
138
 
139
+ def read_from_local_cfg(key: str) -> Union[list, str]:
140
+ """Function to read a single value from the CGC config
141
+
142
+ :param key: key name to read the value from config
143
+ :type key: str
144
+ :return: value for the provided key
145
+ :rtype: _type_
146
+ """
147
+ filename_with_path = os.path.join(config_path, "cgc.json")
148
+ try:
149
+ with open(filename_with_path, "r", encoding="UTF-8") as f:
150
+ read_cfg: dict = json.load(f)
151
+ if key == "context_filename":
152
+ return read_cfg[key]
153
+ cgc_api_url = read_from_cfg("cgc_api_url")
154
+ if key is None:
155
+ return read_cfg[cgc_api_url]
156
+ return read_cfg[cgc_api_url][key]
157
+ except FileNotFoundError as e:
158
+ raise e
159
+ except KeyError as e:
160
+ if (
161
+ key == "compute_templates"
162
+ or key == "database_templates"
163
+ or key == "storage_classes"
164
+ ):
165
+ return []
166
+ elif key == "default_storage_class":
167
+ return None
168
+ raise NoConfigFileFound() from e
169
+ except json.decoder.JSONDecodeError as e:
170
+ if key == "context_filename":
171
+ raise FileNotFoundError from e
172
+ return []
173
+
174
+
73
175
  def read_from_cfg(key: str, filename=None):
74
176
  """Function to read a single value from config
75
177
 
@@ -83,7 +185,7 @@ def read_from_cfg(key: str, filename=None):
83
185
  else:
84
186
  filename_with_path = os.path.join(config_path, filename)
85
187
  try:
86
- with open(filename_with_path, "r+", encoding="UTF-8") as f:
188
+ with open(filename_with_path, "r", encoding="UTF-8") as f:
87
189
  read_cfg = json.load(f)
88
190
  if key is None:
89
191
  return read_cfg
@@ -38,7 +38,3 @@ TMP_DIR = os.getenv("TMP_DIR")
38
38
  RELEASE = int(os.getenv("RELEASE"))
39
39
  MAJOR_VERSION = int(os.getenv("MAJOR_VERSION"))
40
40
  MINOR_VERSION = int(os.getenv("MINOR_VERSION"))
41
-
42
-
43
- def get_config_file_name():
44
- return os.getenv("CONFIG_FILE_NAME")
@@ -5,7 +5,7 @@ UNKNOWN_ERROR = "An unknown error occurred. Please try again or contact support
5
5
  TIMEOUT_ERROR = (
6
6
  "Connection timed out. Try again or contact support at support@comtegra.pl"
7
7
  )
8
- CONNECTION_ERROR = "Error connecting to the server. Try again or contact support at support@comtegra.pl"
8
+ CONNECTION_ERROR = "Your current context server is not reachable. Try again or contact support at support@comtegra.pl"
9
9
  CERTIFICATE_ERROR = " Could not find a suitable TLS CA certificate bundle, invalid path to server certificate."
10
10
 
11
11
  DISABLED_ERROROUTDATED_MAJOR = (
@@ -16,6 +16,8 @@ from cgc.utils.consts.message_consts import (
16
16
  )
17
17
 
18
18
  from cgc.utils.requests_helper import call_api, EndpointTypes
19
+ from cgc.utils.config_utils import save_to_local_config
20
+ import logging
19
21
 
20
22
 
21
23
  def get_server_version():
@@ -29,11 +31,9 @@ def get_server_version():
29
31
  try:
30
32
  __res.raise_for_status()
31
33
  except Exception:
32
- click.echo(
33
- "Your current context server is not available, cannot check server version.",
34
- color="red",
35
- )
34
+ logging.debug("Your current context server is not reachable.")
36
35
  else:
36
+ save_to_local_config(**__res.json())
37
37
  return __res.json()
38
38
 
39
39
 
@@ -59,8 +59,10 @@ def check_version():
59
59
  "server_status"
60
60
  ] # braking change - 0.9.0, will not work with lower server version
61
61
  server_version = f"{server_release}.{server_major}.{server_minor}"
62
- client_version = f"{RELEASE}.{MAJOR_VERSION}.{MINOR_VERSION}"
63
- if server_major >= MAJOR_VERSION and server_release > RELEASE:
62
+ client_version = _get_version()
63
+ if (
64
+ server_major > MAJOR_VERSION and server_release == RELEASE
65
+ ) or server_release > RELEASE:
64
66
  click.echo(prepare_error_message(OUTDATED_MAJOR))
65
67
  print_compare_versions(server_version, client_version)
66
68
  while True:
@@ -93,6 +95,21 @@ def check_version():
93
95
  f"Server at maintenance, current status: {server_status}"
94
96
  )
95
97
  )
98
+ if server_release == RELEASE and (
99
+ server_major < MAJOR_VERSION or server_minor < MINOR_VERSION
100
+ ):
101
+ click.echo(
102
+ prepare_warning_message(
103
+ "You are using a newer client version than the server."
104
+ )
105
+ )
106
+ print_compare_versions(server_version, client_version)
107
+ elif server_release != RELEASE:
108
+ click.echo(
109
+ prepare_warning_message(
110
+ f"Server version is not compatible with the client version. Server version: {server_version}, Client version: {client_version}"
111
+ )
112
+ )
96
113
 
97
114
 
98
115
  def _get_version():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cgcsdk
3
- Version: 1.0.14
3
+ Version: 1.0.16
4
4
  Summary: Comtegra GPU Cloud REST API client
5
5
  Home-page: https://git.comtegra.pl/
6
6
  Author: Comtegra AI Team
@@ -1,22 +1,22 @@
1
- cgc/.env,sha256=C_RaN_SqTDvKhLL3epnhR5d2Y_0V8Uw-fEEGTkOlS1Q,210
2
- cgc/CHANGELOG.md,sha256=a7qt1BD65-gg2BeD6Uk46lXosgfcJjqE8LvBaLuFe5M,9024
1
+ cgc/.env,sha256=VMwTqev7-Fa9XaP3Ofy8Th-Dl2GurIWONmQgSLCUl2A,210
2
+ cgc/CHANGELOG.md,sha256=gbcedsO6U2gjiefnjcOYV4czqaG0-QmtsSnykXZ_iL4,9625
3
3
  cgc/__init__.py,sha256=d03Xv8Pw4ktNyUHfmicP6XfxYPXnVYLaCZPyUlg_RNQ,326
4
4
  cgc/cgc.py,sha256=3I_Ef0ggX9caaJKJkhfGYSe8XwkHzSWxwGAClMHDnUs,1663
5
5
  cgc/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  cgc/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- cgc/commands/cgc_cmd.py,sha256=QwqjZl9FmAMOvuUQMJm-wOV88OxbdwK5UIS0zkg-mAo,4956
8
- cgc/commands/cgc_cmd_responses.py,sha256=wO9Hf5_4jaw1ZRhQvh9Za0S_vhc22t-0_CoEGz5ndNE,2245
7
+ cgc/commands/cgc_cmd.py,sha256=xuRoMtO7VG0sZbOFrEGxPWRg94SBrgyRJDQif4el7UM,5031
8
+ cgc/commands/cgc_cmd_responses.py,sha256=0bkRIxfd7EiVdShOubxhDiUSlGj6yEZQL_NOHtu334k,3525
9
9
  cgc/commands/cgc_helpers.py,sha256=ngArFjVw-8P_2g7J8k3b9xgDxfJw7JeaOtkhTySMSGU,1174
10
- cgc/commands/cgc_models.py,sha256=T4eCMO5AO2kZpIRqpZ7ZfgO9gxCo_MLlY33msCzrLvA,182
10
+ cgc/commands/cgc_models.py,sha256=ThYrflL6gfiu3u3cWCbiisY7vfkKbo9JGqd5Jqr2jXc,352
11
11
  cgc/commands/exceptions.py,sha256=kVCWEdcqmkLO5QVIINdEXQw29_glyX2C11ZMFgT7b8E,155
12
12
  cgc/commands/auth/__init__.py,sha256=JnqNezSEUEVVPQIymya4llXr8LIug2vymQSAGBr4Ovg,397
13
13
  cgc/commands/auth/auth_cmd.py,sha256=f8_2oIlFQ1k0zvJXGfrwkdVoK5EJBwWesNdOcjtY_uM,4220
14
14
  cgc/commands/auth/auth_logic.py,sha256=PzEyC6OBRSne_5dQT-cq69J0i8BqfGL0MIoXW-yM8qE,2281
15
- cgc/commands/auth/auth_responses.py,sha256=yMGQzggdRkO3hYgXiy6DAFqmVw4lkgfd12g1qQx-CBc,2000
15
+ cgc/commands/auth/auth_responses.py,sha256=kIGyCbNqSQKhDsVpIIcbzR-1bFQHkC3ClbUPBNfKPwk,2095
16
16
  cgc/commands/auth/auth_utils.py,sha256=8NZQQuR0JDCxRkNOHy8nRLDpeT3W87iNyaBI1owj79c,3597
17
17
  cgc/commands/compute/__init__.py,sha256=lCdLfZ0ECSHtXEUSwq5YRHH85yXHchSsz8ZJvmClPtI,239
18
- cgc/commands/compute/compute_cmd.py,sha256=eXKeoOV3DsOypuxzKMa3MpITOKKbF2cZjy30iofHHG8,15489
19
- cgc/commands/compute/compute_models.py,sha256=yse6mCEhhE2RdWIO7EHx4r5Y-350gWWy4UWFsdltNNg,778
18
+ cgc/commands/compute/compute_cmd.py,sha256=YZYR0milioX8aShLUC5NSKWcJSimQx6d4WmweAgdCrw,16131
19
+ cgc/commands/compute/compute_models.py,sha256=MNwxG94fFJfaQ3kGlVXSJh1H5qPHLSU_6BnoiuWS7UA,860
20
20
  cgc/commands/compute/compute_responses.py,sha256=DnRIZC_F1ZE887fMOXXYGYuYmB9n5x63sf8Ga_TL4Ww,5806
21
21
  cgc/commands/compute/compute_utils.py,sha256=Pj4xa-C8-MzXF-CCnnHNXn0sXjHy_t-N7c-eig-vF1Y,9182
22
22
  cgc/commands/compute/billing/__init__.py,sha256=ccjz-AzBCROjuR11qZRM4_62slI9ErmLi27xPUoRPHM,752
@@ -24,13 +24,13 @@ cgc/commands/compute/billing/billing_cmd.py,sha256=8lGf2GPyr09ZmqkRshmgfSPQgXa9o
24
24
  cgc/commands/compute/billing/billing_responses.py,sha256=5vQSR_d41uizengzfXlHXL7XivO_73PpWdKmoUgqYNw,1965
25
25
  cgc/commands/compute/billing/billing_utils.py,sha256=zXLbBBcWeOgur-r0OKiIjaKeaxMNxASXWzCTeTsyC6o,4711
26
26
  cgc/commands/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- cgc/commands/db/db_cmd.py,sha256=4iamsHTy68YjrKztyRGz2PqQgo54Q_rURgm2zpp_hL8,3512
28
- cgc/commands/db/db_models.py,sha256=oJUAF23nb_r8fSyhLHMJrRbRzLY_vpJQu6Vy4WRa0cg,1036
27
+ cgc/commands/db/db_cmd.py,sha256=cUS5t91QL_VfLaXJITYUjBiszoE-P8PYnm3u8VkmHEQ,3750
28
+ cgc/commands/db/db_models.py,sha256=zpMcrDBanLx7YQJ_-PWrQCbh3B-C0qWn1Pt5SnDwsh0,1351
29
29
  cgc/commands/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  cgc/commands/debug/debug_cmd.py,sha256=kuAuh5YOqzGFjoiYZwfM9FJ1z5OeSpC0JAIUEzS83lM,412
31
31
  cgc/commands/jobs/__init__.py,sha256=E-438wgIzlnGmXs5jgmWAhJ1KNV6UXF2gz8SXu3UxA0,231
32
- cgc/commands/jobs/job_utils.py,sha256=_WffuTWYZamaXmrkRQIRmQzFsrvNbWzSwXlVLqLbmis,6194
33
- cgc/commands/jobs/jobs_cmd.py,sha256=Q-orK6B9Zk1zAf8sOM6QqF9Eeu092P-UEg4GRA-zX-s,6555
32
+ cgc/commands/jobs/job_utils.py,sha256=LlgMK11XQ-yjl_eOTFYwdJwOxyeiSCNkZQRZFpFW14A,6560
33
+ cgc/commands/jobs/jobs_cmd.py,sha256=4zHZtT2y_FoBrGNR5nfSJ0gi-MX1rUP68KwpvuZ8m0Q,6922
34
34
  cgc/commands/jobs/jobs_responses.py,sha256=QXFXA4zwQOo5Gvq5rEc7J_cxxsYqkdU19X9MCcZetUM,1771
35
35
  cgc/commands/resource/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  cgc/commands/resource/resource_cmd.py,sha256=y2R6ZF0jFhTppD9JUpwLIcEdFRXg82Xw_5yrNIfG4mI,4089
@@ -45,9 +45,10 @@ cgc/commands/user/secret_models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
45
45
  cgc/commands/user/secret_responses.py,sha256=sg7HP5a0LnUvlo9w8oOcYASwI2cPeRnecjTVMa5yk7Y,1600
46
46
  cgc/commands/user/secret_utils.py,sha256=70dn937rzecQ0eE-8zb9fLnBDWtjWjdQY1LJvHv3NzM,1623
47
47
  cgc/commands/volume/__init__.py,sha256=Ou3kyb72aaXkrVCfQCVdniA65R2xHsRFgebooG1gflA,384
48
- cgc/commands/volume/data_model.py,sha256=meprXdaXLo3mMTZta1ks1-BJ7G0rO16qi_ycH-sXJpY,1295
49
- cgc/commands/volume/volume_cmd.py,sha256=bKXEdlxcL1gmVYeRXQeVY463IMHO-So4PWPBRXL6O4Y,7673
50
- cgc/commands/volume/volume_responses.py,sha256=PqYe5HVbClkCMIuXWn_FDvPUNxsJW8M_hFIB0lf40bo,3213
48
+ cgc/commands/volume/data_model.py,sha256=lLHuKRMe-5mgHL3i48QJn8_S3tJHFMCwu8cJAxXe-PU,1267
49
+ cgc/commands/volume/volume_cmd.py,sha256=Eylo_V8Tex8OF5IQcAlpo6ggC4FXYfnQunGtat6WwSs,8279
50
+ cgc/commands/volume/volume_models.py,sha256=eKHYLcAUezoJ1X2ENE-GE2CgE8lynlfT3Hs2PI8zAnY,779
51
+ cgc/commands/volume/volume_responses.py,sha256=lO6NiUFJkNTTKLerFihe3CeQwdo56WyUN5bk01dtfMs,4027
51
52
  cgc/commands/volume/volume_utils.py,sha256=6IuDCNT-KAvUUF_EDg5cL9JewTGsbBsZlYd_zKHowCU,1973
52
53
  cgc/sdk/__init__.py,sha256=m8uAD2e_ADbHC4_kaOpLrUk_bHy7wC56rPjhcttclCs,177
53
54
  cgc/sdk/exceptions.py,sha256=99XIzDO6LYKjex715troH-MkGUN7hi2Bit4KHfSHDis,214
@@ -55,7 +56,7 @@ cgc/sdk/job.py,sha256=q9Vsarc3rKzurM4AOmbQDsQUVdyRqx0UzJVe_uO8xCU,5318
55
56
  cgc/sdk/postgresql.py,sha256=ziXaMMwjSF3k1OAID3F9npqWVxreQaoZ8wn7X8x1FZw,1637
56
57
  cgc/sdk/resource.py,sha256=w8SVRqrx0Mj1FS91Bt0oaMAfC4CDEcomgqzFCNgGaPc,13537
57
58
  cgc/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- cgc/telemetry/basic.py,sha256=XagCcyH4QSEPmfiQ1WCjqXslnJO6IaJCY0AMPySd5rc,3175
59
+ cgc/telemetry/basic.py,sha256=h0f2yAanaflkEnetLjQvs_IR43KL0JmhJ30yrl6tOas,3174
59
60
  cgc/tests/__init__.py,sha256=8aI3MVpkzaj0_UX02kZCtY5vmGO0rnq0mw2H04-OHf8,102743
60
61
  cgc/tests/responses_tests.py,sha256=9vLOaUICeUXoDObeFwuu0FBTmC-hnJNZfzvolT96dGM,9188
61
62
  cgc/tests/desired_responses/test_billing_invoice.txt,sha256=KR5m2gamn_bgfBdBmWDH2sPRJIPOw1u8kdH-gYE8jow,1579
@@ -68,7 +69,7 @@ cgc/tests/desired_responses/test_tabulate_response.txt,sha256=beNyCTS9fwrHn4ueEO
68
69
  cgc/tests/desired_responses/test_volume_list.txt,sha256=vYB1p50BBHD801q7LUdDc_aca4ezQ8CFLWw7I-b4Uao,309
69
70
  cgc/utils/__init__.py,sha256=JOvbqyGdFtswXE1TntOqM7XuIg5-t042WzAzvmX7Xtk,3661
70
71
  cgc/utils/click_group.py,sha256=Scfw8eMIyt2dE1ezUq2JuiI-E_LklqXQXJEr7L-EG6A,633
71
- cgc/utils/config_utils.py,sha256=-DkUBncq_nTUIbxnF9vTiibZHgJ6Beomzk7fg7zQJVA,3465
72
+ cgc/utils/config_utils.py,sha256=W3aS1WhUmWEdHNUU3Zl3u7gTRceQWtfSf1dum2CqpVc,6947
72
73
  cgc/utils/custom_exceptions.py,sha256=qvHdzaunZswZgN96iOHZIfLjehlJ79mcjqoMoW-tqEM,2628
73
74
  cgc/utils/get_headers_data.py,sha256=JdEg5vrAHcWfsSJ7poYk3sNIY10OxX7YGVcmua-37lY,413
74
75
  cgc/utils/message_utils.py,sha256=FAiUC-0zJiMhfPQAQC0ki1ZUs1vI_QqHwLmfoCDbLeU,1790
@@ -76,17 +77,17 @@ cgc/utils/prepare_headers.py,sha256=Hi3WNqtqydW56tNTLZmpfMTpu4aKCoDrLx4OcCGH9_U,
76
77
  cgc/utils/requests_helper.py,sha256=Z89dTOTbSSi1xmtNPmAdJpduR9-DC12WEQsHYeuM9a0,2046
77
78
  cgc/utils/response_utils.py,sha256=9vJqAt2UFJ1n-oesFPe6CB_ooGoStjl-twY_31Jt4_I,7374
78
79
  cgc/utils/update.py,sha256=AsQwhcBqsjgNPKn6AN6ojt0Ew5otvJXyshys6bjr7DQ,413
79
- cgc/utils/version_control.py,sha256=jYm_XYHzM61DR8aJlwDLAB6psOGLjOiXR6zTOEfv57A,3515
80
+ cgc/utils/version_control.py,sha256=MTXpwFC0zl0xKZsllFHZMOUm0VuihA0u3g4jVW70c7w,4163
80
81
  cgc/utils/consts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
- cgc/utils/consts/env_consts.py,sha256=3MaR8sMlQyGWP6sBWajhVbnzhDovD2ZQjKYeQ27e-1w,1225
82
- cgc/utils/consts/message_consts.py,sha256=8CIe3N_HL6Pj-gSArkPkpegsvm-QMWxqqnSgtzG08Qw,1218
82
+ cgc/utils/consts/env_consts.py,sha256=_yHjhXDBwYfLfUWfHOPlhGdzC0j9VFlkjKuSrvblBsU,1154
83
+ cgc/utils/consts/message_consts.py,sha256=xSW7XaqPIAO_uJr-eK2fcjP2o7t0OJ0OP75otbUBnAg,1232
83
84
  cgc/utils/cryptography/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
85
  cgc/utils/cryptography/aes_crypto.py,sha256=S0rKg38oy7rM5lTrP6DDpjLA-XRxuZggAXyxMFHtyzY,3333
85
86
  cgc/utils/cryptography/encryption_module.py,sha256=rbblBBorHYPGl-iKblyZX3_NuPEvUTpnH1l_RgNGCbA,1958
86
87
  cgc/utils/cryptography/rsa_crypto.py,sha256=h3jU5qPpj9uVjP1rTqZJTdYB5yjhD9HZpr_nD439h9Q,4180
87
- cgcsdk-1.0.14.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
- cgcsdk-1.0.14.dist-info/METADATA,sha256=9bvU67kTs5GpfGtnmy7FWIifW4aJmOwSiMEwYO4iKH0,3091
89
- cgcsdk-1.0.14.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
90
- cgcsdk-1.0.14.dist-info/entry_points.txt,sha256=bdfIHeJ6Y-BBr5yupCVoK7SUrJj1yNdew8OtIOg_3No,36
91
- cgcsdk-1.0.14.dist-info/top_level.txt,sha256=nqW9tqcIcCXFigQT69AuOk7XHKc4pCuv4HGJQGXb6iA,12
92
- cgcsdk-1.0.14.dist-info/RECORD,,
88
+ cgcsdk-1.0.16.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
+ cgcsdk-1.0.16.dist-info/METADATA,sha256=omC-JocyzpOolm58Rv7yP2lYEqvBDwAfl8EPP_OsGSw,3091
90
+ cgcsdk-1.0.16.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
91
+ cgcsdk-1.0.16.dist-info/entry_points.txt,sha256=bdfIHeJ6Y-BBr5yupCVoK7SUrJj1yNdew8OtIOg_3No,36
92
+ cgcsdk-1.0.16.dist-info/top_level.txt,sha256=nqW9tqcIcCXFigQT69AuOk7XHKc4pCuv4HGJQGXb6iA,12
93
+ cgcsdk-1.0.16.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5