Skip to content

Commit 2e51c92

Browse files
author
Matt Sokoloff
committed
test delegated access
1 parent 0fb8e21 commit 2e51c92

File tree

8 files changed

+116
-39
lines changed

8 files changed

+116
-39
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
# Version 3.4.0 (2021-09-10)
4+
## Added
5+
* New `IAMIntegration` entity
6+
* `iam_integration` parameter to `Client.create_dataset()` to specify the iam integration
7+
- `create_dataset` will use the default integration if this param is not set
8+
* `Organization.get_iam_integrations()` to list all integrations available to your org
9+
* `Organization.get_default_iam_integration()` to only get the defaault iam integration
10+
311
# Version 3.3.0 (2021-09-02)
412
## Added
513
* `Dataset.create_data_rows_sync()` for synchronous bulk uploads of data rows

labelbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "labelbox"
2-
__version__ = "3.3.0"
2+
__version__ = "3.4.0"
33

44
from labelbox.schema.project import Project
55
from labelbox.client import Client

labelbox/client.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# type: ignore
22
from datetime import datetime, timezone
33
import json
4-
from labelbox.schema.iam_integration import IAMIntegration
4+
55
import logging
66
import mimetypes
77
import os
@@ -10,8 +10,9 @@
1010
import requests
1111
import requests.exceptions
1212

13-
from labelbox import utils
1413
import labelbox.exceptions
14+
from labelbox import utils
15+
from labelbox import __version__ as SDK_VERSION
1516
from labelbox.orm import query
1617
from labelbox.orm.db_object import DbObject
1718
from labelbox.pagination import PaginatedCollection
@@ -23,8 +24,8 @@
2324
from labelbox.schema.organization import Organization
2425
from labelbox.schema.data_row_metadata import DataRowMetadataOntology
2526
from labelbox.schema.labeling_frontend import LabelingFrontend
27+
from labelbox.schema.iam_integration import IAMIntegration
2628
from labelbox.schema import role
27-
from labelbox import __version__ as SDK_VERSION
2829

2930
logger = logging.getLogger(__name__)
3031

@@ -504,15 +505,18 @@ def _create(self, db_object_type, data):
504505
res = res["create%s" % db_object_type.type_name()]
505506
return db_object_type(self, res)
506507

507-
def create_dataset(self, **kwargs):
508+
def create_dataset(self, iam_integration=IAMIntegration._DEFAULT, **kwargs):
508509
""" Creates a Dataset object on the server.
510+
This will attempt to connect the organization's default IAM integration if it exists
509511
510512
Attribute values are passed as keyword arguments.
511513
512514
>>> project = client.get_project("<project_uid>")
513515
>>> dataset = client.create_dataset(name="<dataset_name>", projects=project)
514516
515517
Args:
518+
iam_integration (IAMIntegration) : Uses the default integration.
519+
Optionally specify another integration or set as None to not use delegated access
516520
**kwargs: Keyword arguments with Dataset attribute values.
517521
Returns:
518522
A new Dataset object.
@@ -521,31 +525,41 @@ def create_dataset(self, **kwargs):
521525
any of the attribute names given in kwargs.
522526
"""
523527
dataset = self._create(Dataset, kwargs)
524-
iam_integration = kwargs.get('iam_integration') or self.get_organization().get_default_iam_integration()
525-
if iam_integration is not None:
526-
if not isinstance(iam_integration, IAMIntegration):
527-
raise TypeError(f"iam integration must be a reference an `IAMIntegration` object. Found {type(iam_integration)}")
528-
529-
if not iam_integration.valid:
530-
raise ValueError("Invalid integration is invalid. Please select another integration or remove default.")
531-
try:
532-
self.execute("""
533-
mutation setSignerForDatasetPyApi($signerId: ID!, $datasetId: ID!) {
534-
setSignerForDataset(data: { signerId: $signerId}, where: {id: $datasetId}){id}}
535-
""", {'signerId' : iam_integration.uid, 'datasetId' : dataset.uid})
536-
validation_result = self.execute("""
537-
mutation validateDatasetPyApi($id: ID!){validateDataset(where: {id : $id}){
538-
valid checks{name, success}}}
539-
""", {'id' : dataset.uid})
540-
if not validation_result['validateDataset']['checks'][0]['success']:
541-
raise labelbox.exceptions.LabelboxError(
542-
f"IAMIntegration {validation_result['validateDataset']['checks']['name']} was not successfully added added to the project."
543-
)
544-
except Exception as e:
545-
dataset.delete()
546-
raise e
547-
return dataset
548528

529+
if iam_integration == IAMIntegration._DEFAULT:
530+
iam_integration = self.get_organization(
531+
).get_default_iam_integration()
532+
533+
if iam_integration is None:
534+
return dataset
535+
536+
if not isinstance(iam_integration, IAMIntegration):
537+
raise TypeError(
538+
f"iam integration must be a reference an `IAMIntegration` object. Found {type(iam_integration)}"
539+
)
540+
541+
if not iam_integration.valid:
542+
raise ValueError("Integration is not valid. Please select another.")
543+
try:
544+
self.execute(
545+
"""mutation setSignerForDatasetPyApi($signerId: ID!, $datasetId: ID!) {
546+
setSignerForDataset(data: { signerId: $signerId}, where: {id: $datasetId}){id}}
547+
""", {
548+
'signerId': iam_integration.uid,
549+
'datasetId': dataset.uid
550+
})
551+
validation_result = self.execute(
552+
"""mutation validateDatasetPyApi($id: ID!){validateDataset(where: {id : $id}){
553+
valid checks{name, success}}}
554+
""", {'id': dataset.uid})
555+
if not validation_result['validateDataset']['checks'][0]['success']:
556+
raise labelbox.exceptions.LabelboxError(
557+
f"IAMIntegration {validation_result['validateDataset']['checks']['name']} was not successfully added added to the project."
558+
)
559+
except Exception as e:
560+
dataset.delete()
561+
raise e
562+
return dataset
549563

550564
def create_project(self, **kwargs):
551565
""" Creates a Project object on the server.
@@ -643,5 +657,3 @@ def create_model(self, name, ontology_id):
643657
"ontologyId": ontology_id
644658
})
645659
return Model(self, result['createModel'])
646-
647-

labelbox/schema/dataset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class Dataset(DbObject, Updateable, Deletable):
4444
data_rows = Relationship.ToMany("DataRow", False)
4545
created_by = Relationship.ToOne("User", False, "created_by")
4646
organization = Relationship.ToOne("Organization", False)
47-
iam_integration = Relationship.ToOne("IAMIntegration", False, "iam_integration", "signer")
47+
iam_integration = Relationship.ToOne("IAMIntegration", False,
48+
"iam_integration", "signer")
4849

4950
def create_data_row(self, **kwargs):
5051
""" Creates a single DataRow belonging to this dataset.

labelbox/schema/iam_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class IAMIntegration(DbObject):
1515
is_org_default (boolean)
1616
1717
"""
18+
_DEFAULT = "DEFAULT"
1819

1920
name = Field.String("name")
2021
created_at = Field.DateTime("created_at")

labelbox/schema/organization.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,20 +130,31 @@ def remove_user(self, user: User):
130130
updateUser(where: {id: $%s}, data: {deleted: true}) { id deleted }
131131
}""" % (user_id_param, user_id_param), {user_id_param: user.uid})
132132

133-
134133
def get_iam_integrations(self):
135134
"""
136135
Returns all IAM Integrations for an organization
137136
"""
138-
res = self.client.execute("""query getAllIntegrationsPyApi { iamIntegrations {%s} } """ % query.results_query_part(Entity.IAMIntegration))
139-
return [Entity.IAMIntegration(self.client, integration_data) for integration_data in res['iamIntegrations']]
137+
res = self.client.execute(
138+
"""query getAllIntegrationsPyApi { iamIntegrations {%s} } """ %
139+
query.results_query_part(Entity.IAMIntegration))
140+
return [
141+
Entity.IAMIntegration(self.client, integration_data)
142+
for integration_data in res['iamIntegrations']
143+
]
140144

141145
def get_default_iam_integration(self):
142146
"""
143-
Returns the default IAM integration for the organization
147+
Returns the default IAM integration for the organization.
148+
Will return None if there are no default integrations for the org.
144149
"""
145150
integrations = self.get_iam_integrations()
146-
default_integration = [integration for integration in integrations if integration.is_org_default]
151+
default_integration = [
152+
integration for integration in integrations
153+
if integration.is_org_default
154+
]
147155
if len(default_integration) > 1:
148-
raise ValueError("Found more than one default signer. Please contact Labelbox to resolve")
149-
return None if not len(default_integration) else default_integration.pop()
156+
raise ValueError(
157+
"Found more than one default signer. Please contact Labelbox to resolve"
158+
)
159+
return None if not len(
160+
default_integration) else default_integration.pop()

tests/integration/test_data_row_metadata.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def test_delete_non_existent_schema_id(datarow, mdo):
209209

210210

211211
@pytest.mark.slow
212+
@pytest.mark.skip("Test is inconsistent.")
212213
def test_large_bulk_delete_non_existent_schema_id(big_dataset, mdo):
213214
deletes = []
214215
n_fields_start = 0
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import requests
2+
import pytest
3+
4+
5+
@pytest.mark.skip("Can only be tested in specific organizations.")
6+
def test_default_integration(client):
7+
# This tests assumes the following:
8+
# 1. gcp delegated access is configured to work with utkarsh-da-test-bucket
9+
# 2. the integration name is gcp test
10+
# 3. This integration is the default
11+
ds = client.create_dataset(name="new_ds")
12+
dr = ds.create_data_row(
13+
row_data=
14+
"gs://utkarsh-da-test-bucket/mathew-schwartz-8rj4sz9YLCI-unsplash.jpg")
15+
assert requests.get(dr.row_data).status_code == 200
16+
assert ds.iam_integration().name == "GCP Test"
17+
ds.delete()
18+
19+
20+
@pytest.mark.skip("Can only be tested in specific organizations.")
21+
def test_non_default_integration(client):
22+
# This tests assumes the following:
23+
# 1. aws delegated access is configured to work with lbox-test-bucket
24+
# 2. an integration called aws is available to the org
25+
integrations = client.get_organization().get_iam_integrations()
26+
integration = [inte for inte in integrations if 'aws' in inte.name][0]
27+
assert integration.valid
28+
ds = client.create_dataset(iam_integration=integration, name="new_ds")
29+
assert ds.iam_integration().name == "aws"
30+
dr = ds.create_data_row(
31+
row_data=
32+
"https://lbox-test-bucket.s3.us-east-1.amazonaws.com/2021_09_08_0hz_Kleki.png"
33+
)
34+
assert requests.get(dr.row_data).status_code == 200
35+
ds.delete()
36+
37+
38+
def test_no_integration(client, image_url):
39+
ds = client.create_dataset(iam_integration=None, name="new_ds")
40+
assert ds.iam_integration() is None
41+
dr = ds.create_data_row(row_data=image_url)
42+
assert requests.get(dr.row_data).status_code == 200
43+
ds.delete()

0 commit comments

Comments
 (0)