clarifai 10.5.4__py3-none-any.whl → 10.8.0__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.
clarifai/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ __version__ = "10.8.0"
clarifai/client/app.py CHANGED
@@ -4,7 +4,7 @@ from typing import Any, Dict, Generator, List
4
4
 
5
5
  import yaml
6
6
  from clarifai_grpc.grpc.api import resources_pb2, service_pb2
7
- from clarifai_grpc.grpc.api.resources_pb2 import Concept
7
+ from clarifai_grpc.grpc.api.resources_pb2 import Concept, ConceptRelation
8
8
  from clarifai_grpc.grpc.api.status import status_code_pb2
9
9
  from google.protobuf.json_format import MessageToDict
10
10
 
@@ -19,7 +19,9 @@ from clarifai.client.workflow import Workflow
19
19
  from clarifai.constants.model import TRAINABLE_MODEL_TYPES
20
20
  from clarifai.errors import UserError
21
21
  from clarifai.urls.helper import ClarifaiUrlHelper
22
- from clarifai.utils.logging import display_workflow_tree, get_logger
22
+ from clarifai.utils.logging import (display_concept_relations_tree, display_workflow_tree,
23
+ get_logger)
24
+ from clarifai.utils.misc import concept_relations_accumulation
23
25
  from clarifai.workflows.utils import get_yaml_output_info_proto, is_same_yaml_model
24
26
  from clarifai.workflows.validate import validate
25
27
 
@@ -258,6 +260,26 @@ class App(Lister, BaseClient):
258
260
  yield Module.from_auth_helper(
259
261
  auth=self.auth_helper, module_id=imv_info['module_version']['module_id'], **imv_info)
260
262
 
263
+ def get_input_count(self) -> int:
264
+ """Get count of all the inputs in the app.
265
+
266
+ Returns:
267
+ input_count: count of inputs in the app.
268
+
269
+ Example:
270
+ >>> from clarifai.client.app import App
271
+ >>> app = App(app_id="app_id", user_id="user_id")
272
+ >>> input_count = app.get_input_count()
273
+ """
274
+ request = service_pb2.GetInputCountRequest(user_app_id=self.user_app_id)
275
+ response = self._grpc_request(self.STUB.GetInputCount, request)
276
+
277
+ if response.status.code != status_code_pb2.SUCCESS:
278
+ raise Exception(response.status)
279
+ self.logger.info("\nGetting App input Counts\n%s", response.status)
280
+
281
+ return response.counts.processed
282
+
261
283
  def list_concepts(self, page_no: int = None,
262
284
  per_page: int = None) -> Generator[Concept, None, None]:
263
285
  """Lists all the concepts for the app.
@@ -288,6 +310,53 @@ class App(Lister, BaseClient):
288
310
  concept_info['id'] = concept_info.pop('concept_id')
289
311
  yield Concept(**concept_info)
290
312
 
313
+ def search_concept_relations(self,
314
+ concept_id: str = None,
315
+ predicate: str = None,
316
+ page_no: int = None,
317
+ per_page: int = None,
318
+ show_tree: bool = False) -> Generator[ConceptRelation, None, None]:
319
+ """List all the concept relations of the app.
320
+
321
+ Args:
322
+ concept_id (str, optional): The concept ID to filter the concept relations.
323
+ predicate (str, optional): Type of relation to filter the concept relations. For ex : 'hypernym', 'hyponym' or 'synonym'
324
+ page_no (int, optional): The page number to list.
325
+ per_page (int, optional): The number of items per page.
326
+ show_tree (bool, optional): If True, prints out rich tree representation of concept relations.
327
+
328
+ Yields:
329
+ ConceptRelation: ConceptRelations in the app.
330
+
331
+ Example:
332
+ >>> from clarifai.client.app import App
333
+ >>> app = App(app_id="app_id", user_id="user_id")
334
+ >>> all_concept_relations = list(app.search_concept_relations())
335
+
336
+ Note:
337
+ Defaults to 16 per page if page_no is specified and per_page is not specified.
338
+ If both page_no and per_page are None, then lists all the resources.
339
+ """
340
+ request_data = dict(user_app_id=self.user_app_id, concept_id=concept_id, predicate=predicate)
341
+ all_concept_relations_infos = self.list_pages_generator(
342
+ self.STUB.ListConceptRelations,
343
+ service_pb2.ListConceptRelationsRequest,
344
+ request_data,
345
+ per_page=per_page,
346
+ page_no=page_no)
347
+ relations_dict = {}
348
+ for concept_relation_info in all_concept_relations_infos:
349
+ if show_tree:
350
+ current_subject_concept = concept_relation_info['subject_concept']['id']
351
+ current_object_concept = concept_relation_info['object_concept']['id']
352
+ current_predicate = concept_relation_info['predicate']
353
+ relations_dict = concept_relations_accumulation(relations_dict, current_subject_concept,
354
+ current_object_concept, current_predicate)
355
+ concept_relation_info['id'] = concept_relation_info.pop('concept_relation_id')
356
+ yield ConceptRelation(**concept_relation_info)
357
+ if show_tree:
358
+ display_concept_relations_tree(relations_dict)
359
+
291
360
  def list_trainable_model_types(self) -> List[str]:
292
361
  """Lists all the trainable model types.
293
362
 
@@ -352,33 +421,12 @@ class App(Lister, BaseClient):
352
421
 
353
422
  return Model.from_auth_helper(auth=self.auth_helper, **kwargs)
354
423
 
355
- def create_workflow(self,
356
- config_filepath: str,
357
- generate_new_id: bool = False,
358
- display: bool = True) -> Workflow:
359
- """Creates a workflow for the app.
360
-
361
- Args:
362
- config_filepath (str): The path to the yaml workflow config file.
363
- generate_new_id (bool): If True, generate a new workflow ID.
364
- display (bool): If True, display the workflow nodes tree.
365
-
366
- Returns:
367
- Workflow: A Workflow object for the specified workflow config.
368
-
369
- Example:
370
- >>> from clarifai.client.app import App
371
- >>> app = App(app_id="app_id", user_id="user_id")
372
- >>> workflow = app.create_workflow(config_filepath="config.yml")
373
- """
374
- if not os.path.exists(config_filepath):
375
- raise UserError(f"Workflow config file not found at {config_filepath}")
376
-
424
+ def _process_workflow_config(self, config_filepath: str):
377
425
  with open(config_filepath, 'r') as file:
378
- data = yaml.safe_load(file)
426
+ workflow_config = yaml.safe_load(file)
379
427
 
380
- data = validate(data)
381
- workflow = data['workflow']
428
+ workflow_config = validate(workflow_config)
429
+ workflow = workflow_config['workflow']
382
430
 
383
431
  # Get all model objects from the workflow nodes.
384
432
  all_models = []
@@ -420,6 +468,32 @@ class App(Lister, BaseClient):
420
468
  node.node_inputs.append(resources_pb2.NodeInput(node_id=ni['node_id']))
421
469
  nodes.append(node)
422
470
 
471
+ return workflow, nodes
472
+
473
+ def create_workflow(self,
474
+ config_filepath: str,
475
+ generate_new_id: bool = False,
476
+ display: bool = True) -> Workflow:
477
+ """Creates a workflow for the app.
478
+
479
+ Args:
480
+ config_filepath (str): The path to the yaml workflow config file.
481
+ generate_new_id (bool): If True, generate a new workflow ID.
482
+ display (bool): If True, display the workflow nodes tree.
483
+
484
+ Returns:
485
+ Workflow: A Workflow object for the specified workflow config.
486
+
487
+ Example:
488
+ >>> from clarifai.client.app import App
489
+ >>> app = App(app_id="app_id", user_id="user_id")
490
+ >>> workflow = app.create_workflow(config_filepath="config.yml")
491
+ """
492
+ if not os.path.exists(config_filepath):
493
+ raise UserError(f"Workflow config file not found at {config_filepath}")
494
+
495
+ workflow, nodes = self._process_workflow_config(config_filepath)
496
+
423
497
  workflow_id = workflow['id']
424
498
  if generate_new_id:
425
499
  workflow_id = str(uuid.uuid4())
@@ -470,6 +544,60 @@ class App(Lister, BaseClient):
470
544
 
471
545
  return Module.from_auth_helper(auth=self.auth_helper, module_id=module_id, **kwargs)
472
546
 
547
+ def create_concepts(self, concept_ids: List[str], concepts: List[str] = []) -> None:
548
+ """Add concepts to the app.
549
+
550
+ Args:
551
+ concept_ids (List[str]): List of concept IDs of concepts to add to the app.
552
+ concepts (List[str], optional): List of concepts names of concepts to add to the app.
553
+
554
+ Example:
555
+ >>> from clarifai.client.app import App
556
+ >>> app = App(app_id="app_id", user_id="user_id")
557
+ >>> app.add_concepts(concept_ids=["concept_id_1", "concept_id_2", ..])
558
+ """
559
+ if not concepts:
560
+ concepts = list(concept_ids)
561
+ concepts_to_add = [
562
+ resources_pb2.Concept(id=concept_id, name=concept)
563
+ for concept_id, concept in zip(concept_ids, concepts)
564
+ ]
565
+ request = service_pb2.PostConceptsRequest(
566
+ user_app_id=self.user_app_id, concepts=concepts_to_add)
567
+ response = self._grpc_request(self.STUB.PostConcepts, request)
568
+ if response.status.code != status_code_pb2.SUCCESS:
569
+ raise Exception(response.status)
570
+ self.logger.info("\nConcepts added\n%s", response.status)
571
+
572
+ def create_concept_relations(self, subject_concept_id: str, object_concept_ids: List[str],
573
+ predicates: List[str]) -> None:
574
+ """Creates concept relations between concepts of the app.
575
+
576
+ Args:
577
+ subject_concept_id (str): The concept ID of the subject concept.
578
+ object_concept_ids (List[str]): List of concepts IDs of object concepts.
579
+ predicates (List[str]): List of predicates corresponding to each relation between subject and objects.
580
+
581
+ Example:
582
+ >>> from clarifai.client.app import App
583
+ >>> app = App(app_id="app_id", user_id="user_id")
584
+ >>> app.create_concept_relation(subject_concept_id="subject_concept_id", object_concept_ids=["object_concept_id_1", "object_concept_id_2", ..], predicates=["predicate_1", "predicate_2", ..])
585
+ """
586
+ assert len(object_concept_ids) == len(predicates)
587
+ subject_relations = [
588
+ resources_pb2.ConceptRelation(
589
+ object_concept=resources_pb2.Concept(id=object_concept_id), predicate=predicate)
590
+ for object_concept_id, predicate in zip(object_concept_ids, predicates)
591
+ ]
592
+ request = service_pb2.PostConceptRelationsRequest(
593
+ user_app_id=self.user_app_id,
594
+ concept_id=subject_concept_id,
595
+ concept_relations=subject_relations)
596
+ response = self._grpc_request(self.STUB.PostConceptRelations, request)
597
+ if response.status.code != status_code_pb2.SUCCESS:
598
+ raise Exception(response.status)
599
+ self.logger.info("\nConcept Relations created\n%s", response.status)
600
+
473
601
  def dataset(self, dataset_id: str, dataset_version_id: str = None, **kwargs) -> Dataset:
474
602
  """Returns a Dataset object for the existing dataset ID.
475
603
 
@@ -592,6 +720,100 @@ class App(Lister, BaseClient):
592
720
  """
593
721
  return Inputs.from_auth_helper(self.auth_helper)
594
722
 
723
+ def patch_dataset(self, dataset_id: str, action: str = 'merge', **kwargs) -> Dataset:
724
+ """Patches a dataset for the app.
725
+
726
+ Args:
727
+ dataset_id (str): The dataset ID for the dataset to create.
728
+ action (str): The action to perform on the dataset (merge/overwrite/remove).
729
+ **kwargs: Additional keyword arguments to be passed to patch the Dataset.
730
+
731
+ Returns:
732
+ Dataset: A Dataset object for the specified dataset ID.
733
+ """
734
+ if "visibility" in kwargs:
735
+ kwargs["visibility"] = resources_pb2.Visibility(gettable=kwargs["visibility"])
736
+ if "image_url" in kwargs:
737
+ kwargs["image"] = resources_pb2.Image(url=kwargs.pop("image_url"))
738
+ request = service_pb2.PatchDatasetsRequest(
739
+ user_app_id=self.user_app_id,
740
+ datasets=[resources_pb2.Dataset(id=dataset_id, **kwargs)],
741
+ action=action)
742
+ response = self._grpc_request(self.STUB.PatchDatasets, request)
743
+ if response.status.code != status_code_pb2.SUCCESS:
744
+ raise Exception(response.status)
745
+ self.logger.info("\nDataset patched\n%s", response.status)
746
+
747
+ return Dataset.from_auth_helper(self.auth_helper, dataset_id=dataset_id, **kwargs)
748
+
749
+ def patch_model(self, model_id: str, action: str = 'merge', **kwargs) -> Model:
750
+ """Patches a model of the app.
751
+
752
+ Args:
753
+ model_id (str): The model ID of the model to patch.
754
+ action (str): The action to perform on the model (merge/overwrite/remove).
755
+ **kwargs: Additional keyword arguments to be passed to patch the Model.
756
+
757
+ Returns:
758
+ Model: A Model object of the specified model ID.
759
+ """
760
+ if "visibility" in kwargs:
761
+ kwargs["visibility"] = resources_pb2.Visibility(gettable=kwargs["visibility"])
762
+ if "image_url" in kwargs:
763
+ kwargs["image"] = resources_pb2.Image(url=kwargs.pop("image_url"))
764
+ request = service_pb2.PatchModelsRequest(
765
+ user_app_id=self.user_app_id,
766
+ models=[resources_pb2.Model(id=model_id, **kwargs)],
767
+ action=action)
768
+ response = self._grpc_request(self.STUB.PatchModels, request)
769
+ if response.status.code != status_code_pb2.SUCCESS:
770
+ raise Exception(response.status)
771
+ self.logger.info("\nModel %s patched successfully\n%s", model_id, response.status)
772
+ kwargs.update({
773
+ 'model_id': model_id,
774
+ })
775
+
776
+ return Model.from_auth_helper(self.auth_helper, **kwargs)
777
+
778
+ def patch_workflow(self,
779
+ workflow_id: str,
780
+ action: str = 'merge',
781
+ config_filepath: str = None,
782
+ **kwargs) -> Workflow:
783
+ """Patches a workflow of the app.
784
+
785
+ Args:
786
+ workflow_id (str): The Workflow ID of the workflow to patch.
787
+ action (str): The action to perform on the workflow (merge/overwrite/remove).
788
+ config_filepath (str): The path to the yaml workflow config file.
789
+ **kwargs: Additional keyword arguments to be passed to patch the Workflow.
790
+
791
+ Returns:
792
+ Workflow: A Workflow object of the specified workflow ID.
793
+ """
794
+ if config_filepath:
795
+ if not os.path.exists(config_filepath):
796
+ raise UserError(f"Workflow config file not found at {config_filepath}")
797
+ _, kwargs['nodes'] = self._process_workflow_config(config_filepath)
798
+ if "visibility" in kwargs:
799
+ kwargs["visibility"] = resources_pb2.Visibility(gettable=kwargs["visibility"])
800
+ if "image_url" in kwargs:
801
+ kwargs["image"] = resources_pb2.Image(url=kwargs.pop("image_url"))
802
+
803
+ request = service_pb2.PatchWorkflowsRequest(
804
+ user_app_id=self.user_app_id,
805
+ workflows=[resources_pb2.Workflow(id=workflow_id, **kwargs)],
806
+ action=action)
807
+ response = self._grpc_request(self.STUB.PatchWorkflows, request)
808
+ if response.status.code != status_code_pb2.SUCCESS:
809
+ raise Exception(response.status)
810
+ self.logger.info("\nWorkflow patched\n%s", response.status)
811
+ kwargs.update({
812
+ 'workflow_id': workflow_id,
813
+ })
814
+
815
+ return Workflow.from_auth_helper(self.auth_helper, **kwargs)
816
+
595
817
  def delete_dataset(self, dataset_id: str) -> None:
596
818
  """Deletes an dataset for the user.
597
819
 
@@ -665,6 +887,31 @@ class App(Lister, BaseClient):
665
887
  raise Exception(response.status)
666
888
  self.logger.info("\nModule Deleted\n%s", response.status)
667
889
 
890
+ def delete_concept_relations(self, concept_id: str,
891
+ concept_relation_ids: List[str] = []) -> None:
892
+ """Delete concept relations of a concept of the app.
893
+
894
+ Args:
895
+ concept_id (str): The concept ID of the concept to delete relations for.
896
+ concept_relation_ids (List[str], optional): List of concept relation IDs of concept relations.
897
+
898
+ Example:
899
+ >>> from clarifai.client.app import App
900
+ >>> app = App(app_id="app_id", user_id="user_id")
901
+ >>> app.delete_concept_relations(concept_id="concept_id", concept_relation_ids=["concept_relation_id_1", "concept_relation_id_2", ..])
902
+ """
903
+ if not concept_relation_ids:
904
+ concept_relation_ids = [
905
+ concept_relation.id
906
+ for concept_relation in list(self.search_concept_relations(concept_id=concept_id))
907
+ ]
908
+ request = service_pb2.DeleteConceptRelationsRequest(
909
+ user_app_id=self.user_app_id, concept_id=concept_id, ids=concept_relation_ids)
910
+ response = self._grpc_request(self.STUB.DeleteConceptRelations, request)
911
+ if response.status.code != status_code_pb2.SUCCESS:
912
+ raise Exception(response.status)
913
+ self.logger.info("\nConcept Relations Deleted\n%s", response.status)
914
+
668
915
  def search(self, **kwargs) -> Model:
669
916
  """Returns a Search object for the user and app ID.
670
917
 
clarifai/client/base.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from datetime import datetime
2
2
  from typing import Any, Callable
3
3
 
4
+ from clarifai_grpc.grpc.api import resources_pb2
4
5
  from google.protobuf import struct_pb2
5
6
  from google.protobuf.timestamp_pb2 import Timestamp
6
7
  from google.protobuf.wrappers_pb2 import BoolValue
@@ -176,6 +177,10 @@ class BaseClient:
176
177
  continue
177
178
  elif key == 'size':
178
179
  value = int(value)
180
+ elif key == 'image_info':
181
+ value = resources_pb2.ImageInfo(**value)
182
+ elif key == 'hosted_image_info':
183
+ continue
179
184
  elif key in ['metadata']:
180
185
  if isinstance(value, dict) and value != {}:
181
186
  value_s = struct_pb2.Struct()
clarifai/client/model.py CHANGED
@@ -11,6 +11,7 @@ from clarifai_grpc.grpc.api.resources_pb2 import Input
11
11
  from clarifai_grpc.grpc.api.status import status_code_pb2
12
12
  from google.protobuf.json_format import MessageToDict
13
13
  from google.protobuf.struct_pb2 import Struct, Value
14
+ from requests.adapters import HTTPAdapter, Retry
14
15
  from tqdm import tqdm
15
16
 
16
17
  from clarifai.client.base import BaseClient
@@ -1295,7 +1296,11 @@ class Model(Lister, BaseClient):
1295
1296
  model_export_url = get_model_export_response.export.url
1296
1297
  model_export_file_size = get_model_export_response.export.size
1297
1298
 
1298
- response = requests.get(model_export_url, stream=True)
1299
+ session = requests.Session()
1300
+ retries = Retry(total=10, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
1301
+ session.mount('https://', HTTPAdapter(max_retries=retries))
1302
+ session.headers.update({'Authorization': self.metadata[0][1]})
1303
+ response = session.get(model_export_url, stream=True)
1299
1304
  response.raise_for_status()
1300
1305
 
1301
1306
  with open(local_filepath, 'wb') as f:
clarifai/client/search.py CHANGED
@@ -277,7 +277,8 @@ class Search(Lister, BaseClient):
277
277
  Get successful inputs of type image or text
278
278
  >>> from clarifai.client.search import Search
279
279
  >>> search = Search(user_id='user_id', app_id='app_id', top_k=10, metric='cosine')
280
- >>> res = search.query(filters=[{'input_types': ['image', 'text']}, {'input_status_code': 30000}])
280
+ # This performs OR operation on input_types and input_status_code
281
+ >>> res = search.query(filters=[{'input_types': ['image', 'text'], 'input_status_code': 30000}])
281
282
 
282
283
  Vector search over inputs
283
284
  >>> from clarifai.client.search import Search
@@ -298,43 +299,27 @@ class Search(Lister, BaseClient):
298
299
  raise UserError(f"Invalid rank or filter input: {err}")
299
300
 
300
301
  # For each rank, create a Rank proto message
301
- rank_annot_proto = []
302
+ # For ranks it only allows resources_pb2.Annotation proto, so no need of splitting protos into annotation and input.
303
+ all_ranks = []
302
304
  for rank_dict in ranks:
303
- rank_annot_proto.append(self._get_annot_proto(**rank_dict))
304
- all_ranks = [resources_pb2.Rank(annotation=rank_annot) for rank_annot in rank_annot_proto]
305
-
306
- # Calls PostInputsSearches for annotation ranks, input filters
307
- if any(["input" in k for k in filters[0].keys()]):
308
- filters_input_proto = []
309
- for filter_dict in filters:
310
- filters_input_proto.append(self._get_input_proto(**filter_dict))
311
- all_filters = [
312
- resources_pb2.Filter(input=filter_input) for filter_input in filters_input_proto
313
- ]
314
- request_data = dict(
315
- user_app_id=self.user_app_id,
316
- searches=[
317
- resources_pb2.Search(
318
- query=resources_pb2.Query(ranks=all_ranks, filters=all_filters),
319
- algorithm=self.algorithm,
320
- metric=self.metric_distance)
321
- ])
322
- if self.pagination:
323
- return self._list_all_pages_generator(self.STUB.PostInputsSearches,
324
- service_pb2.PostInputsSearchesRequest, request_data,
325
- page_no, per_page)
326
- return self._list_topk_generator(self.STUB.PostInputsSearches,
327
- service_pb2.PostInputsSearchesRequest, request_data)
328
-
329
- # Calls PostAnnotationsSearches for annotation ranks, filters
330
- filters_annot_proto = []
331
- for filter_dict in filters:
332
- filters_annot_proto.append(self._get_annot_proto(**filter_dict))
333
-
334
- all_filters = [
335
- resources_pb2.Filter(annotation=filter_annot) for filter_annot in filters_annot_proto
336
- ]
337
-
305
+ rank_annot_proto = self._get_annot_proto(**rank_dict)
306
+ all_ranks.append(resources_pb2.Rank(annotation=rank_annot_proto))
307
+
308
+ all_filters = []
309
+ # check for filters which is compatible with input proto
310
+ for each_filter in filters:
311
+ input_dict = {
312
+ key: each_filter.pop(key)
313
+ for key in ['input_types', 'input_dataset_ids', 'input_status_code']
314
+ if key in each_filter
315
+ }
316
+
317
+ all_filters.append(
318
+ resources_pb2.Filter(
319
+ annotation=self._get_annot_proto(**each_filter),
320
+ input=self._get_input_proto(**input_dict)))
321
+
322
+ # Create a PostInputsSearchesRequest proto message
338
323
  request_data = dict(
339
324
  user_app_id=self.user_app_id,
340
325
  searches=[
@@ -343,9 +328,10 @@ class Search(Lister, BaseClient):
343
328
  algorithm=self.algorithm,
344
329
  metric=self.metric_distance)
345
330
  ])
331
+ # Calls PostInputsSearches for annotation ranks, input filters
346
332
  if self.pagination:
347
- return self._list_all_pages_generator(self.STUB.PostAnnotationsSearches,
348
- service_pb2.PostAnnotationsSearchesRequest,
349
- request_data, page_no, per_page)
350
- return self._list_topk_generator(self.STUB.PostAnnotationsSearches,
351
- service_pb2.PostAnnotationsSearchesRequest, request_data)
333
+ return self._list_all_pages_generator(self.STUB.PostInputsSearches,
334
+ service_pb2.PostInputsSearchesRequest, request_data,
335
+ page_no, per_page)
336
+ return self._list_topk_generator(self.STUB.PostInputsSearches,
337
+ service_pb2.PostInputsSearchesRequest, request_data)
clarifai/client/user.py CHANGED
@@ -3,6 +3,7 @@ from typing import Any, Dict, Generator, List
3
3
  from clarifai_grpc.grpc.api import resources_pb2, service_pb2
4
4
  from clarifai_grpc.grpc.api.status import status_code_pb2
5
5
  from google.protobuf.json_format import MessageToDict
6
+ from google.protobuf.wrappers_pb2 import BoolValue
6
7
 
7
8
  from clarifai.client.app import App
8
9
  from clarifai.client.base import BaseClient
@@ -222,6 +223,38 @@ class User(Lister, BaseClient):
222
223
 
223
224
  return dict(self.auth_helper, check_runner_exists=False, **kwargs)
224
225
 
226
+ def patch_app(self, app_id: str, action: str = 'overwrite', **kwargs) -> App:
227
+ """Patch an app for the user.
228
+
229
+ Args:
230
+ app_id (str): The app ID for the app to patch.
231
+ action (str): The action to perform on the app (overwrite/remove).
232
+ **kwargs: Additional keyword arguments to be passed to patch the App.
233
+
234
+ Returns:
235
+ App: Patched App object for the specified app ID.
236
+ """
237
+ if "base_workflow" in kwargs:
238
+ kwargs["default_workflow"] = resources_pb2.Workflow(
239
+ id=kwargs.pop("base_workflow"), app_id="main", user_id="clarifai")
240
+ if "visibility" in kwargs:
241
+ kwargs["visibility"] = resources_pb2.Visibility(gettable=kwargs["visibility"])
242
+ if "image_url" in kwargs:
243
+ kwargs["image"] = resources_pb2.Image(url=kwargs.pop("image_url"))
244
+ if "is_template" in kwargs:
245
+ kwargs["is_template"] = BoolValue(value=kwargs["is_template"])
246
+ request = service_pb2.PatchAppRequest(
247
+ user_app_id=resources_pb2.UserAppIDSet(user_id=self.id, app_id=app_id),
248
+ app=resources_pb2.App(id=app_id, **kwargs),
249
+ action=action,
250
+ reindex=False)
251
+ response = self._grpc_request(self.STUB.PatchApp, request)
252
+ if response.status.code != status_code_pb2.SUCCESS:
253
+ raise Exception(response.status)
254
+ self.logger.info("\nApp patched\n%s", response.status)
255
+
256
+ return App.from_auth_helper(auth=self.auth_helper, app_id=app_id)
257
+
225
258
  def delete_app(self, app_id: str) -> None:
226
259
  """Deletes an app for the user.
227
260
 
@@ -196,7 +196,7 @@ class InputAnnotationDownloader:
196
196
 
197
197
  if data_dict.get("concepts") or data_dict.get("regions"):
198
198
  file_name = os.path.join(split, "annotations", input_.id + ".json")
199
- annot_data = data_dict.get("concepts") or data_dict.get("regions")
199
+ annot_data = data_dict.get("regions", []) + data_dict.get("concepts")
200
200
 
201
201
  self._save_annotation_to_archive(new_archive, annot_data, file_name)
202
202
  self.num_annotations += 1
clarifai/utils/logging.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from collections import defaultdict
3
- from typing import Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Optional, Union
4
4
 
5
5
  from rich import print as rprint
6
6
  from rich.console import Console
@@ -138,3 +138,16 @@ def process_log_files(log_file_path: str,) -> tuple:
138
138
  return [], []
139
139
 
140
140
  return duplicate_input_ids, failed_input_ids
141
+
142
+
143
+ def display_concept_relations_tree(relations_dict: Dict[str, Any]) -> None:
144
+ """Print all the concept relations of the app in rich tree format.
145
+
146
+ Args:
147
+ relations_dict (dict): A dict of concept relations info.
148
+ """
149
+ for parent, children in relations_dict.items():
150
+ tree = Tree(parent)
151
+ for child in children:
152
+ tree.add(child)
153
+ rprint(tree)
clarifai/utils/misc.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import List
2
+ from typing import Any, Dict, List
3
3
 
4
4
  from clarifai.errors import UserError
5
5
 
@@ -46,3 +46,26 @@ def get_from_env(key: str, env_key: str) -> str:
46
46
  raise UserError(f"Did not find `{key}`, please add an environment variable"
47
47
  f" `{env_key}` which contains it, or pass"
48
48
  f" `{key}` as a named parameter.")
49
+
50
+
51
+ def concept_relations_accumulation(relations_dict: Dict[str, Any], subject_concept: str,
52
+ object_concept: str, predicate: str) -> Dict[str, Any]:
53
+ """Append the concept relation to relations dict based on its predicate.
54
+
55
+ Args:
56
+ relations_dict (dict): A dict of concept relations info.
57
+ """
58
+ if predicate == 'hyponym':
59
+ if object_concept in relations_dict:
60
+ relations_dict[object_concept].append(subject_concept)
61
+ else:
62
+ relations_dict[object_concept] = [subject_concept]
63
+ elif predicate == 'hypernym':
64
+ if subject_concept in relations_dict:
65
+ relations_dict[subject_concept].append(object_concept)
66
+ else:
67
+ relations_dict[subject_concept] = [object_concept]
68
+ else:
69
+ relations_dict[object_concept] = []
70
+ relations_dict[subject_concept] = []
71
+ return relations_dict
clarifai/versions.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import os
2
+ from clarifai import __version__
2
3
 
3
- CLIENT_VERSION = "10.5.4"
4
+ CLIENT_VERSION = __version__
4
5
  OS_VER = os.sys.platform
5
6
  PYTHON_VERSION = '.'.join(
6
7
  map(str, [os.sys.version_info.major, os.sys.version_info.minor, os.sys.version_info.micro]))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clarifai
3
- Version: 10.5.4
3
+ Version: 10.8.0
4
4
  Summary: Clarifai Python SDK
5
5
  Home-page: https://github.com/Clarifai/clarifai-python
6
6
  Author: Clarifai
@@ -20,7 +20,7 @@ Classifier: Operating System :: OS Independent
20
20
  Requires-Python: >=3.8
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: clarifai-grpc >=10.5.0
23
+ Requires-Dist: clarifai-grpc >=10.8.0
24
24
  Requires-Dist: numpy >=1.22.0
25
25
  Requires-Dist: tqdm >=4.65.0
26
26
  Requires-Dist: tritonclient >=2.34.0
@@ -1,17 +1,17 @@
1
- clarifai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1
+ clarifai/__init__.py,sha256=x_bDnpR_W5o1ETeLzxk1ahCDd3RSOmf0Fs_F69i8AEo,23
2
2
  clarifai/cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  clarifai/errors.py,sha256=RwzTajwds51wLD0MVlMC5kcpBnzRpreDLlazPSBZxrg,2605
4
- clarifai/versions.py,sha256=_ce_y-64EmSRIfWAk92G7sa7D3E2Wk4HRM5Sh2iBBTo,186
4
+ clarifai/versions.py,sha256=jctnczzfGk_S3EnVqb2FjRKfSREkNmvNEwAAa_VoKiQ,222
5
5
  clarifai/client/__init__.py,sha256=xI1U0l5AZdRThvQAXCLsd9axxyFzXXJ22m8LHqVjQRU,662
6
- clarifai/client/app.py,sha256=br3C2paGmWzKu4eG0kUN1m8kDPKqpGPq28f7Xiy2v54,27329
7
- clarifai/client/base.py,sha256=wStnn_gS6sYo36OlYRzEkOFShXOQg3DKUp8i6DomAxQ,7178
6
+ clarifai/client/app.py,sha256=mAUtG5ucNC_MJdyDZi99ttP7R3Tffm6Qp7DahOYo_fE,38531
7
+ clarifai/client/base.py,sha256=JXbbjg2CXo8rOdw-XgKWWtLVAhPv3OZua5LFT5w4U2Q,7380
8
8
  clarifai/client/dataset.py,sha256=XX-J-9Ict1CQrEycq-JbdxUTuucSgLeDSvnlHE1ucQY,29903
9
9
  clarifai/client/input.py,sha256=ZLqa1jGx4NgCbunOTpJxCq4lDQ5xAf4GQ0rsZY8AHCM,44456
10
10
  clarifai/client/lister.py,sha256=03KGMvs5RVyYqxLsSrWhNc34I8kiF1Ph0NeyEwu7nMU,2082
11
- clarifai/client/model.py,sha256=Q0XEOWaZvSFPx7cLp4xJcwV5SVD1iU_6-DdDJmF-Hfk,72623
11
+ clarifai/client/model.py,sha256=TsIiId2a1eqR8Ct83f8KbqBsxiDT9XNPxqLSQtr3i1A,72930
12
12
  clarifai/client/module.py,sha256=360JaOasX0DZCNE_Trj0LNTr-T_tUDZLfGpz0CdIi78,4248
13
- clarifai/client/search.py,sha256=iwZqwuEodbjIOEPMIjpic8caFGg3u51RK816pr-574o,14964
14
- clarifai/client/user.py,sha256=EQTeotfYTNedGcbTICYOUJqKgWhfVHvaMRTJ1hdoIdQ,10372
13
+ clarifai/client/search.py,sha256=GaPWN6JmTQGZaCHr6U1yv0zqR6wKFl7i9IVLg2ul1CI,14254
14
+ clarifai/client/user.py,sha256=Fr3vDEHieqD7HRRKnlp9h-AIX4AuYBirRYtG4KLwSe8,11836
15
15
  clarifai/client/workflow.py,sha256=e3axkhU6c6WcxK9P5tgmnV464k-afslSzsSXx6nSMgA,10560
16
16
  clarifai/client/auth/__init__.py,sha256=7EwR0NrozkAUwpUnCsqXvE_p0wqx_SelXlSpKShKJK0,136
17
17
  clarifai/client/auth/helper.py,sha256=hqwI7Zlsvivc-O9aAdtxyJT3zkpuMvbxjRaiCTsWYGk,14183
@@ -25,7 +25,7 @@ clarifai/constants/search.py,sha256=yYEqTaFg-KdnpJE_Ytp-EPVHIIC395iNtZrpVlLIf4o,
25
25
  clarifai/constants/workflow.py,sha256=cECq1xdvf44MCdtK2AbkiuuwhyL-6OWZdQfYbsLKy_o,33
26
26
  clarifai/datasets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  clarifai/datasets/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- clarifai/datasets/export/inputs_annotations.py,sha256=Mzo7UMoxuOvgLSmkU9e6WvSCGLPIa6NXKuQHyesVLG8,9737
28
+ clarifai/datasets/export/inputs_annotations.py,sha256=X1Y1XjnJrPrlwqv2aM4a1mBPUpWgEpz5stq2Vq-mLaM,9740
29
29
  clarifai/datasets/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  clarifai/datasets/upload/base.py,sha256=IP4sdBRfThk2l0W1rDWciFrAJnKwVsM-gu4zEslJ2_E,2198
31
31
  clarifai/datasets/upload/features.py,sha256=oq0PGpAw8LEafiSkdMMl0yn-NJeZ7K_CKzpJ71b0H40,1731
@@ -96,8 +96,8 @@ clarifai/schema/search.py,sha256=JjTi8ammJgZZ2OGl4K6tIA4zEJ1Fr2ASZARXavI1j5c,244
96
96
  clarifai/urls/helper.py,sha256=tjoMGGHuWX68DUB0pk4MEjrmFsClUAQj2jmVEM_Sy78,4751
97
97
  clarifai/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  clarifai/utils/constants.py,sha256=MG_iHnSwNEyUZOpvsrTicNwaT4CIjmlK_Ixk_qqEX8g,142
99
- clarifai/utils/logging.py,sha256=sR_pRfSHZvHb08KcduAGz9oFrzioWqqCDZ3mHB8rpUY,4601
100
- clarifai/utils/misc.py,sha256=GznzquXXFt8J9qzMWtTJPFWCSc5QTs_ZBldW1mXCZzE,1285
99
+ clarifai/utils/logging.py,sha256=ubo_knWpCmlXmt6uKAYppIQef1IcwPUJdqeqwjxe3RI,4977
100
+ clarifai/utils/misc.py,sha256=WV3KGM5_MwHySVthjUK4O93x6F_kE1h3-xT4zE4EvnU,2150
101
101
  clarifai/utils/model_train.py,sha256=Mndqy5GNu7kjQHjDyNVyamL0hQFLGSHcWhOuPyOvr1w,8005
102
102
  clarifai/utils/evaluation/__init__.py,sha256=PYkurUrXrGevByj7RFb6CoU1iC7fllyQSfnnlo9WnY8,69
103
103
  clarifai/utils/evaluation/helpers.py,sha256=d_dcASRI_lhsHIRukAF1S-w7XazLpK9y6E_ug3l50t4,18440
@@ -107,9 +107,9 @@ clarifai/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
107
107
  clarifai/workflows/export.py,sha256=vICRhIreqDSShxLKjHNM2JwzKsf1B4fdXB0ciMcA70k,1945
108
108
  clarifai/workflows/utils.py,sha256=nGeB_yjVgUO9kOeKTg4OBBaBz-AwXI3m-huSVj-9W18,1924
109
109
  clarifai/workflows/validate.py,sha256=yJq03MaJqi5AK3alKGJJBR89xmmjAQ31sVufJUiOqY8,2556
110
- clarifai-10.5.4.dist-info/LICENSE,sha256=mUqF_d12-qE2n41g7C5_sq-BMLOcj6CNN-jevr15YHU,555
111
- clarifai-10.5.4.dist-info/METADATA,sha256=y6nmwxvlO3GuzTeIyHPpEPagkFL_lOr_v_ztEIwc1Pg,19372
112
- clarifai-10.5.4.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
113
- clarifai-10.5.4.dist-info/entry_points.txt,sha256=qZOr_MIPG0dBBE1zringDJS_wXNGTAA_SQ-zcbmDHOw,82
114
- clarifai-10.5.4.dist-info/top_level.txt,sha256=wUMdCQGjkxaynZ6nZ9FAnvBUCgp5RJUVFSy2j-KYo0s,9
115
- clarifai-10.5.4.dist-info/RECORD,,
110
+ clarifai-10.8.0.dist-info/LICENSE,sha256=mUqF_d12-qE2n41g7C5_sq-BMLOcj6CNN-jevr15YHU,555
111
+ clarifai-10.8.0.dist-info/METADATA,sha256=4oERpF1zh5aLZU6ZUh4jh6XqXyl-4n9pnle4KlbwBOM,19372
112
+ clarifai-10.8.0.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
113
+ clarifai-10.8.0.dist-info/entry_points.txt,sha256=qZOr_MIPG0dBBE1zringDJS_wXNGTAA_SQ-zcbmDHOw,82
114
+ clarifai-10.8.0.dist-info/top_level.txt,sha256=wUMdCQGjkxaynZ6nZ9FAnvBUCgp5RJUVFSy2j-KYo0s,9
115
+ clarifai-10.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (74.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5