2020from labelbox .pagination import PaginatedCollection
2121from labelbox .schema .consensus_settings import ConsensusSettings
2222from labelbox .schema .data_row import DataRow
23+ from labelbox .schema .export_filters import ProjectExportFilters
2324from labelbox .schema .export_params import ProjectExportParams
2425from labelbox .schema .media_type import MediaType
2526from labelbox .schema .queue_mode import QueueMode
4647logger = logging .getLogger (__name__ )
4748
4849
50+ def _validate_datetime (string_date : str ) -> bool :
51+ """helper function validate that datetime is as follows: YYYY-MM-DD for the export"""
52+ if string_date :
53+ for fmt in ("%Y-%m-%d" , "%Y-%m-%d %H:%M:%S" ):
54+ try :
55+ datetime .strptime (string_date , fmt )
56+ return True
57+ except ValueError :
58+ pass
59+ raise ValueError (f"""Incorrect format for: { string_date } .
60+ Format must be \" YYYY-MM-DD\" or \" YYYY-MM-DD hh:mm:ss\" """ )
61+ return True
62+
63+
4964class Project (DbObject , Updateable , Deletable ):
5065 """ A Project is a container that includes a labeling frontend, an ontology,
5166 datasets and labels.
@@ -337,19 +352,6 @@ def _string_from_dict(dictionary: dict, value_with_quotes=False) -> str:
337352 if dictionary .get (c )
338353 ])
339354
340- def _validate_datetime (string_date : str ) -> bool :
341- """helper function validate that datetime is as follows: YYYY-MM-DD for the export"""
342- if string_date :
343- for fmt in ("%Y-%m-%d" , "%Y-%m-%d %H:%M:%S" ):
344- try :
345- datetime .strptime (string_date , fmt )
346- return True
347- except ValueError :
348- pass
349- raise ValueError (f"""Incorrect format for: { string_date } .
350- Format must be \" YYYY-MM-DD\" or \" YYYY-MM-DD hh:mm:ss\" """ )
351- return True
352-
353355 sleep_time = 2
354356 id_param = "projectId"
355357 filter_param = ""
@@ -403,12 +405,24 @@ def _validate_datetime(string_date: str) -> bool:
403405 """
404406 Creates a project run export task with the given params and returns the task.
405407
406- >>> export_task = export_v2("my_export_task", filter={"media_attributes": True})
408+ >>> task = project.export_v2(
409+ >>> filters={
410+ >>> "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"],
411+ >>> "label_created_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"]
412+ >>> },
413+ >>> params={
414+ >>> "include_performance_details": False,
415+ >>> "include_labels": True
416+ >>> })
417+ >>> task.wait_till_done()
418+ >>> task.result
419+
407420
408421 """
409422
410423 def export_v2 (self ,
411424 task_name : Optional [str ] = None ,
425+ filters : ProjectExportFilters = None ,
412426 params : Optional [ProjectExportParams ] = None ) -> Task :
413427
414428 _params = params or ProjectExportParams ({
@@ -420,6 +434,15 @@ def export_v2(self,
420434 "label_details" : False
421435 })
422436
437+ _filters = filters or ProjectExportFilters ()
438+
439+ def _get_timezone () -> str :
440+ timezone_query_str = """query CurrentUserPyApi { user { timezone } }"""
441+ tz_res = self .client .execute (timezone_query_str )
442+ return tz_res ["user" ]["timezone" ] or "UTC"
443+
444+ timezone : Optional [str ] = None
445+
423446 mutation_name = "exportDataRowsInProject"
424447 create_task_query_str = """mutation exportDataRowsInProjectPyApi($input: ExportDataRowsInProjectInput!){
425448 %s(input: $input) {taskId} }
@@ -428,7 +451,11 @@ def export_v2(self,
428451 "input" : {
429452 "taskName" : task_name ,
430453 "filters" : {
431- "projectId" : self .uid
454+ "projectId" : self .uid ,
455+ "searchQuery" : {
456+ "scope" : None ,
457+ "query" : []
458+ }
432459 },
433460 "params" : {
434461 "includeAttachments" :
@@ -446,6 +473,88 @@ def export_v2(self,
446473 },
447474 }
448475 }
476+
477+ if _filters .get ('last_activity_at' ) is not None :
478+ if timezone is None :
479+ timezone = _get_timezone ()
480+ values = _filters ['last_activity_at' ]
481+ start , end = values
482+ if (start is not None and end is not None ):
483+ [_validate_datetime (date ) for date in values ]
484+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
485+ {
486+ "type" : "data_row_last_activity_at" ,
487+ "value" : {
488+ "operator" : "BETWEEN" ,
489+ "timezone" : timezone ,
490+ "value" : {
491+ "min" : start ,
492+ "max" : end
493+ }
494+ }
495+ })
496+ elif (start is not None ):
497+ _validate_datetime (start )
498+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
499+ {
500+ "type" : "data_row_last_activity_at" ,
501+ "value" : {
502+ "operator" : "GREATER_THAN_OR_EQUAL" ,
503+ "timezone" : timezone ,
504+ "value" : start
505+ }
506+ })
507+ elif (end is not None ):
508+ _validate_datetime (end )
509+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
510+ {
511+ "type" : "data_row_last_activity_at" ,
512+ "value" : {
513+ "operator" : "LESS_THAN_OR_EQUAL" ,
514+ "timezone" : timezone ,
515+ "value" : end
516+ }
517+ })
518+
519+ if _filters .get ('label_created_at' ) is not None :
520+ if timezone is None :
521+ timezone = _get_timezone ()
522+ values = _filters ['label_created_at' ]
523+ start , end = values
524+ if (start is not None and end is not None ):
525+ [_validate_datetime (date ) for date in values ]
526+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
527+ {
528+ "type" : "labeled_at" ,
529+ "value" : {
530+ "operator" : "BETWEEN" ,
531+ "value" : {
532+ "min" : start ,
533+ "max" : end
534+ }
535+ }
536+ })
537+ elif (start is not None ):
538+ _validate_datetime (start )
539+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
540+ {
541+ "type" : "labeled_at" ,
542+ "value" : {
543+ "operator" : "GREATER_THAN_OR_EQUAL" ,
544+ "value" : start
545+ }
546+ })
547+ elif (end is not None ):
548+ _validate_datetime (end )
549+ query_params ["input" ]["filters" ]['searchQuery' ]['query' ].append (
550+ {
551+ "type" : "labeled_at" ,
552+ "value" : {
553+ "operator" : "LESS_THAN_OR_EQUAL" ,
554+ "value" : end
555+ }
556+ })
557+
449558 res = self .client .execute (
450559 create_task_query_str ,
451560 query_params ,
0 commit comments