Skip to content

Commit 4a1a668

Browse files
authored
Merge pull request #409 from Labelbox/al-1351
initial addition to include filters for export_labels
2 parents c9a9332 + 1274529 commit 4a1a668

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

labelbox/schema/organization.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from labelbox import utils
55
from labelbox.orm.db_object import DbObject, query, Entity
66
from labelbox.orm.model import Field, Relationship
7+
from labelbox.schema.invite import InviteLimit
78

89
if TYPE_CHECKING:
910
from labelbox import Role, User, ProjectRole, Invite, InviteLimit, IAMIntegration
@@ -94,7 +95,7 @@ def invite_user(
9495
raise LabelboxError(f"Unable to send invite for email {email}")
9596
return Entity.Invite(self.client, invite_response)
9697

97-
def invite_limit(self) -> "InviteLimit":
98+
def invite_limit(self) -> InviteLimit:
9899
""" Retrieve invite limits for the org
99100
This already accounts for users currently in the org
100101
Meaining that `used = users + invites, remaining = limit - (users + invites)`
@@ -108,7 +109,7 @@ def invite_limit(self) -> "InviteLimit":
108109
"""query InvitesLimitPyApi($%s: ID!) {
109110
invitesLimit(where: {id: $%s}) { used limit remaining }
110111
}""" % (org_id_param, org_id_param), {org_id_param: self.uid})
111-
return Entity.InviteLimit(
112+
return InviteLimit(
112113
**{utils.snake_case(k): v for k, v in res['invitesLimit'].items()})
113114

114115
def remove_user(self, user: "User") -> None:

labelbox/schema/project.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ def export_queued_data_rows(self,
194194
self.uid)
195195
time.sleep(sleep_time)
196196

197-
def video_label_generator(self, timeout_seconds=600) -> LabelGenerator:
197+
def video_label_generator(self,
198+
timeout_seconds=600,
199+
**kwargs) -> LabelGenerator:
198200
"""
199201
Download video annotations
200202
@@ -203,7 +205,8 @@ def video_label_generator(self, timeout_seconds=600) -> LabelGenerator:
203205
"""
204206
_check_converter_import()
205207
json_data = self.export_labels(download=True,
206-
timeout_seconds=timeout_seconds)
208+
timeout_seconds=timeout_seconds,
209+
**kwargs)
207210
# assert that the instance this would fail is only if timeout runs out
208211
assert isinstance(
209212
json_data,
@@ -222,7 +225,7 @@ def video_label_generator(self, timeout_seconds=600) -> LabelGenerator:
222225
"Or use project.label_generator() for text and imagery data.")
223226
return LBV1Converter.deserialize_video(json_data, self.client)
224227

225-
def label_generator(self, timeout_seconds=600) -> LabelGenerator:
228+
def label_generator(self, timeout_seconds=600, **kwargs) -> LabelGenerator:
226229
"""
227230
Download text and image annotations
228231
@@ -231,7 +234,8 @@ def label_generator(self, timeout_seconds=600) -> LabelGenerator:
231234
"""
232235
_check_converter_import()
233236
json_data = self.export_labels(download=True,
234-
timeout_seconds=timeout_seconds)
237+
timeout_seconds=timeout_seconds,
238+
**kwargs)
235239
# assert that the instance this would fail is only if timeout runs out
236240
assert isinstance(
237241
json_data,
@@ -250,26 +254,69 @@ def label_generator(self, timeout_seconds=600) -> LabelGenerator:
250254
"Or use project.video_label_generator() for video data.")
251255
return LBV1Converter.deserialize(json_data)
252256

253-
def export_labels(
254-
self,
255-
download=False,
256-
timeout_seconds=600) -> Optional[Union[str, List[Dict[Any, Any]]]]:
257+
def export_labels(self,
258+
download=False,
259+
timeout_seconds=600,
260+
**kwargs) -> Optional[Union[str, List[Dict[Any, Any]]]]:
257261
""" Calls the server-side Label exporting that generates a JSON
258262
payload, and returns the URL to that payload.
259263
260264
Will only generate a new URL at a max frequency of 30 min.
261265
262266
Args:
267+
download (bool): Returns the url if False
263268
timeout_seconds (float): Max waiting time, in seconds.
269+
start (str): Earliest date for labels, formatted "YYYY-MM-DD"
270+
end (str): Latest date for labels, formatted "YYYY-MM-DD"
264271
Returns:
265272
URL of the data file with this Project's labels. If the server didn't
266273
generate during the `timeout_seconds` period, None is returned.
267274
"""
275+
276+
def _string_from_dict(dictionary: dict, value_with_quotes=False) -> str:
277+
"""Returns a concatenated string of the dictionary's keys and values
278+
279+
The string will be formatted as {key}: 'value' for each key. Value will be inclusive of
280+
quotations while key will not. This can be toggled with `value_with_quotes`"""
281+
282+
quote = "\"" if value_with_quotes else ""
283+
return ",".join([
284+
f"""{c}: {quote}{dictionary.get(c)}{quote}"""
285+
for c in dictionary
286+
if dictionary.get(c)
287+
])
288+
289+
def _validate_datetime(string_date: str) -> bool:
290+
"""helper function validate that datetime is as follows: YYYY-MM-DD for the export"""
291+
if string_date:
292+
try:
293+
datetime.strptime(string_date, "%Y-%m-%d")
294+
except ValueError:
295+
raise ValueError(f"""Incorrect format for: {string_date}.
296+
Format must be \"YYYY-MM-DD\"""")
297+
return True
298+
268299
sleep_time = 2
269300
id_param = "projectId"
301+
filter_param = ""
302+
filter_param_dict = {}
303+
304+
if "start" in kwargs or "end" in kwargs:
305+
created_at_dict = {
306+
"start": kwargs.get("start", ""),
307+
"end": kwargs.get("end", "")
308+
}
309+
[_validate_datetime(date) for date in created_at_dict.values()]
310+
filter_param_dict["labelCreatedAt"] = "{%s}" % _string_from_dict(
311+
created_at_dict, value_with_quotes=True)
312+
313+
if filter_param_dict:
314+
filter_param = """, filters: {%s }""" % (_string_from_dict(
315+
filter_param_dict, value_with_quotes=False))
316+
270317
query_str = """mutation GetLabelExportUrlPyApi($%s: ID!)
271-
{exportLabels(data:{projectId: $%s }) {downloadUrl createdAt shouldPoll} }
272-
""" % (id_param, id_param)
318+
{exportLabels(data:{projectId: $%s%s}) {downloadUrl createdAt shouldPoll} }
319+
""" % (id_param, id_param, filter_param)
273320

274321
while True:
275322
res = self.client.execute(query_str, {id_param: self.uid})

tests/integration/test_data_rows.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ def test_data_row_bulk_creation(dataset, rand_gen, image_url):
5757
},
5858
])
5959
assert task in client.get_user().created_tasks()
60-
# TODO make Tasks expandable
61-
with pytest.raises(InvalidQueryError):
62-
assert task.created_by() == client.get_user()
6360
task.wait_till_done()
6461
assert task.status == "COMPLETE"
6562

0 commit comments

Comments
 (0)