trainml 0.5.14__py3-none-any.whl → 0.5.15__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.
Files changed (32) hide show
  1. tests/integration/cloudbender/conftest.py +28 -0
  2. tests/integration/cloudbender/test_providers_integration.py +3 -8
  3. tests/integration/cloudbender/test_regions_integration.py +42 -0
  4. tests/integration/cloudbender/test_services_integration.py +87 -0
  5. tests/integration/conftest.py +1 -1
  6. tests/integration/projects/conftest.py +2 -1
  7. tests/integration/projects/test_projects_credentials_integration.py +1 -0
  8. tests/integration/projects/test_projects_data_connectors_integration.py +1 -0
  9. tests/integration/projects/test_projects_datastores_integration.py +1 -0
  10. tests/integration/projects/test_projects_integration.py +1 -0
  11. tests/integration/projects/test_projects_members_integration.py +1 -0
  12. tests/integration/projects/test_projects_secrets_integration.py +1 -0
  13. tests/integration/projects/test_projects_services_integration.py +1 -0
  14. tests/integration/test_checkpoints_integration.py +1 -0
  15. tests/integration/test_datasets_integration.py +1 -0
  16. tests/integration/test_jobs_integration.py +5 -0
  17. tests/integration/test_models_integration.py +1 -0
  18. tests/integration/test_volumes_integration.py +1 -0
  19. trainml/__init__.py +1 -1
  20. trainml/cloudbender/services.py +19 -1
  21. trainml/projects/services.py +17 -0
  22. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/METADATA +1 -1
  23. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/RECORD +27 -29
  24. tests/integration/projects/test_projects_keys_integration.py +0 -43
  25. tests/unit/cli/projects/test_cli_project_key_unit.py +0 -26
  26. tests/unit/projects/test_project_keys_unit.py +0 -96
  27. trainml/cli/project/key.py +0 -124
  28. trainml/projects/keys.py +0 -71
  29. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/LICENSE +0 -0
  30. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/WHEEL +0 -0
  31. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/entry_points.txt +0 -0
  32. {trainml-0.5.14.dist-info → trainml-0.5.15.dist-info}/top_level.txt +0 -0
@@ -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( trainml):
12
+ provider = await trainml.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(trainml, provider):
22
+ region = await trainml.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, trainml):
14
- provider = await trainml.cloudbender.providers.enable(type="test")
15
- yield provider
16
- await provider.remove()
17
-
18
- async def test_get_providers(self, trainml):
11
+ @mark.xdist_group("cloudbender_resources")
12
+ class GetProviderTests:
13
+ async def test_get_providers(self, trainml, provider):
19
14
  providers = await trainml.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, trainml, provider, region):
14
+ regions = await trainml.cloudbender.regions.list(provider_uuid=provider.id)
15
+ assert len(regions) > 0
16
+
17
+ async def test_get_region(self, trainml, provider, region):
18
+ response = await trainml.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\( trainml , \*\*{.*'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, trainml, region):
36
+ service = await trainml.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, trainml, region,service):
50
+ services = await trainml.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, trainml, provider, region, service):
54
+ response = await trainml.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\( trainml , \*\*{.*'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 trainml(env):
45
45
  trainml = TrainML(**env)
46
46
  yield trainml
@@ -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(trainml):
6
7
  project = await trainml.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):
@@ -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 GetProjectsTests:
12
13
  async def test_get_projects(self, trainml):
13
14
  projects = await trainml.projects.list()
@@ -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 ProjectMembersTests:
12
13
  @fixture(scope="class")
13
14
  async def project_member(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 ProjectSecretsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_secret(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 ProjectServicesTests:
12
13
  @fixture(scope="class")
13
14
  async def project_service(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.checkpoints]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("checkpoints")
11
12
  class GetCheckpointTests:
12
13
  @fixture(scope="class")
13
14
  async def checkpoint(self, trainml):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.datasets]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("datasets")
11
12
  class GetDatasetTests:
12
13
  @fixture(scope="class")
13
14
  async def dataset(self, trainml):
@@ -20,6 +20,7 @@ def extract_domain_suffix(hostname):
20
20
 
21
21
 
22
22
  @fixture(scope="class")
23
+ @mark.xdist_group("job_lifecycle")
23
24
  async def job(trainml):
24
25
  job = await trainml.jobs.create(
25
26
  name="CLI Automated Tests - Job Lifecycle",
@@ -38,6 +39,7 @@ async def job(trainml):
38
39
 
39
40
  @mark.create
40
41
  @mark.asyncio
42
+ @mark.xdist_group("job_lifecycle")
41
43
  class JobLifeCycleTests:
42
44
  async def test_wait_for_running(self, job):
43
45
  assert job.status != "running"
@@ -413,6 +415,7 @@ class JobAPIWorkerValidationTests:
413
415
 
414
416
  @mark.create
415
417
  @mark.asyncio
418
+ @mark.xdist_group("job_io")
416
419
  class JobIOTests:
417
420
  async def test_job_local_output(self, trainml, capsys):
418
421
  temp_dir = tempfile.TemporaryDirectory()
@@ -527,6 +530,7 @@ class JobIOTests:
527
530
 
528
531
  @mark.create
529
532
  @mark.asyncio
533
+ @mark.xdist_group("job_types")
530
534
  class JobTypeTests:
531
535
  async def test_endpoint(self, trainml):
532
536
 
@@ -648,6 +652,7 @@ class JobTypeTests:
648
652
 
649
653
  @mark.create
650
654
  @mark.asyncio
655
+ @mark.xdist_group("job_features")
651
656
  class JobFeatureTests:
652
657
  async def test_cpu_instance(self, trainml, capsys):
653
658
  job = await trainml.jobs.create(
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.models]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("models")
11
12
  class GetModelTests:
12
13
  @fixture(scope="class")
13
14
  async def model(self, trainml):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.volumes]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("volumes")
11
12
  class GetVolumeTests:
12
13
  @fixture(scope="class")
13
14
  async def volume(self, trainml):
trainml/__init__.py CHANGED
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.14"
16
+ __version__ = "0.5.15"
17
17
  __all__ = "TrainML"
@@ -67,7 +67,7 @@ class Services(object):
67
67
 
68
68
 
69
69
  class Service:
70
- def __init__(self, trainml, **kwargs):
70
+ def __init__(self, trainml, **kwargs):
71
71
  self.trainml = trainml
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 TrainMLException(f"Timeout waiting for {status}")
180
+
181
+ async def generate_certificate(self, **kwargs):
182
+ resp = await self.trainml._query(
183
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate",
184
+ "POST",
185
+ kwargs
186
+ )
187
+ self.__init__(self.trainml, **resp)
188
+ return self
189
+
190
+ async def sign_client_certificate(self, csr, **kwargs):
191
+ certificate = await self.trainml._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
@@ -77,3 +77,20 @@ class ProjectService:
77
77
  await self.trainml._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.trainml._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.trainml._query(
91
+ f"/project/{self._project_uuid}/services/{self._id}/certificate/sign",
92
+ "POST",
93
+ kwargs,
94
+ dict(csr=csr)
95
+ )
96
+ return certificate
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.14
3
+ Version: 0.5.15
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -3,26 +3,28 @@ examples/create_dataset_and_training_job.py,sha256=Ht6soyBZxt8qA9Fp6zAW2syGEdasv
3
3
  examples/local_storage.py,sha256=w8iAeqr5CLOCOkNrqGzEDtybjDGGY7SQUqeE0ibMUrM,1745
4
4
  examples/training_inference_pipeline.py,sha256=SNr4RFT9y69F9G9tMD8ONUbJmXRFrq1yxynq-FbfEf8,2334
5
5
  tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- tests/integration/conftest.py,sha256=VWWTfofsFcBOdSCXQxNYbMcDEEaErDi2wFFMn--LE4Y,1134
7
- tests/integration/test_checkpoints_integration.py,sha256=Giy5Z6WyselfXzskDGR7Z0ks2KK2U7WGQBDPfyqQ6dk,3206
8
- tests/integration/test_datasets_integration.py,sha256=zdHOevduuMUWvVxaHBslpmH8AdvPdqEJ95MdqCC5_rw,3499
6
+ tests/integration/conftest.py,sha256=otgb7lZu62_5zAMAsPpr0U2j0h5lApPlDRCiu7ZfDr8,1135
7
+ tests/integration/test_checkpoints_integration.py,sha256=RbOHZK-fjj2xvYo1XDa6H0war43Qe_1PbiNFndVY87o,3239
8
+ tests/integration/test_datasets_integration.py,sha256=sV88UAMM0IyVVum3zvxfYFbqmR8W4skNWE7jQxbc9ms,3529
9
9
  tests/integration/test_environments_integration.py,sha256=0IckhJvQhd8j4Ouiu0hMq2b7iA1dbZpZYmknyfWjsFM,1403
10
10
  tests/integration/test_gpu_types_integration.py,sha256=V2OncokZWWVq_l5FSmKEDM4EsWrmpB-zKiVPt-we0aY,1256
11
- tests/integration/test_jobs_integration.py,sha256=MHjzm4B6KRcWVRzvjGqHWxzOjvxCcHJhtegk7R_P5ng,25100
12
- tests/integration/test_models_integration.py,sha256=u6KVnX-F00TqwiU-gEoZJP1oKfAblqnyRptOc9vNGJ4,2878
13
- tests/integration/test_volumes_integration.py,sha256=gOmZpwwFxqeOAVmfKWSTmuyshx8nb2zu_0xv1RUEepM,3270
11
+ tests/integration/test_jobs_integration.py,sha256=_c33hYpub3VoWiFY-dAzTVyB66U3n5yvMxoRR-pskHg,25263
12
+ tests/integration/test_models_integration.py,sha256=Kwh8MQ0hOaecclAPq07LBEtAxoZB3rx-yamq0WGtWEA,2906
13
+ tests/integration/test_volumes_integration.py,sha256=W5DeuuKCBx3C89LRWc0jhAZjJEREuPiawTLUnqpPrjg,3299
14
14
  tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- tests/integration/cloudbender/test_providers_integration.py,sha256=oV8ydFsosDZ_Z1Dkg2IN-ZhWuIl5e_HkHAORMsOsAJc,1473
15
+ tests/integration/cloudbender/conftest.py,sha256=jBYUHKJioI-WHM-d0Y4lq5pD4LiOPINj6K9kQkgaGWY,777
16
+ tests/integration/cloudbender/test_providers_integration.py,sha256=mQn2dr4oMpctCVCVs53G5P0Raa0U2uBxEYjZtFFSvTg,1327
17
+ tests/integration/cloudbender/test_regions_integration.py,sha256=Ex1SB8xrl3KQ48n-Cka_1Whj8_uFsmhvJuOSdfK3kPk,1406
18
+ tests/integration/cloudbender/test_services_integration.py,sha256=_cGxEtjTDwz4pne6s1gVyEwoRcYYKcJClNUP5Yalrz0,3367
16
19
  tests/integration/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- tests/integration/projects/conftest.py,sha256=sNN1Svq2IO63Yx3DC7-5ZnksaEifGpDZU-3ti9sZhIU,249
18
- tests/integration/projects/test_projects_credentials_integration.py,sha256=3PnpxNEsedXmyyhqjSb8T4JzupB1Kmwu--3stNTBlN8,1636
19
- tests/integration/projects/test_projects_data_connectors_integration.py,sha256=R90v7HVN4i9k-AOEl17AvnbWIwnD9Fl1OK9IGOV847o,1630
20
- tests/integration/projects/test_projects_datastores_integration.py,sha256=S9aSvryVjrcoEDezr33x4T5aPjEhUwL9O1-1eyzhQ-s,1469
21
- tests/integration/projects/test_projects_integration.py,sha256=HP4jPAyTqGt7pwk42bxQsqGp0olgai6sI-S1XXcXoJs,1241
22
- tests/integration/projects/test_projects_keys_integration.py,sha256=O-a9OwBxIM2l0vi__GMlWP8ncNswxGBJvEhgo8PoalQ,1423
23
- tests/integration/projects/test_projects_members_integration.py,sha256=7nnG9wS4a-OZpOI4C0k3SyeHCqZVP-XsYyHNN6I5hCo,1843
24
- tests/integration/projects/test_projects_secrets_integration.py,sha256=bagLLfSUP805gOOPbn_xCQyifflNzIs8DGUBeUwOhO0,1481
25
- tests/integration/projects/test_projects_services_integration.py,sha256=lBnrqFpjsL3zfaJJmFh2rCWfNmmX-TjyOPCR3KobDAc,1530
20
+ tests/integration/projects/conftest.py,sha256=v4XqQEmNSmV_hNEjLEIOeGJxFoAl-km8UXO_5jO--6E,294
21
+ tests/integration/projects/test_projects_credentials_integration.py,sha256=DYkd57hxu6wGFAyfgQGQbIR6Ja0501UCsSnjWzxBEkk,1675
22
+ tests/integration/projects/test_projects_data_connectors_integration.py,sha256=0giPCNeMx-Mj6--i0LTEt3m_ZwElSQzhrbxmcpNOthc,1669
23
+ tests/integration/projects/test_projects_datastores_integration.py,sha256=Eln5xPy6qp2JWai97U61fAQa4lGpmYlSTjbuzJl2jYc,1508
24
+ tests/integration/projects/test_projects_integration.py,sha256=1tHXGeF05Bsr5osyMyf4VY8qf1a4xYLUaF7yRaoDoLE,1280
25
+ tests/integration/projects/test_projects_members_integration.py,sha256=bEc9AqC-YZ0hifvVaDmcVGbK3aYuTBNGBj0g4rjBEiQ,1882
26
+ tests/integration/projects/test_projects_secrets_integration.py,sha256=dnIT0KiHnXBVP-azfw8MCspTjX7q1ic03rz8XZNijvU,1520
27
+ tests/integration/projects/test_projects_services_integration.py,sha256=3DpOtVMIdG_Icp4s7JxMT70ZDKC1wMD8imkKjlQChb0,1569
26
28
  tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
29
  tests/unit/conftest.py,sha256=hLfECgT0w4pVZABrpK4UUUeaX1aDzQvj8-erWTGBjHA,35588
28
30
  tests/unit/test_auth_unit.py,sha256=nfhlOCR7rUsn_MaD8QQtBc2v0k8pIxqbzGgRAZK1WGc,858
@@ -56,7 +58,6 @@ tests/unit/cli/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
56
58
  tests/unit/cli/projects/test_cli_project_credential_unit.py,sha256=gnmEpNbpEG5S9cYQUjSGty2e8mqeC_cCKmIUAJCgRXg,943
57
59
  tests/unit/cli/projects/test_cli_project_data_connector_unit.py,sha256=_NgnxV8zTIycpHKe7dyL6EXq774puFHQjvgCKsiEMO0,993
58
60
  tests/unit/cli/projects/test_cli_project_datastore_unit.py,sha256=E_kLoIZ5U92LQaPcYAKDyUqpjM1Xe6PVPvofU1Yn6Yk,936
59
- tests/unit/cli/projects/test_cli_project_key_unit.py,sha256=Ma5HjsZt2RaHKgmMdGY59EIc2WdAgbWKWn2LvlYeDqQ,894
60
61
  tests/unit/cli/projects/test_cli_project_secret_unit.py,sha256=QBkq1gVqkjGUni2xCihL994bvseNcA-dyUWaJVU9GjI,915
61
62
  tests/unit/cli/projects/test_cli_project_service_unit.py,sha256=TRNOwK3RUCtktYdCGzfhQE3fE_mn8LweCN5E4gRn1h4,922
62
63
  tests/unit/cli/projects/test_cli_project_unit.py,sha256=0IynsMwFsXBZn-u0gX2iir7AjPsNraHe68PuAe-Lkaw,627
@@ -73,12 +74,11 @@ tests/unit/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
73
74
  tests/unit/projects/test_project_credentials_unit.py,sha256=ObTaJ7_g0MP5N-0qHWdeYips0in1j_4UkOhXXI2FFwo,3480
74
75
  tests/unit/projects/test_project_data_connectors_unit.py,sha256=uQPFx6rwYMWZz1IJkns_LdM5gccyHSIaKb3k8zmfSkY,3385
75
76
  tests/unit/projects/test_project_datastores_unit.py,sha256=kcoaSs54gGqJ9gwPMpP6KHAPRvDJtdJpm49s4aYYt3E,3129
76
- tests/unit/projects/test_project_keys_unit.py,sha256=2-w_VwmWxWHoLWBCoBAfg5WozyeYJHV361ZndlA0xk4,3161
77
77
  tests/unit/projects/test_project_members_unit.py,sha256=C5N6jzT7zM3eL3_bXEddddBHPFJ3617T0lYluBjMy78,3466
78
78
  tests/unit/projects/test_project_secrets_unit.py,sha256=StVlY-ZR3CKxXAt9NL8hTkXUEdgGESH0m6foEPXKE-4,3276
79
79
  tests/unit/projects/test_project_services_unit.py,sha256=ZK5nz78RAmmaCrAwYBd34t87dh6etU-7snLB_xukZHM,3331
80
80
  tests/unit/projects/test_projects_unit.py,sha256=8rJ40bJ7YszEsoL6D02Muey-sgRjqFmMoR-R3IiP_Ig,3810
81
- trainml/__init__.py,sha256=vYYGXBmjJ4UtHA7qJ4sjTIRUHAaoVlBJUKLRiP0Sioc,433
81
+ trainml/__init__.py,sha256=yxX-2bQsXFk3KgMV8b7day_Wz6qy6Ag4kOHvcMCv15k,433
82
82
  trainml/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
83
83
  trainml/auth.py,sha256=clbx5S5prJ3u62aEESdBXIHF_HmreQ-L1ShxcyWQNDs,26565
84
84
  trainml/checkpoints.py,sha256=Hg3_Si7ohzsv4_8JIjoj9pMRpHNZRWKC5R2gtgmp01s,8790
@@ -113,7 +113,6 @@ trainml/cli/project/__init__.py,sha256=HDcJUbKMHhz4Thrvpst5hnywFqzsv0XWmvfKNRi8z
113
113
  trainml/cli/project/credential.py,sha256=gByXKiYf5sJeNRtuXWcercWv8P2IzO5TjT8Ypp4mCR8,3443
114
114
  trainml/cli/project/data_connector.py,sha256=KLDhpNJfwcJkcmyuJRgcVH70Jf73619O9ddYP8vhMvk,1411
115
115
  trainml/cli/project/datastore.py,sha256=IwF0LqsSFn7DrKPRdxQs6kturk9MCI52A1aoWeb7ClA,1336
116
- trainml/cli/project/key.py,sha256=kQlCs_N-5c27hOyGkmT22_J47x8U6CZaSardsaPYGbw,3203
117
116
  trainml/cli/project/secret.py,sha256=tHt7bYDt0MNkLTTePTIJFUh9Mn9ogebPcaSCiNRJFNQ,1927
118
117
  trainml/cli/project/service.py,sha256=FzcyGI9MN-UvnjdKW7GmXLTXLUIXtuTFAISR_FmCmJo,1310
119
118
  trainml/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhdss,64
@@ -125,19 +124,18 @@ trainml/cloudbender/devices.py,sha256=QORNmKdLJoqGZmeWXRnivC1JmNBIw-ebvf4bsoem3r
125
124
  trainml/cloudbender/nodes.py,sha256=U2sTIL2fuBVsNFPsJrvB2JbBcuLULF1-AwJ4dp5ChmM,5924
126
125
  trainml/cloudbender/providers.py,sha256=22ymUl1KLC8UlyoWMsIrOKhUDOwnhklhHQ3EZ_V6eWA,3738
127
126
  trainml/cloudbender/regions.py,sha256=nfSY9fIWp_AaRE_1Y0qwXX6WVSyPKxpji-zUfM3BNUo,5157
128
- trainml/cloudbender/services.py,sha256=KC3VcyljvnazUUG-Tzwm6Ab6d0--yuccXjOaMgYB5uA,5126
127
+ trainml/cloudbender/services.py,sha256=bJtp9ljq8g10r-04phV_XXS8mS8YR3KMbJ4XtHa2SOI,5773
129
128
  trainml/projects/__init__.py,sha256=6NKCcHtQMeGB1IyU-djANphfnDX6MEkrXUM5Fyq9fWg,75
130
129
  trainml/projects/credentials.py,sha256=6WqHy_-SZZwqE4rULLF8gSyeRVmfsUxqZBuCjBXyxKw,2255
131
130
  trainml/projects/data_connectors.py,sha256=WZATdUq4vE3x47Ny5HDwPD7lIUx0syjaSE9BmFWNuEg,2216
132
131
  trainml/projects/datastores.py,sha256=qocqD5mGfm2V_wh36CEh3oldnIeJg57ppc6CM129Dkk,2095
133
- trainml/projects/keys.py,sha256=qcQAdysTbfVW19tzzxZDvBBWjU9DG0dkViLmG_S0SyM,2157
134
132
  trainml/projects/members.py,sha256=7fptZ2leIQEUiRtvd3z15RTOJyPafzc5wwQLNxICElQ,2826
135
133
  trainml/projects/projects.py,sha256=5_6HUg9ZYq8v69QPgC4Yfd_DM0OlZd8tnxh2ahjOgm0,2890
136
134
  trainml/projects/secrets.py,sha256=TIvBd3rAvd4lF3pm5qR98UslHjldzlnzn_n9yvpmLgg,2160
137
- trainml/projects/services.py,sha256=W2IHgRUT9BemWThsY7uAot2gbmyPaH41WAii-NQ_cT8,2206
138
- trainml-0.5.14.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
139
- trainml-0.5.14.dist-info/METADATA,sha256=nZzRcI63jvOZveDYFvGm4UEVLrPRCItQ6nUxTQ2Pw-Q,7346
140
- trainml-0.5.14.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
141
- trainml-0.5.14.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
142
- trainml-0.5.14.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
143
- trainml-0.5.14.dist-info/RECORD,,
135
+ trainml/projects/services.py,sha256=rI-uFmojqOTNLbqBeX6gaSwMkI6LKzRuJthQCH0A2h4,2771
136
+ trainml-0.5.15.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
137
+ trainml-0.5.15.dist-info/METADATA,sha256=kFbUgFw2pcOjJgySdhnKmVrkVpkFMI6mtVrN7t26yXA,7346
138
+ trainml-0.5.15.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
139
+ trainml-0.5.15.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
140
+ trainml-0.5.15.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
141
+ trainml-0.5.15.dist-info/RECORD,,
@@ -1,43 +0,0 @@
1
- import re
2
- import sys
3
- import asyncio
4
- from pytest import mark, fixture
5
-
6
- pytestmark = [mark.sdk, mark.integration, mark.projects]
7
-
8
-
9
- @mark.create
10
- @mark.asyncio
11
- class ProjectKeysTests:
12
- @fixture(scope="class")
13
- async def project_key(self, project):
14
- project_key = await project.keys.put(
15
- type="aws", key_id="ASFHALKF", secret="IUHKLHKAHF"
16
- )
17
- yield project_key
18
- await project.keys.remove(type="aws")
19
-
20
- async def test_list_project_keys(self, project, project_key):
21
- keys = await project.keys.list()
22
- assert len(keys) > 0
23
-
24
- async def test_project_key_properties(self, project, project_key):
25
- assert isinstance(project_key.project_uuid, str)
26
- assert isinstance(project_key.type, str)
27
- assert isinstance(project_key.key_id, str)
28
- assert project_key.type == "aws"
29
- assert project.id == project_key.project_uuid
30
-
31
- async def test_project_key_str(self, project_key):
32
- string = str(project_key)
33
- regex = r"^{.*\"type\": \"" + project_key.type + r"\".*}$"
34
- assert isinstance(string, str)
35
- assert re.match(regex, string)
36
-
37
- async def test_project_key_repr(self, project_key):
38
- string = repr(project_key)
39
- regex = (
40
- r"^ProjectKey\( trainml , \*\*{.*'type': '" + project_key.type + r"'.*}\)$"
41
- )
42
- assert isinstance(string, str)
43
- assert re.match(regex, string)
@@ -1,26 +0,0 @@
1
- import re
2
- import json
3
- import click
4
- from unittest.mock import AsyncMock, patch, create_autospec
5
- from pytest import mark, fixture, raises
6
-
7
- pytestmark = [mark.cli, mark.unit, mark.projects]
8
-
9
- from trainml.cli.project import key as specimen
10
- from trainml.projects import (
11
- Project,
12
- )
13
-
14
-
15
- def test_list_keys(runner, mock_project_keys):
16
- with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
17
- project = create_autospec(Project)
18
- mock_trainml.projects = AsyncMock()
19
- mock_trainml.projects.get = AsyncMock(return_value=project)
20
- mock_trainml.projects.get_current = AsyncMock(return_value=project)
21
- project.keys = AsyncMock()
22
- project.keys.list = AsyncMock(return_value=mock_project_keys)
23
- result = runner.invoke(specimen, ["list"])
24
- print(result)
25
- assert result.exit_code == 0
26
- project.keys.list.assert_called_once()
@@ -1,96 +0,0 @@
1
- import re
2
- import json
3
- import logging
4
- from unittest.mock import AsyncMock, patch
5
- from pytest import mark, fixture, raises
6
- from aiohttp import WSMessage, WSMsgType
7
-
8
- import trainml.projects.keys as specimen
9
- from trainml.exceptions import (
10
- ApiError,
11
- SpecificationError,
12
- TrainMLException,
13
- )
14
-
15
- pytestmark = [mark.sdk, mark.unit, mark.projects]
16
-
17
-
18
- @fixture
19
- def project_keys(mock_trainml):
20
- yield specimen.ProjectKeys(mock_trainml, project_id="1")
21
-
22
-
23
- @fixture
24
- def project_key(mock_trainml):
25
- yield specimen.ProjectKey(
26
- mock_trainml, project_uuid="proj-id-1", type="aws", key_id="AIYHGFSDLK"
27
- )
28
-
29
-
30
- class ProjectKeysTests:
31
- @mark.asyncio
32
- async def test_project_keys_list(self, project_keys, mock_trainml):
33
- api_response = [
34
- {"project_uuid": "proj-id-1", "type": "aws", "key_id": "AIYHGFSDLK"},
35
- {"project_uuid": "proj-id-1", "type": "gcp", "key_id": "credentials.json"},
36
- ]
37
- mock_trainml._query = AsyncMock(return_value=api_response)
38
- resp = await project_keys.list()
39
- mock_trainml._query.assert_called_once_with("/project/1/keys", "GET", dict())
40
- assert len(resp) == 2
41
-
42
- @mark.asyncio
43
- async def test_remove_project_key(
44
- self,
45
- project_keys,
46
- mock_trainml,
47
- ):
48
- api_response = dict()
49
- mock_trainml._query = AsyncMock(return_value=api_response)
50
- await project_keys.remove("aws")
51
- mock_trainml._query.assert_called_once_with(
52
- "/project/1/key/aws", "DELETE", dict()
53
- )
54
-
55
- @mark.asyncio
56
- async def test_put_project_key(self, project_keys, mock_trainml):
57
- requested_config = dict(type="aws", key_id="AIUDHADA", secret="ASKHJSLKF")
58
- expected_payload = dict(key_id="AIUDHADA", secret="ASKHJSLKF")
59
- api_response = {
60
- "project_uuid": "project-id-1",
61
- "type": "aws",
62
- "key_id": "AIUDHADA",
63
- }
64
-
65
- mock_trainml._query = AsyncMock(return_value=api_response)
66
- response = await project_keys.put(**requested_config)
67
- mock_trainml._query.assert_called_once_with(
68
- "/project/1/key/aws", "PUT", None, expected_payload
69
- )
70
- assert response.type == "aws"
71
-
72
-
73
- class ProjectKeyTests:
74
- def test_project_key_properties(self, project_key):
75
- assert isinstance(project_key.type, str)
76
- assert isinstance(project_key.key_id, str)
77
- assert isinstance(project_key.project_uuid, str)
78
-
79
- def test_project_key_str(self, project_key):
80
- string = str(project_key)
81
- regex = r"^{.*\"type\": \"" + project_key.type + r"\".*}$"
82
- assert isinstance(string, str)
83
- assert re.match(regex, string)
84
-
85
- def test_project_key_repr(self, project_key):
86
- string = repr(project_key)
87
- regex = (
88
- r"^ProjectKey\( trainml , \*\*{.*'type': '" + project_key.type + r"'.*}\)$"
89
- )
90
- assert isinstance(string, str)
91
- assert re.match(regex, string)
92
-
93
- def test_project_key_bool(self, project_key, mock_trainml):
94
- empty_project_key = specimen.ProjectKey(mock_trainml)
95
- assert bool(project_key)
96
- assert not bool(empty_project_key)
@@ -1,124 +0,0 @@
1
- import click
2
- import os
3
- import json
4
- import base64
5
- from pathlib import Path
6
- from trainml.cli import pass_config
7
- from trainml.cli.project import project
8
-
9
-
10
- @project.group()
11
- @pass_config
12
- def key(config):
13
- """trainML project key commands."""
14
- pass
15
-
16
-
17
- @key.command()
18
- @pass_config
19
- def list(config):
20
- """List keys."""
21
- data = [
22
- ["TYPE", "KEY ID", "UPDATED AT"],
23
- [
24
- "-" * 80,
25
- "-" * 80,
26
- "-" * 80,
27
- ],
28
- ]
29
- project = config.trainml.run(config.trainml.client.projects.get_current())
30
- keys = config.trainml.run(project.keys.list())
31
-
32
- for key in keys:
33
- data.append(
34
- [
35
- key.type,
36
- key.key_id,
37
- key.updated_at.isoformat(timespec="seconds"),
38
- ]
39
- )
40
-
41
- for row in data:
42
- click.echo(
43
- "{: >13.11} {: >37.35} {: >28.26}" "".format(*row),
44
- file=config.stdout,
45
- )
46
-
47
-
48
- @key.command()
49
- @click.argument(
50
- "type",
51
- type=click.Choice(
52
- [
53
- "aws",
54
- "azure",
55
- "docker",
56
- "gcp",
57
- "huggingface",
58
- "kaggle",
59
- "ngc",
60
- "wasabi",
61
- ],
62
- case_sensitive=False,
63
- ),
64
- )
65
- @pass_config
66
- def put(config, type):
67
- """
68
- Set a key.
69
-
70
- A key is uploaded.
71
- """
72
- project = config.trainml.run(config.trainml.client.projects.get_current())
73
-
74
- tenant = None
75
-
76
- if type in ["aws", "wasabi"]:
77
- key_id = click.prompt("Enter the key ID", type=str, hide_input=False)
78
- secret = click.prompt("Enter the secret key", type=str, hide_input=True)
79
- elif type == "azure":
80
- key_id = click.prompt(
81
- "Enter the Application (client) ID", type=str, hide_input=False
82
- )
83
- tenant = click.prompt(
84
- "Enter the Directory (tenant) ley", type=str, hide_input=False
85
- )
86
- secret = click.prompt("Enter the client secret", type=str, hide_input=True)
87
- elif type in ["docker", "huggingface"]:
88
- key_id = click.prompt("Enter the username", type=str, hide_input=False)
89
- secret = click.prompt("Enter the access token", type=str, hide_input=True)
90
- elif type in ["gcp", "kaggle"]:
91
- file_name = click.prompt(
92
- "Enter the path of the credentials file",
93
- type=click.Path(
94
- exists=True, file_okay=True, dir_okay=False, resolve_path=True
95
- ),
96
- hide_input=False,
97
- )
98
- key_id = os.path.basename(file_name)
99
- with open(file_name) as f:
100
- secret = json.load(f)
101
- secret = json.dumps(secret)
102
- elif type == "ngc":
103
- key_id = "$oauthtoken"
104
- secret = click.prompt("Enter the access token", type=str, hide_input=True)
105
- else:
106
- raise click.UsageError("Unsupported key type")
107
-
108
- return config.trainml.run(
109
- project.keys.put(type=type, key_id=key_id, secret=secret, tenant=tenant)
110
- )
111
-
112
-
113
- @key.command()
114
- @click.argument("name", type=click.STRING)
115
- @pass_config
116
- def remove(config, name):
117
- """
118
- Remove a key.
119
-
120
-
121
- """
122
- project = config.trainml.run(config.trainml.client.projects.get_current())
123
-
124
- return config.trainml.run(project.key.remove(name))
trainml/projects/keys.py DELETED
@@ -1,71 +0,0 @@
1
- import json
2
- import logging
3
- from datetime import datetime
4
- from dateutil import parser, tz
5
-
6
-
7
- class ProjectKeys(object):
8
- def __init__(self, trainml, project_id):
9
- self.trainml = trainml
10
- self.project_id = project_id
11
-
12
- async def list(self, **kwargs):
13
- resp = await self.trainml._query(
14
- f"/project/{self.project_id}/keys", "GET", kwargs
15
- )
16
- keys = [ProjectKey(self.trainml, **service) for service in resp]
17
- return keys
18
-
19
- async def put(self, type, key_id, secret, tenant=None, **kwargs):
20
- data = dict(key_id=key_id, secret=secret, tenant=tenant)
21
- payload = {k: v for k, v in data.items() if v is not None}
22
- logging.info(f"Creating Project Key {type}")
23
- resp = await self.trainml._query(
24
- f"/project/{self.project_id}/key/{type}", "PUT", None, payload
25
- )
26
- key = ProjectKey(self.trainml, **resp)
27
- logging.info(f"Created Project Key {type} in project {self.project_id}")
28
-
29
- return key
30
-
31
- async def remove(self, type, **kwargs):
32
- await self.trainml._query(
33
- f"/project/{self.project_id}/key/{type}", "DELETE", kwargs
34
- )
35
-
36
-
37
- class ProjectKey:
38
- def __init__(self, trainml, **kwargs):
39
- self.trainml = trainml
40
- self._entity = kwargs
41
- self._type = self._entity.get("type")
42
- self._project_uuid = self._entity.get("project_uuid")
43
- self._key_id = self._entity.get("key_id")
44
- self._updated_at = self._entity.get("updatedAt")
45
-
46
- @property
47
- def type(self) -> str:
48
- return self._type
49
-
50
- @property
51
- def project_uuid(self) -> str:
52
- return self._project_uuid
53
-
54
- @property
55
- def key_id(self) -> str:
56
- return self._key_id
57
-
58
- @property
59
- def updated_at(self) -> datetime:
60
- timestamp = parser.isoparse(self._updated_at)
61
- timezone = tz.tzlocal()
62
- return timestamp.astimezone(timezone)
63
-
64
- def __str__(self):
65
- return json.dumps({k: v for k, v in self._entity.items()})
66
-
67
- def __repr__(self):
68
- return f"ProjectKey( trainml , **{self._entity.__repr__()})"
69
-
70
- def __bool__(self):
71
- return bool(self._type)