proximl 0.5.13.post1__tar.gz → 0.5.15__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 (147) hide show
  1. {proximl-0.5.13.post1/proximl.egg-info → proximl-0.5.15}/PKG-INFO +1 -1
  2. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/__init__.py +1 -1
  3. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/job/create.py +32 -2
  4. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/services.py +19 -1
  5. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/jobs.py +9 -0
  6. proximl-0.5.15/proximl/projects/members.py +98 -0
  7. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/projects.py +2 -0
  8. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/services.py +17 -0
  9. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/proximl.py +36 -19
  10. {proximl-0.5.13.post1 → proximl-0.5.15/proximl.egg-info}/PKG-INFO +1 -1
  11. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl.egg-info/SOURCES.txt +6 -0
  12. {proximl-0.5.13.post1 → proximl-0.5.15}/pyproject.toml +1 -0
  13. proximl-0.5.15/tests/integration/cloudbender/conftest.py +28 -0
  14. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/cloudbender/test_providers_integration.py +3 -8
  15. proximl-0.5.15/tests/integration/cloudbender/test_regions_integration.py +42 -0
  16. proximl-0.5.15/tests/integration/cloudbender/test_services_integration.py +87 -0
  17. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/conftest.py +1 -1
  18. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/conftest.py +2 -1
  19. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_credentials_integration.py +1 -0
  20. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_data_connectors_integration.py +1 -0
  21. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_datastores_integration.py +1 -0
  22. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_integration.py +1 -0
  23. proximl-0.5.15/tests/integration/projects/test_projects_members_integration.py +50 -0
  24. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_secrets_integration.py +1 -0
  25. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/test_projects_services_integration.py +1 -0
  26. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_checkpoints_integration.py +1 -0
  27. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_datasets_integration.py +1 -0
  28. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_jobs_integration.py +13 -8
  29. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_models_integration.py +1 -0
  30. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_volumes_integration.py +1 -0
  31. proximl-0.5.15/tests/unit/projects/test_project_members_unit.py +107 -0
  32. {proximl-0.5.13.post1 → proximl-0.5.15}/LICENSE +0 -0
  33. {proximl-0.5.13.post1 → proximl-0.5.15}/README.md +0 -0
  34. {proximl-0.5.13.post1 → proximl-0.5.15}/examples/__init__.py +0 -0
  35. {proximl-0.5.13.post1 → proximl-0.5.15}/examples/create_dataset_and_training_job.py +0 -0
  36. {proximl-0.5.13.post1 → proximl-0.5.15}/examples/local_storage.py +0 -0
  37. {proximl-0.5.13.post1 → proximl-0.5.15}/examples/training_inference_pipeline.py +0 -0
  38. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/__main__.py +0 -0
  39. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/auth.py +0 -0
  40. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/checkpoints.py +0 -0
  41. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/__init__.py +0 -0
  42. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/checkpoint.py +0 -0
  43. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/__init__.py +0 -0
  44. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/data_connector.py +0 -0
  45. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/datastore.py +0 -0
  46. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/device.py +0 -0
  47. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/node.py +0 -0
  48. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/provider.py +0 -0
  49. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/region.py +0 -0
  50. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/cloudbender/service.py +0 -0
  51. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/connection.py +0 -0
  52. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/dataset.py +0 -0
  53. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/environment.py +0 -0
  54. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/gpu.py +0 -0
  55. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/job/__init__.py +0 -0
  56. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/model.py +0 -0
  57. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/__init__.py +0 -0
  58. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/credential.py +0 -0
  59. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/data_connector.py +0 -0
  60. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/datastore.py +0 -0
  61. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/secret.py +0 -0
  62. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/project/service.py +0 -0
  63. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cli/volume.py +0 -0
  64. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/__init__.py +0 -0
  65. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/cloudbender.py +0 -0
  66. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/data_connectors.py +0 -0
  67. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/datastores.py +0 -0
  68. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/device_configs.py +0 -0
  69. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/devices.py +0 -0
  70. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/nodes.py +0 -0
  71. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/providers.py +0 -0
  72. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/cloudbender/regions.py +0 -0
  73. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/connections.py +0 -0
  74. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/datasets.py +0 -0
  75. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/environments.py +0 -0
  76. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/exceptions.py +0 -0
  77. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/gpu_types.py +0 -0
  78. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/models.py +0 -0
  79. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/__init__.py +0 -0
  80. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/credentials.py +0 -0
  81. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/data_connectors.py +0 -0
  82. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/datastores.py +0 -0
  83. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/projects/secrets.py +0 -0
  84. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl/volumes.py +0 -0
  85. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl.egg-info/dependency_links.txt +0 -0
  86. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl.egg-info/entry_points.txt +0 -0
  87. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl.egg-info/requires.txt +0 -0
  88. {proximl-0.5.13.post1 → proximl-0.5.15}/proximl.egg-info/top_level.txt +0 -0
  89. {proximl-0.5.13.post1 → proximl-0.5.15}/setup.cfg +0 -0
  90. {proximl-0.5.13.post1 → proximl-0.5.15}/setup.py +0 -0
  91. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/__init__.py +0 -0
  92. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/cloudbender/__init__.py +0 -0
  93. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/projects/__init__.py +0 -0
  94. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_environments_integration.py +0 -0
  95. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/integration/test_gpu_types_integration.py +0 -0
  96. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/__init__.py +0 -0
  97. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/__init__.py +0 -0
  98. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/__init__.py +0 -0
  99. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_datastore_unit.py +0 -0
  100. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_device_unit.py +0 -0
  101. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_node_unit.py +0 -0
  102. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_provider_unit.py +0 -0
  103. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_region_unit.py +0 -0
  104. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/cloudbender/test_cli_service_unit.py +0 -0
  105. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/conftest.py +0 -0
  106. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/__init__.py +0 -0
  107. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_credential_unit.py +0 -0
  108. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_data_connector_unit.py +0 -0
  109. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_datastore_unit.py +0 -0
  110. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_secret_unit.py +0 -0
  111. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_service_unit.py +0 -0
  112. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/projects/test_cli_project_unit.py +0 -0
  113. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_checkpoint_unit.py +0 -0
  114. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_datasets_unit.py +0 -0
  115. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_environment_unit.py +0 -0
  116. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_gpu_unit.py +0 -0
  117. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_job_unit.py +0 -0
  118. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_model_unit.py +0 -0
  119. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cli/test_cli_volume_unit.py +0 -0
  120. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/__init__.py +0 -0
  121. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_data_connectors_unit.py +0 -0
  122. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_datastores_unit.py +0 -0
  123. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_device_configs_unit.py +0 -0
  124. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_devices_unit.py +0 -0
  125. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_nodes_unit.py +0 -0
  126. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_providers_unit.py +0 -0
  127. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_regions_unit.py +0 -0
  128. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/cloudbender/test_services_unit.py +0 -0
  129. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/conftest.py +0 -0
  130. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/__init__.py +0 -0
  131. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_project_credentials_unit.py +0 -0
  132. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_project_data_connectors_unit.py +0 -0
  133. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_project_datastores_unit.py +0 -0
  134. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_project_secrets_unit.py +0 -0
  135. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_project_services_unit.py +0 -0
  136. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/projects/test_projects_unit.py +0 -0
  137. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_auth_unit.py +0 -0
  138. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_checkpoints_unit.py +0 -0
  139. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_connections_unit.py +0 -0
  140. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_datasets_unit.py +0 -0
  141. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_environments_unit.py +0 -0
  142. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_exceptions.py +0 -0
  143. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_gpu_types_unit.py +0 -0
  144. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_jobs_unit.py +0 -0
  145. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_models_unit.py +0 -0
  146. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_proximl_unit.py +0 -0
  147. {proximl-0.5.13.post1 → proximl-0.5.15}/tests/unit/test_volumes_unit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proximl
3
- Version: 0.5.13.post1
3
+ Version: 0.5.15
4
4
  Summary: proxiML client SDK and command line utilities
5
5
  Home-page: https://github.com/proxiML/python-sdk
6
6
  Author: proxiML
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.13.post1"
16
+ __version__ = "0.5.15"
17
17
  __all__ = "ProxiML"
@@ -180,6 +180,12 @@ def create(config):
180
180
  help="Third Party Credentials to add to the job environment",
181
181
  multiple=True,
182
182
  )
183
+ @click.option(
184
+ "--secret",
185
+ type=click.STRING,
186
+ help="Project secrets to add to the job environment",
187
+ multiple=True,
188
+ )
183
189
  @click.option(
184
190
  "--git-uri",
185
191
  type=click.STRING,
@@ -222,6 +228,7 @@ def notebook(
222
228
  custom_image,
223
229
  env,
224
230
  credential,
231
+ secret,
225
232
  apt_packages,
226
233
  pip_packages,
227
234
  conda_packages,
@@ -254,7 +261,7 @@ def notebook(
254
261
  data=dict(datasets=datasets),
255
262
  model=dict(checkpoints=checkpoints),
256
263
  environment=dict(
257
- credentials=[k for k in credential],
264
+ credentials=[k for k in credential], secrets=[s for s in secret]
258
265
  ),
259
266
  )
260
267
 
@@ -508,6 +515,12 @@ def notebook(
508
515
  help="Third Party Credentials to add to the job environment",
509
516
  multiple=True,
510
517
  )
518
+ @click.option(
519
+ "--secret",
520
+ type=click.STRING,
521
+ help="Project secrets to add to the job environment",
522
+ multiple=True,
523
+ )
511
524
  @click.option(
512
525
  "--apt-packages",
513
526
  type=click.STRING,
@@ -564,6 +577,7 @@ def training(
564
577
  custom_image,
565
578
  env,
566
579
  credential,
580
+ secret,
567
581
  apt_packages,
568
582
  pip_packages,
569
583
  conda_packages,
@@ -596,7 +610,7 @@ def training(
596
610
  data=dict(datasets=datasets),
597
611
  model=dict(checkpoints=checkpoints),
598
612
  environment=dict(
599
- credentials=[k for k in credential],
613
+ credentials=[k for k in credential], secrets=[s for s in secret]
600
614
  ),
601
615
  )
602
616
 
@@ -852,6 +866,12 @@ def training(
852
866
  help="Third Party Credentials to add to the job environment",
853
867
  multiple=True,
854
868
  )
869
+ @click.option(
870
+ "--secret",
871
+ type=click.STRING,
872
+ help="Project secrets to add to the job environment",
873
+ multiple=True,
874
+ )
855
875
  @click.option(
856
876
  "--apt-packages",
857
877
  type=click.STRING,
@@ -908,6 +928,7 @@ def inference(
908
928
  custom_image,
909
929
  env,
910
930
  credential,
931
+ secret,
911
932
  apt_packages,
912
933
  pip_packages,
913
934
  conda_packages,
@@ -934,6 +955,7 @@ def inference(
934
955
  model=dict(checkpoints=checkpoints),
935
956
  environment=dict(
936
957
  credentials=[k for k in credential],
958
+ secrets=[s for s in secret],
937
959
  ),
938
960
  )
939
961
 
@@ -1171,6 +1193,12 @@ def from_json(config, attach, connect, file):
1171
1193
  help="Third Party Credentials to add to the job environment",
1172
1194
  multiple=True,
1173
1195
  )
1196
+ @click.option(
1197
+ "--secret",
1198
+ type=click.STRING,
1199
+ help="Project secrets to add to the job environment",
1200
+ multiple=True,
1201
+ )
1174
1202
  @click.option(
1175
1203
  "--apt-packages",
1176
1204
  type=click.STRING,
@@ -1231,6 +1259,7 @@ def endpoint(
1231
1259
  custom_image,
1232
1260
  env,
1233
1261
  credential,
1262
+ secret,
1234
1263
  apt_packages,
1235
1264
  pip_packages,
1236
1265
  conda_packages,
@@ -1258,6 +1287,7 @@ def endpoint(
1258
1287
  model=dict(checkpoints=checkpoints),
1259
1288
  environment=dict(
1260
1289
  credentials=[k for k in credential],
1290
+ secrets=[s for s in secret],
1261
1291
  ),
1262
1292
  )
1263
1293
 
@@ -67,7 +67,7 @@ class Services(object):
67
67
 
68
68
 
69
69
  class Service:
70
- def __init__(self, proximl, **kwargs):
70
+ def __init__(self, proximl, **kwargs):
71
71
  self.proximl = proximl
72
72
  self._service = kwargs
73
73
  self._id = self._service.get("service_id")
@@ -177,3 +177,21 @@ class Service:
177
177
  logging.debug(f"self: {self}, retry count {count}")
178
178
 
179
179
  raise ProxiMLException(f"Timeout waiting for {status}")
180
+
181
+ async def generate_certificate(self, **kwargs):
182
+ resp = await self.proximl._query(
183
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate",
184
+ "POST",
185
+ kwargs
186
+ )
187
+ self.__init__(self.proximl, **resp)
188
+ return self
189
+
190
+ async def sign_client_certificate(self, csr, **kwargs):
191
+ certificate = await self.proximl._query(
192
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate/sign",
193
+ "POST",
194
+ kwargs,
195
+ dict(csr=csr)
196
+ )
197
+ return certificate
@@ -530,6 +530,15 @@ class Job:
530
530
  "waiting for data/model download",
531
531
  ]
532
532
  )
533
+ or (
534
+ status
535
+ == "running" ## this status could be too short for polling could miss it
536
+ and self.status
537
+ in [
538
+ "uploading",
539
+ "finished"
540
+ ]
541
+ )
533
542
  ):
534
543
  return self
535
544
  elif self.status == "failed":
@@ -0,0 +1,98 @@
1
+ import json
2
+ import logging
3
+ from typing import Literal
4
+
5
+ class ProjectMembers(object):
6
+ def __init__(self, proximl, project_id):
7
+ self.proximl = proximl
8
+ self.project_id = project_id
9
+
10
+ async def list(self, **kwargs):
11
+ resp = await self.proximl._query(
12
+ f"/project/{self.project_id}/access", "GET", kwargs
13
+ )
14
+ members = [ProjectMember(self.proximl, **member) for member in resp]
15
+ return members
16
+
17
+ async def add(self, email: str, job: Literal["all", "read"], dataset: Literal["all", "read"], model: Literal["all", "read"], checkpoint: Literal["all", "read"], volume: Literal["all", "read"], **kwargs):
18
+ data = dict(
19
+ email=email,
20
+ job=job,
21
+ dataset=dataset,
22
+ model=model,
23
+ checkpoint=checkpoint,
24
+ volume=volume,
25
+ )
26
+ payload = {k: v for k, v in data.items() if v is not None}
27
+ resp = await self.proximl._query(
28
+ f"/project/{self.project_id}/access", "POST",kwargs, payload)
29
+ member = ProjectMember(self.proximl, **resp)
30
+ logging.info(f"Added Project Member {email} to project {self.project_id}")
31
+ return member
32
+
33
+
34
+ async def remove(self, email, **kwargs):
35
+ await self.proximl._query(
36
+ f"/project/{self.project_id}/access", "DELETE", dict(**kwargs, email=email)
37
+ )
38
+
39
+
40
+ class ProjectMember:
41
+ def __init__(self, proximl, **kwargs):
42
+ self.proximl = proximl
43
+ self._entity = kwargs
44
+ self._id = self._entity.get("email")
45
+ self._project_uuid = self._entity.get("project_uuid")
46
+ self._owner = self._entity.get("owner")
47
+ self._job = self._entity.get("job")
48
+ self._dataset = self._entity.get("dataset")
49
+ self._model = self._entity.get("model")
50
+ self._checkpoint = self._entity.get("checkpoint")
51
+ self._volume = self._entity.get("volume")
52
+
53
+ @property
54
+ def id(self) -> str:
55
+ return self._id
56
+
57
+ @property
58
+ def project_uuid(self) -> str:
59
+ return self._project_uuid
60
+
61
+ @property
62
+ def email(self) -> str:
63
+ return self._id
64
+
65
+ @property
66
+ def owner(self) -> bool:
67
+ return self._owner
68
+
69
+ @property
70
+ def job(self) -> str:
71
+ return self._job
72
+
73
+ @property
74
+ def dataset(self) -> str:
75
+ return self._dataset
76
+
77
+ @property
78
+ def model(self) -> str:
79
+ return self._model
80
+
81
+ @property
82
+ def checkpoint(self) -> str:
83
+ return self._checkpoint
84
+
85
+ @property
86
+ def volume(self) -> str:
87
+ return self._volume
88
+
89
+ def __str__(self):
90
+ return json.dumps({k: v for k, v in self._entity.items()})
91
+
92
+ def __repr__(self):
93
+ return f"ProjectMember( proximl , **{self._entity.__repr__()})"
94
+
95
+ def __bool__(self):
96
+ return bool(self._id)
97
+
98
+
@@ -5,6 +5,7 @@ from .data_connectors import ProjectDataConnectors
5
5
  from .services import ProjectServices
6
6
  from .credentials import ProjectCredentials
7
7
  from .secrets import ProjectSecrets
8
+ from .members import ProjectMembers
8
9
 
9
10
 
10
11
  class Projects(object):
@@ -56,6 +57,7 @@ class Project:
56
57
  self.services = ProjectServices(self.proximl, self._id)
57
58
  self.credentials = ProjectCredentials(self.proximl, self._id)
58
59
  self.secrets = ProjectSecrets(self.proximl, self._id)
60
+ self.members = ProjectMembers(self.proximl, self._id)
59
61
 
60
62
  @property
61
63
  def id(self) -> str:
@@ -77,3 +77,20 @@ class ProjectService:
77
77
  await self.proximl._query(
78
78
  f"/project/{self._project_uuid}/services/{self._id}/disable", "PATCH"
79
79
  )
80
+
81
+ async def get_service_ca_certificate(self, **kwargs):
82
+ certificate = await self.proximl._query(
83
+ f"/project/{self._project_uuid}/services/{self._id}/certificate/ca",
84
+ "GET",
85
+ kwargs,
86
+ )
87
+ return certificate
88
+
89
+ async def sign_client_certificate(self, csr, **kwargs):
90
+ certificate = await self.proximl._query(
91
+ f"/project/{self._project_uuid}/services/{self._id}/certificate/sign",
92
+ "POST",
93
+ kwargs,
94
+ dict(csr=csr)
95
+ )
96
+ return certificate
@@ -4,6 +4,7 @@ import asyncio
4
4
  import aiohttp
5
5
  import logging
6
6
  import traceback
7
+ import random
7
8
  from importlib.metadata import version
8
9
 
9
10
  from proximl.auth import Auth
@@ -91,7 +92,7 @@ class ProxiML(object):
91
92
  def project(self) -> str:
92
93
  return self.active_project
93
94
 
94
- async def _query(self, path, method, params=None, data=None, headers=None):
95
+ async def _query(self, path, method, params=None, data=None, headers=None,max_retries=3, backoff_factor=0.5):
95
96
  try:
96
97
  tokens = self.auth.get_tokens()
97
98
  except ProxiMLException as e:
@@ -142,24 +143,40 @@ class ProxiML(object):
142
143
  logging.debug(
143
144
  f"Request - Url: {url}, Method: {method}, Params: {params}, Body: {data}, Headers: {headers}"
144
145
  )
145
- async with aiohttp.ClientSession() as session:
146
- async with session.request(
147
- method,
148
- url,
149
- data=json.dumps(data),
150
- headers=headers,
151
- params=params,
152
- ) as resp:
153
- if (resp.status // 100) in [4, 5]:
154
- what = await resp.read()
155
- content_type = resp.headers.get("content-type", "")
156
- resp.close()
157
- if content_type == "application/json":
158
- raise ApiError(resp.status, json.loads(what.decode("utf8")))
159
- else:
160
- raise ApiError(resp.status, {"message": what.decode("utf8")})
161
- results = await resp.json()
162
- return results
146
+ for attempt in range(max_retries):
147
+ try:
148
+ async with aiohttp.ClientSession() as session:
149
+ async with session.request(
150
+ method,
151
+ url,
152
+ data=json.dumps(data),
153
+ headers=headers,
154
+ params=params,
155
+ ) as resp:
156
+ if (resp.status // 100) in [4, 5]:
157
+ if resp.status == 502 and attempt < max_retries - 1:
158
+ wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
159
+ await asyncio.sleep(wait_time)
160
+ continue
161
+ else:
162
+ what = await resp.read()
163
+ content_type = resp.headers.get("content-type", "")
164
+ resp.close()
165
+ if content_type == "application/json":
166
+ raise ApiError(resp.status, json.loads(what.decode("utf8")))
167
+ else:
168
+ raise ApiError(resp.status, {"message": what.decode("utf8")})
169
+ results = await resp.json()
170
+ return results
171
+ except aiohttp.ClientResponseError as e:
172
+ if e.status == 502 and attempt < max_retries - 1:
173
+ wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
174
+ await asyncio.sleep(wait_time)
175
+ continue
176
+ else:
177
+ raise ApiError(e.status, f"Error {e.message}")
178
+
179
+ raise ProxiMLException("Unexpected API failure")
163
180
 
164
181
  async def _ws_subscribe(self, entity, project_uuid, id, msg_handler):
165
182
  headers = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proximl
3
- Version: 0.5.13.post1
3
+ Version: 0.5.15
4
4
  Summary: proxiML client SDK and command line utilities
5
5
  Home-page: https://github.com/proxiML/python-sdk
6
6
  Author: proxiML
@@ -63,6 +63,7 @@ proximl/projects/__init__.py
63
63
  proximl/projects/credentials.py
64
64
  proximl/projects/data_connectors.py
65
65
  proximl/projects/datastores.py
66
+ proximl/projects/members.py
66
67
  proximl/projects/projects.py
67
68
  proximl/projects/secrets.py
68
69
  proximl/projects/services.py
@@ -76,13 +77,17 @@ tests/integration/test_jobs_integration.py
76
77
  tests/integration/test_models_integration.py
77
78
  tests/integration/test_volumes_integration.py
78
79
  tests/integration/cloudbender/__init__.py
80
+ tests/integration/cloudbender/conftest.py
79
81
  tests/integration/cloudbender/test_providers_integration.py
82
+ tests/integration/cloudbender/test_regions_integration.py
83
+ tests/integration/cloudbender/test_services_integration.py
80
84
  tests/integration/projects/__init__.py
81
85
  tests/integration/projects/conftest.py
82
86
  tests/integration/projects/test_projects_credentials_integration.py
83
87
  tests/integration/projects/test_projects_data_connectors_integration.py
84
88
  tests/integration/projects/test_projects_datastores_integration.py
85
89
  tests/integration/projects/test_projects_integration.py
90
+ tests/integration/projects/test_projects_members_integration.py
86
91
  tests/integration/projects/test_projects_secrets_integration.py
87
92
  tests/integration/projects/test_projects_services_integration.py
88
93
  tests/unit/__init__.py
@@ -134,6 +139,7 @@ tests/unit/projects/__init__.py
134
139
  tests/unit/projects/test_project_credentials_unit.py
135
140
  tests/unit/projects/test_project_data_connectors_unit.py
136
141
  tests/unit/projects/test_project_datastores_unit.py
142
+ tests/unit/projects/test_project_members_unit.py
137
143
  tests/unit/projects/test_project_secrets_unit.py
138
144
  tests/unit/projects/test_project_services_unit.py
139
145
  tests/unit/projects/test_projects_unit.py
@@ -1,4 +1,5 @@
1
1
  [tool.pytest.ini_options]
2
+ addopts = "--cov-report term-missing --cov=proximl -n 4 --dist=loadgroup"
2
3
  python_files = "test_*"
3
4
  python_classes = "*Tests"
4
5
  python_functions = "test_*"
@@ -0,0 +1,28 @@
1
+
2
+ from pytest import fixture, mark
3
+
4
+
5
+ pytestmark = [mark.integration,mark.cloudbender]
6
+
7
+ @fixture(scope="session")
8
+ @mark.create
9
+ @mark.asyncio
10
+ @mark.xdist_group("cloudbender_resources")
11
+ async def provider( proximl):
12
+ provider = await proximl.cloudbender.providers.enable(type="test")
13
+ await provider.wait_for("ready")
14
+ yield provider
15
+ await provider.remove()
16
+
17
+ @fixture(scope="session")
18
+ @mark.create
19
+ @mark.asyncio
20
+ @mark.xdist_group("cloudbender_resources")
21
+ async def region(proximl, provider):
22
+ region = await proximl.cloudbender.regions.create(provider_uuid=provider.id,name="test-region",
23
+ public=False,
24
+ storage=dict(mode="local"),)
25
+ await region.wait_for("healthy")
26
+ yield region
27
+ await region.remove()
28
+ await region.wait_for("archived")
@@ -8,14 +8,9 @@ pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.providers]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
- class GetProvidersTests:
12
- @fixture(scope="class")
13
- async def provider(self, proximl):
14
- provider = await proximl.cloudbender.providers.enable(type="test")
15
- yield provider
16
- await provider.remove()
17
-
18
- async def test_get_providers(self, proximl):
11
+ @mark.xdist_group("cloudbender_resources")
12
+ class GetProviderTests:
13
+ async def test_get_providers(self, proximl, provider):
19
14
  providers = await proximl.cloudbender.providers.list()
20
15
  assert len(providers) > 0
21
16
 
@@ -0,0 +1,42 @@
1
+ import re
2
+ import sys
3
+ import asyncio
4
+ from pytest import mark, fixture
5
+
6
+ pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.regions]
7
+
8
+
9
+ @mark.create
10
+ @mark.asyncio
11
+ @mark.xdist_group("cloudbender_resources")
12
+ class GetRegionTests:
13
+ async def test_get_regions(self, proximl, provider, region):
14
+ regions = await proximl.cloudbender.regions.list(provider_uuid=provider.id)
15
+ assert len(regions) > 0
16
+
17
+ async def test_get_region(self, proximl, provider, region):
18
+ response = await proximl.cloudbender.regions.get(provider.id, region.id)
19
+ assert response.id == region.id
20
+
21
+ async def test_region_properties(self, provider, region):
22
+ assert isinstance(region.id, str)
23
+ assert isinstance(region.provider_uuid, str)
24
+ assert isinstance(region.type, str)
25
+ assert region.type == "test"
26
+ assert region.provider_uuid == provider.id
27
+
28
+ async def test_region_str(self, region):
29
+ string = str(region)
30
+ regex = r"^{.*\"region_uuid\": \"" + region.id + r"\".*}$"
31
+ assert isinstance(string, str)
32
+ assert re.match(regex, string)
33
+
34
+ async def test_region_repr(self, region):
35
+ string = repr(region)
36
+ regex = (
37
+ r"^Region\( proximl , \*\*{.*'region_uuid': '"
38
+ + region.id
39
+ + r"'.*}\)$"
40
+ )
41
+ assert isinstance(string, str)
42
+ assert re.match(regex, string)
@@ -0,0 +1,87 @@
1
+ import re
2
+ import sys
3
+ import asyncio
4
+ from pytest import mark, fixture
5
+ from cryptography import x509
6
+ from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
7
+ from cryptography.hazmat.primitives.asymmetric import rsa
8
+ from cryptography.hazmat.primitives import serialization, hashes
9
+
10
+ pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.regions]
11
+
12
+ def get_csr(service_id):
13
+ private_key = rsa.generate_private_key(
14
+ public_exponent=65537,
15
+ key_size=4096
16
+ )
17
+ csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
18
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
19
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "proxiML"),
20
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, service_id),
21
+ x509.NameAttribute(NameOID.COMMON_NAME, "test-client"), # Client identity
22
+ ])).add_extension(
23
+ x509.ExtendedKeyUsage([
24
+ ExtendedKeyUsageOID.CLIENT_AUTH # Client authentication usage
25
+ ]),
26
+ critical=True
27
+ ).sign(private_key, hashes.SHA256())
28
+ return csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
29
+
30
+ @mark.create
31
+ @mark.asyncio
32
+ @mark.xdist_group("cloudbender_resources")
33
+ class GetServiceTests:
34
+ @fixture(scope="class")
35
+ async def service(self, proximl, region):
36
+ service = await proximl.cloudbender.services.create(
37
+ provider_uuid=region.provider_uuid,
38
+ region_uuid=region.id,
39
+ name="CLI Automated Service",
40
+ type="tcp",
41
+ port="8989",
42
+ public=False,
43
+ )
44
+ await service.wait_for("active")
45
+ yield service
46
+ await service.remove()
47
+ await service.wait_for("archived")
48
+
49
+ async def test_get_services(self, proximl, region,service):
50
+ services = await proximl.cloudbender.services.list(provider_uuid=region.provider_uuid, region_uuid=region.id)
51
+ assert len(services) > 0
52
+
53
+ async def test_get_service(self, proximl, provider, region, service):
54
+ response = await proximl.cloudbender.services.get(provider.id, region.id, service.id)
55
+ assert response.id == service.id
56
+
57
+ async def test_service_properties(self, region, service):
58
+ assert isinstance(service.id, str)
59
+ assert isinstance(service.provider_uuid, str)
60
+ assert isinstance(service.region_uuid, str)
61
+ assert isinstance(service.public, bool)
62
+ assert service.port == "8989"
63
+ assert service.provider_uuid == region.provider_uuid
64
+ assert service.region_uuid == region.id
65
+
66
+ async def test_service_str(self, service):
67
+ string = str(service)
68
+ regex = r"^{.*\"service_id\": \"" + service.id + r"\".*}$"
69
+ assert isinstance(string, str)
70
+ assert re.match(regex, string)
71
+
72
+ async def test_service_repr(self, service):
73
+ string = repr(service)
74
+ regex = (
75
+ r"^Service\( proximl , \*\*{.*'service_id': '"
76
+ + service.id
77
+ + r"'.*}\)$"
78
+ )
79
+ assert isinstance(string, str)
80
+ assert re.match(regex, string)
81
+
82
+ async def test_service_certificate(self, service):
83
+ service = await service.generate_certificate()
84
+ assert isinstance(service._service.get("auth_cert"), str)
85
+ csr = get_csr(service.id)
86
+ certificate = await service.sign_client_certificate(csr)
87
+ assert isinstance(certificate, str)
@@ -40,7 +40,7 @@ def env(request):
40
40
  yield ENVS[env]
41
41
 
42
42
 
43
- @fixture(scope="module")
43
+ @fixture(scope="session")
44
44
  def proximl(env):
45
45
  proximl = ProxiML(**env)
46
46
  yield proximl
@@ -1,7 +1,8 @@
1
- from pytest import fixture
1
+ from pytest import fixture, mark
2
2
 
3
3
 
4
4
  @fixture(scope="module")
5
+ @mark.xdist_group("project_resources")
5
6
  async def project(proximl):
6
7
  project = await proximl.projects.create(
7
8
  name="New Project", copy_credentials=False, copy_secrets=False
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectCredentialsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_credential(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectDataConnectorsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_data_connector(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectDatastoresTests:
12
13
  @fixture(scope="class")
13
14
  async def project_datastore(self, project):