88import mimetypes
99import os
1010import time
11+ import urllib .parse
1112
1213from google .api_core import retry
1314import requests
2829from labelbox .schema .labeling_frontend import LabelingFrontend
2930from labelbox .schema .model import Model
3031from labelbox .schema .model_run import ModelRun
31- from labelbox .schema .ontology import Ontology , Tool , Classification
32+ from labelbox .schema .ontology import Ontology , Tool , Classification , FeatureSchema
3233from labelbox .schema .organization import Organization
3334from labelbox .schema .user import User
3435from labelbox .schema .project import Project
@@ -55,7 +56,8 @@ def __init__(self,
5556 api_key = None ,
5657 endpoint = 'https://api.labelbox.com/graphql' ,
5758 enable_experimental = False ,
58- app_url = "https://app.labelbox.com" ):
59+ app_url = "https://app.labelbox.com" ,
60+ rest_endpoint = "https://api.labelbox.com/api/v1" ):
5961 """ Creates and initializes a Labelbox Client.
6062
6163 Logging is defaulted to level WARNING. To receive more verbose
@@ -88,6 +90,7 @@ def __init__(self,
8890 logger .info ("Initializing Labelbox client at '%s'" , endpoint )
8991 self .app_url = app_url
9092 self .endpoint = endpoint
93+ self .rest_endpoint = rest_endpoint
9194 self .headers = {
9295 'Accept' : 'application/json' ,
9396 'Content-Type' : 'application/json' ,
@@ -899,6 +902,192 @@ def create_ontology_from_feature_schemas(self,
899902 normalized = {'tools' : tools , 'classifications' : classifications }
900903 return self .create_ontology (name , normalized , media_type )
901904
905+ def delete_unused_feature_schema (self , feature_schema_id : str ) -> None :
906+ """
907+ Deletes a feature schema if it is not used by any ontologies or annotations
908+ Args:
909+ feature_schema_id (str): The id of the feature schema to delete
910+ Example:
911+ >>> client.delete_unused_feature_schema("cleabc1my012ioqvu5anyaabc")
912+ """
913+
914+ endpoint = self .rest_endpoint + "/feature-schemas/" + urllib .parse .quote (
915+ feature_schema_id )
916+ response = requests .delete (
917+ endpoint ,
918+ headers = self .headers ,
919+ )
920+
921+ if response .status_code != requests .codes .no_content :
922+ raise labelbox .exceptions .LabelboxError (
923+ "Failed to delete the feature schema, message: " +
924+ str (response .json ()['message' ]))
925+
926+ def delete_unused_ontology (self , ontology_id : str ) -> None :
927+ """
928+ Deletes an ontology if it is not used by any annotations
929+ Args:
930+ ontology_id (str): The id of the ontology to delete
931+ Example:
932+ >>> client.delete_unused_ontology("cleabc1my012ioqvu5anyaabc")
933+ """
934+
935+ endpoint = self .rest_endpoint + "/ontologies/" + urllib .parse .quote (
936+ ontology_id )
937+ response = requests .delete (
938+ endpoint ,
939+ headers = self .headers ,
940+ )
941+
942+ if response .status_code != requests .codes .no_content :
943+ raise labelbox .exceptions .LabelboxError (
944+ "Failed to delete the ontology, message: " +
945+ str (response .json ()['message' ]))
946+
947+ def update_feature_schema_title (self , feature_schema_id : str ,
948+ title : str ) -> FeatureSchema :
949+ """
950+ Updates a title of a feature schema
951+ Args:
952+ feature_schema_id (str): The id of the feature schema to update
953+ title (str): The new title of the feature schema
954+ Returns:
955+ The updated feature schema
956+ Example:
957+ >>> client.update_feature_schema_title("cleabc1my012ioqvu5anyaabc", "New Title")
958+ """
959+
960+ endpoint = self .rest_endpoint + "/feature-schemas/" + urllib .parse .quote (
961+ feature_schema_id ) + '/definition'
962+ response = requests .patch (
963+ endpoint ,
964+ headers = self .headers ,
965+ json = {"title" : title },
966+ )
967+
968+ if response .status_code == requests .codes .ok :
969+ return self .get_feature_schema (feature_schema_id )
970+ else :
971+ raise labelbox .exceptions .LabelboxError (
972+ "Failed to update the feature schema, message: " +
973+ str (response .json ()['message' ]))
974+
975+ def upsert_feature_schema (self , feature_schema : Dict ) -> FeatureSchema :
976+ """
977+ Upserts a feature schema
978+ Args:
979+ feature_schema: Dict representing the feature schema to upsert
980+ Returns:
981+ The upserted feature schema
982+ Example:
983+ Insert a new feature schema
984+ >>> tool = Tool(name="tool", tool=Tool.Type.BOUNDING_BOX, color="#FF0000")
985+ >>> client.upsert_feature_schema(tool.asdict())
986+ Update an existing feature schema
987+ >>> tool = Tool(feature_schema_id="cleabc1my012ioqvu5anyaabc", name="tool", tool=Tool.Type.BOUNDING_BOX, color="#FF0000")
988+ >>> client.upsert_feature_schema(tool.asdict())
989+ """
990+
991+ feature_schema_id = feature_schema .get (
992+ "featureSchemaId" ) or "new_feature_schema_id"
993+ endpoint = self .rest_endpoint + "/feature-schemas/" + urllib .parse .quote (
994+ feature_schema_id )
995+ response = requests .put (
996+ endpoint ,
997+ headers = self .headers ,
998+ json = {"normalized" : json .dumps (feature_schema )},
999+ )
1000+
1001+ if response .status_code == requests .codes .ok :
1002+ return self .get_feature_schema (response .json ()['schemaId' ])
1003+ else :
1004+ raise labelbox .exceptions .LabelboxError (
1005+ "Failed to upsert the feature schema, message: " +
1006+ str (response .json ()['message' ]))
1007+
1008+ def insert_feature_schema_into_ontology (self , feature_schema_id : str ,
1009+ ontology_id : str ,
1010+ position : int ) -> None :
1011+ """
1012+ Inserts a feature schema into an ontology. If the feature schema is already in the ontology,
1013+ it will be moved to the new position.
1014+ Args:
1015+ feature_schema_id (str): The feature schema id to upsert
1016+ ontology_id (str): The id of the ontology to insert the feature schema into
1017+ position (int): The position number of the feature schema in the ontology
1018+ Example:
1019+ >>> client.insert_feature_schema_into_ontology("cleabc1my012ioqvu5anyaabc", "clefdvwl7abcgefgu3lyvcde", 2)
1020+ """
1021+
1022+ endpoint = self .rest_endpoint + '/ontologies/' + urllib .parse .quote (
1023+ ontology_id ) + "/feature-schemas/" + urllib .parse .quote (
1024+ feature_schema_id )
1025+ response = requests .post (
1026+ endpoint ,
1027+ headers = self .headers ,
1028+ json = {"position" : position },
1029+ )
1030+ if response .status_code != requests .codes .created :
1031+ raise labelbox .exceptions .LabelboxError (
1032+ "Failed to insert the feature schema into the ontology, message: "
1033+ + str (response .json ()['message' ]))
1034+
1035+ def get_unused_ontologies (self , after : str = None ) -> List [str ]:
1036+ """
1037+ Returns a list of unused ontology ids
1038+ Args:
1039+ after (str): The cursor to use for pagination
1040+ Returns:
1041+ A list of unused ontology ids
1042+ Example:
1043+ To get the first page of unused ontology ids (100 at a time)
1044+ >>> client.get_unused_ontologies()
1045+ To get the next page of unused ontology ids
1046+ >>> client.get_unused_ontologies("cleabc1my012ioqvu5anyaabc")
1047+ """
1048+
1049+ endpoint = self .rest_endpoint + "/ontologies/unused"
1050+ response = requests .get (
1051+ endpoint ,
1052+ headers = self .headers ,
1053+ json = {"after" : after },
1054+ )
1055+
1056+ if response .status_code == requests .codes .ok :
1057+ return response .json ()
1058+ else :
1059+ raise labelbox .exceptions .LabelboxError (
1060+ "Failed to get unused ontologies, message: " +
1061+ str (response .json ()['message' ]))
1062+
1063+ def get_unused_feature_schemas (self , after : str = None ) -> List [str ]:
1064+ """
1065+ Returns a list of unused feature schema ids
1066+ Args:
1067+ after (str): The cursor to use for pagination
1068+ Returns:
1069+ A list of unused feature schema ids
1070+ Example:
1071+ To get the first page of unused feature schema ids (100 at a time)
1072+ >>> client.get_unused_feature_schemas()
1073+ To get the next page of unused feature schema ids
1074+ >>> client.get_unused_feature_schemas("cleabc1my012ioqvu5anyaabc")
1075+ """
1076+
1077+ endpoint = self .rest_endpoint + "/feature-schemas/unused"
1078+ response = requests .get (
1079+ endpoint ,
1080+ headers = self .headers ,
1081+ json = {"after" : after },
1082+ )
1083+
1084+ if response .status_code == requests .codes .ok :
1085+ return response .json ()
1086+ else :
1087+ raise labelbox .exceptions .LabelboxError (
1088+ "Failed to get unused feature schemas, message: " +
1089+ str (response .json ()['message' ]))
1090+
9021091 def create_ontology (self , name , normalized , media_type = None ) -> Ontology :
9031092 """
9041093 Creates an ontology from normalized data
0 commit comments