clarifai 9.8.1__py3-none-any.whl → 9.9.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.
Files changed (124) hide show
  1. clarifai/client/app.py +115 -14
  2. clarifai/client/base.py +11 -4
  3. clarifai/client/dataset.py +8 -3
  4. clarifai/client/input.py +34 -28
  5. clarifai/client/model.py +71 -2
  6. clarifai/client/module.py +4 -2
  7. clarifai/client/runner.py +161 -0
  8. clarifai/client/search.py +173 -0
  9. clarifai/client/user.py +110 -4
  10. clarifai/client/workflow.py +27 -2
  11. clarifai/constants/search.py +2 -0
  12. clarifai/datasets/upload/loaders/xview_detection.py +1 -1
  13. clarifai/models/model_serving/README.md +3 -3
  14. clarifai/models/model_serving/cli/deploy_cli.py +2 -3
  15. clarifai/models/model_serving/cli/repository.py +3 -5
  16. clarifai/models/model_serving/constants.py +1 -5
  17. clarifai/models/model_serving/docs/custom_config.md +5 -6
  18. clarifai/models/model_serving/docs/dependencies.md +5 -10
  19. clarifai/models/model_serving/examples/image_classification/age_vit/requirements.txt +1 -0
  20. clarifai/models/model_serving/examples/text_classification/xlm-roberta/requirements.txt +1 -0
  21. clarifai/models/model_serving/examples/text_to_image/sd-v1.5/requirements.txt +1 -0
  22. clarifai/models/model_serving/examples/text_to_text/bart-summarize/requirements.txt +1 -0
  23. clarifai/models/model_serving/examples/visual_detection/yolov5x/requirements.txt +1 -1
  24. clarifai/models/model_serving/examples/visual_embedding/vit-base/requirements.txt +1 -0
  25. clarifai/models/model_serving/examples/visual_segmentation/segformer-b2/requirements.txt +1 -0
  26. clarifai/models/model_serving/model_config/__init__.py +2 -0
  27. clarifai/models/model_serving/model_config/config.py +298 -0
  28. clarifai/models/model_serving/model_config/model_types_config/text-classifier.yaml +18 -0
  29. clarifai/models/model_serving/model_config/model_types_config/text-embedder.yaml +18 -0
  30. clarifai/models/model_serving/model_config/model_types_config/text-to-image.yaml +18 -0
  31. clarifai/models/model_serving/model_config/model_types_config/text-to-text.yaml +18 -0
  32. clarifai/models/model_serving/model_config/model_types_config/visual-classifier.yaml +18 -0
  33. clarifai/models/model_serving/model_config/model_types_config/visual-detector.yaml +28 -0
  34. clarifai/models/model_serving/model_config/model_types_config/visual-embedder.yaml +18 -0
  35. clarifai/models/model_serving/model_config/model_types_config/visual-segmenter.yaml +18 -0
  36. clarifai/models/model_serving/model_config/serializer.py +1 -1
  37. clarifai/models/model_serving/models/default_test.py +22 -21
  38. clarifai/models/model_serving/models/output.py +2 -2
  39. clarifai/models/model_serving/pb_model_repository.py +2 -5
  40. clarifai/runners/__init__.py +0 -0
  41. clarifai/runners/example.py +33 -0
  42. clarifai/schema/search.py +60 -0
  43. clarifai/utils/logging.py +53 -3
  44. clarifai/versions.py +1 -1
  45. clarifai/workflows/__init__.py +0 -0
  46. clarifai/workflows/export.py +68 -0
  47. clarifai/workflows/utils.py +59 -0
  48. clarifai/workflows/validate.py +67 -0
  49. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/METADATA +20 -2
  50. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/RECORD +102 -86
  51. clarifai_utils/client/app.py +115 -14
  52. clarifai_utils/client/base.py +11 -4
  53. clarifai_utils/client/dataset.py +8 -3
  54. clarifai_utils/client/input.py +34 -28
  55. clarifai_utils/client/model.py +71 -2
  56. clarifai_utils/client/module.py +4 -2
  57. clarifai_utils/client/runner.py +161 -0
  58. clarifai_utils/client/search.py +173 -0
  59. clarifai_utils/client/user.py +110 -4
  60. clarifai_utils/client/workflow.py +27 -2
  61. clarifai_utils/constants/search.py +2 -0
  62. clarifai_utils/datasets/upload/loaders/xview_detection.py +1 -1
  63. clarifai_utils/models/model_serving/README.md +3 -3
  64. clarifai_utils/models/model_serving/cli/deploy_cli.py +2 -3
  65. clarifai_utils/models/model_serving/cli/repository.py +3 -5
  66. clarifai_utils/models/model_serving/constants.py +1 -5
  67. clarifai_utils/models/model_serving/docs/custom_config.md +5 -6
  68. clarifai_utils/models/model_serving/docs/dependencies.md +5 -10
  69. clarifai_utils/models/model_serving/examples/image_classification/age_vit/requirements.txt +1 -0
  70. clarifai_utils/models/model_serving/examples/text_classification/xlm-roberta/requirements.txt +1 -0
  71. clarifai_utils/models/model_serving/examples/text_to_image/sd-v1.5/requirements.txt +1 -0
  72. clarifai_utils/models/model_serving/examples/text_to_text/bart-summarize/requirements.txt +1 -0
  73. clarifai_utils/models/model_serving/examples/visual_detection/yolov5x/requirements.txt +1 -1
  74. clarifai_utils/models/model_serving/examples/visual_embedding/vit-base/requirements.txt +1 -0
  75. clarifai_utils/models/model_serving/examples/visual_segmentation/segformer-b2/requirements.txt +1 -0
  76. clarifai_utils/models/model_serving/model_config/__init__.py +2 -0
  77. clarifai_utils/models/model_serving/model_config/config.py +298 -0
  78. clarifai_utils/models/model_serving/model_config/model_types_config/text-classifier.yaml +18 -0
  79. clarifai_utils/models/model_serving/model_config/model_types_config/text-embedder.yaml +18 -0
  80. clarifai_utils/models/model_serving/model_config/model_types_config/text-to-image.yaml +18 -0
  81. clarifai_utils/models/model_serving/model_config/model_types_config/text-to-text.yaml +18 -0
  82. clarifai_utils/models/model_serving/model_config/model_types_config/visual-classifier.yaml +18 -0
  83. clarifai_utils/models/model_serving/model_config/model_types_config/visual-detector.yaml +28 -0
  84. clarifai_utils/models/model_serving/model_config/model_types_config/visual-embedder.yaml +18 -0
  85. clarifai_utils/models/model_serving/model_config/model_types_config/visual-segmenter.yaml +18 -0
  86. clarifai_utils/models/model_serving/model_config/serializer.py +1 -1
  87. clarifai_utils/models/model_serving/models/default_test.py +22 -21
  88. clarifai_utils/models/model_serving/models/output.py +2 -2
  89. clarifai_utils/models/model_serving/pb_model_repository.py +2 -5
  90. clarifai_utils/runners/__init__.py +0 -0
  91. clarifai_utils/runners/example.py +33 -0
  92. clarifai_utils/schema/search.py +60 -0
  93. clarifai_utils/utils/logging.py +53 -3
  94. clarifai_utils/versions.py +1 -1
  95. clarifai_utils/workflows/__init__.py +0 -0
  96. clarifai_utils/workflows/export.py +68 -0
  97. clarifai_utils/workflows/utils.py +59 -0
  98. clarifai_utils/workflows/validate.py +67 -0
  99. clarifai/models/model_serving/envs/triton_conda-cp3.8-torch1.13.1-19f97078.yaml +0 -35
  100. clarifai/models/model_serving/envs/triton_conda-cp3.8-torch2.0.0-ce980f28.yaml +0 -51
  101. clarifai/models/model_serving/examples/image_classification/age_vit/triton_conda.yaml +0 -1
  102. clarifai/models/model_serving/examples/text_classification/xlm-roberta/triton_conda.yaml +0 -1
  103. clarifai/models/model_serving/examples/text_to_image/sd-v1.5/triton_conda.yaml +0 -1
  104. clarifai/models/model_serving/examples/text_to_text/bart-summarize/triton_conda.yaml +0 -1
  105. clarifai/models/model_serving/examples/visual_detection/yolov5x/triton_conda.yaml +0 -1
  106. clarifai/models/model_serving/examples/visual_embedding/vit-base/triton_conda.yaml +0 -1
  107. clarifai/models/model_serving/examples/visual_segmentation/segformer-b2/triton_conda.yaml +0 -1
  108. clarifai/models/model_serving/model_config/deploy.py +0 -75
  109. clarifai/models/model_serving/model_config/triton_config.py +0 -226
  110. clarifai_utils/models/model_serving/envs/triton_conda-cp3.8-torch1.13.1-19f97078.yaml +0 -35
  111. clarifai_utils/models/model_serving/envs/triton_conda-cp3.8-torch2.0.0-ce980f28.yaml +0 -51
  112. clarifai_utils/models/model_serving/examples/image_classification/age_vit/triton_conda.yaml +0 -1
  113. clarifai_utils/models/model_serving/examples/text_classification/xlm-roberta/triton_conda.yaml +0 -1
  114. clarifai_utils/models/model_serving/examples/text_to_image/sd-v1.5/triton_conda.yaml +0 -1
  115. clarifai_utils/models/model_serving/examples/text_to_text/bart-summarize/triton_conda.yaml +0 -1
  116. clarifai_utils/models/model_serving/examples/visual_detection/yolov5x/triton_conda.yaml +0 -1
  117. clarifai_utils/models/model_serving/examples/visual_embedding/vit-base/triton_conda.yaml +0 -1
  118. clarifai_utils/models/model_serving/examples/visual_segmentation/segformer-b2/triton_conda.yaml +0 -1
  119. clarifai_utils/models/model_serving/model_config/deploy.py +0 -75
  120. clarifai_utils/models/model_serving/model_config/triton_config.py +0 -226
  121. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/LICENSE +0 -0
  122. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/WHEEL +0 -0
  123. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/entry_points.txt +0 -0
  124. {clarifai-9.8.1.dist-info → clarifai-9.9.0.dist-info}/top_level.txt +0 -0
@@ -19,8 +19,7 @@ import os
19
19
  from pathlib import Path
20
20
  from typing import Callable, Type
21
21
 
22
- from .model_config.serializer import Serializer
23
- from .model_config.triton_config import TritonModelConfig
22
+ from .model_config import Serializer, TritonModelConfig
24
23
  from .models import inference, pb_model, test
25
24
 
26
25
 
@@ -79,11 +78,9 @@ class TritonModelRepository:
79
78
  pass
80
79
  else:
81
80
  continue
82
- # gen requirements & conda yaml
81
+ # gen requirements
83
82
  with open(os.path.join(repository_path, "requirements.txt"), "w") as f:
84
83
  f.write("clarifai>9.5.3\ntritonclient[all]") # for model upload utils
85
- with open(os.path.join(repository_path, "triton_conda.yaml"), "w") as conda_env:
86
- conda_env.write("name: triton_conda-cp3.8-torch1.13.1-19f97078")
87
84
 
88
85
  if not os.path.isdir(model_version_path):
89
86
  os.mkdir(model_version_path)
File without changes
@@ -0,0 +1,33 @@
1
+ from clarifai_grpc.grpc.api import resources_pb2
2
+
3
+ from clarifai.client.runner import Runner
4
+
5
+
6
+ class MyRunner(Runner):
7
+ """A custom runner that adds "Hello World" to the end of the text and replaces the domain of the
8
+ image URL as an example.
9
+ """
10
+
11
+ def run_input(self, input: resources_pb2.Input) -> resources_pb2.Output:
12
+ """This is the method that will be called when the runner is run. It takes in an input and
13
+ returns an output.
14
+ """
15
+
16
+ output = resources_pb2.Output()
17
+
18
+ data = input.data
19
+
20
+ if data.text.raw != "":
21
+ output.data.text.raw = data.text.raw + "Hello World"
22
+ if data.image.url != "":
23
+ output.data.text.raw = data.image.url.replace("samples.clarifai.com", "newdomain.com")
24
+ return output
25
+
26
+
27
+ if __name__ == '__main__':
28
+ # Make sure you set these env vars before running the example.
29
+ # CLARIFAI_PAT
30
+ # CLARIFAI_USER_ID
31
+
32
+ # You need to first create a runner in the Clarifai API and then use the ID here.
33
+ MyRunner(runner_id="sdk-test-runner").start()
@@ -0,0 +1,60 @@
1
+ from schema import And, Optional, Regex, Schema
2
+
3
+
4
+ def get_schema() -> Schema:
5
+ """Initialize the schema for rank and filter.
6
+
7
+ This schema validates:
8
+
9
+ - Rank and filter must be a list
10
+ - Each item in the list must be a dict
11
+ - The dict can contain these optional keys:
12
+ - 'image_url': Valid URL string
13
+ - 'text_raw': Non-empty string
14
+ - 'metadata': Dict
15
+ - 'image_bytes': Bytes
16
+ - 'geo_point': Dict with 'longitude', 'latitude' and 'geo_limit' as float, float and int respectively
17
+ - 'concepts': List where each item is a concept dict
18
+ - Concept dict requires at least one of:
19
+ - 'name': Non-empty string with dashes/underscores
20
+ - 'id': Non-empty string
21
+ - 'language': Non-empty string
22
+ - 'value': 0 or 1 integer
23
+
24
+ Returns:
25
+ Schema: The schema for rank and filter.
26
+ """
27
+ # Schema for a single concept
28
+ concept_schema = Schema({
29
+ Optional('value'):
30
+ And(int, lambda x: x in [0, 1]),
31
+ Optional('id'):
32
+ And(str, len),
33
+ Optional('language'):
34
+ And(str, len),
35
+ # Non-empty strings with internal dashes and underscores.
36
+ Optional('name'):
37
+ And(str, len, Regex(r'^[0-9A-Za-z]+([-_][0-9A-Za-z]+)*$'))
38
+ })
39
+
40
+ # Schema for a rank or filter item
41
+ rank_filter_item_schema = Schema({
42
+ Optional('image_url'):
43
+ And(str, Regex(r'^https?://')),
44
+ Optional('text_raw'):
45
+ And(str, len),
46
+ Optional('metadata'):
47
+ dict,
48
+ Optional('image_bytes'):
49
+ bytes,
50
+ Optional('geo_point'): {
51
+ 'longitude': float,
52
+ 'latitude': float,
53
+ 'geo_limit': int
54
+ },
55
+ Optional("concepts"):
56
+ And(list, lambda x: all(concept_schema.is_valid(item) and len(item) > 0 for item in x)),
57
+ })
58
+
59
+ # Schema for rank and filter args
60
+ return Schema([rank_filter_item_schema])
clarifai/utils/logging.py CHANGED
@@ -1,16 +1,66 @@
1
1
  import logging
2
- from typing import Optional
2
+ from collections import defaultdict
3
+ from typing import Dict, List, Optional
3
4
 
5
+ from rich import print as rprint
4
6
  from rich.logging import RichHandler
5
7
  from rich.table import Table
6
8
  from rich.traceback import install
9
+ from rich.tree import Tree
7
10
 
8
11
  install()
9
12
 
10
13
 
11
- def table_from_dict(data, column_names, title="") -> Table:
14
+ def display_workflow_tree(nodes_data: List[Dict]) -> None:
15
+ """Displays a tree of the workflow nodes."""
16
+ # Create a mapping of node_id to the list of node_ids that are connected to it.
17
+ node_adj_mapping = defaultdict(list)
18
+ # Create a mapping of node_id to the node data info.
19
+ nodes_data_dict = {}
20
+ for node in nodes_data:
21
+ nodes_data_dict[node["id"]] = node
22
+ if node.get("node_inputs", "") == "":
23
+ node_adj_mapping["Input"].append(node["id"])
24
+ else:
25
+ for node_input in node["node_inputs"]:
26
+ node_adj_mapping[node_input["node_id"]].append(node["id"])
27
+
28
+ # Get all leaf nodes.
29
+ leaf_node_ids = set()
30
+ for node_id in list(nodes_data_dict.keys()):
31
+ if node_adj_mapping.get(node_id, "") == "":
32
+ leaf_node_ids.add(node_id)
33
+
34
+ def build_node_tree(node_id="Input"):
35
+ """Recursively builds a rich tree of the workflow nodes."""
36
+ # Set the style of the current node.
37
+ style_str = "green" if node_id in leaf_node_ids else "white"
38
+
39
+ # Create a Tree object for the current node.
40
+ if node_id != "Input":
41
+ node_table = table_from_dict(
42
+ [nodes_data_dict[node_id]["model"]],
43
+ column_names=["id", "model_type_id", "app_id", "user_id"],
44
+ title="Node: " + node_id)
45
+
46
+ tree = Tree(node_table, style=style_str, guide_style="underline2 white")
47
+ else:
48
+ tree = Tree(f"[green] {node_id}", style=style_str, guide_style="underline2 white")
49
+
50
+ # Recursively add the child nodes of the current node to the tree.
51
+ for child in node_adj_mapping.get(node_id, []):
52
+ tree.add(build_node_tree(child))
53
+
54
+ # Return the tree.
55
+ return tree
56
+
57
+ tree = build_node_tree("Input")
58
+ rprint(tree)
59
+
60
+
61
+ def table_from_dict(data: List[Dict], column_names: List[str], title: str = "") -> Table:
12
62
  """Use this function for printing tables from a list of dicts."""
13
- table = Table(title=title, show_header=True, header_style="bold blue")
63
+ table = Table(title=title, show_lines=False, show_header=True, header_style="blue")
14
64
  for column_name in column_names:
15
65
  table.add_column(column_name)
16
66
  for row in data:
clarifai/versions.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import os
2
2
 
3
- CLIENT_VERSION = '9.8.1'
3
+ CLIENT_VERSION = '9.9.0'
4
4
  OS_VER = os.sys.platform
5
5
  PYTHON_VERSION = '.'.join(
6
6
  map(str, [os.sys.version_info.major, os.sys.version_info.minor, os.sys.version_info.micro]))
File without changes
@@ -0,0 +1,68 @@
1
+ from typing import Any, Dict
2
+
3
+ import yaml
4
+ from google.protobuf.json_format import MessageToDict
5
+
6
+ VALID_YAML_KEYS = ["workflow", "id", "nodes", "node_inputs", "node_id", "model"]
7
+
8
+
9
+ def clean_up_unused_keys(wf: dict):
10
+ """Removes unused keys from dict before exporting to yaml. Supports nested dicts."""
11
+ new_wf = dict()
12
+ for key, val in wf.items():
13
+ if key not in VALID_YAML_KEYS:
14
+ continue
15
+ if key == "model":
16
+ new_wf["model"] = {
17
+ "model_id": wf["model"]["id"],
18
+ "model_version_id": wf["model"]["model_version"]["id"]
19
+ }
20
+ # If the model is not from clarifai main, add the app_id and user_id to the model dict.
21
+ if wf["model"]["user_id"] != "clarifai" and wf["model"]["app_id"] != "main":
22
+ new_wf["model"].update({
23
+ "app_id": wf["model"]["app_id"],
24
+ "user_id": wf["model"]["user_id"]
25
+ })
26
+ elif isinstance(val, dict):
27
+ new_wf[key] = clean_up_unused_keys(val)
28
+ elif isinstance(val, list):
29
+ new_list = []
30
+ for i in val:
31
+ new_list.append(clean_up_unused_keys(i))
32
+ new_wf[key] = new_list
33
+ else:
34
+ new_wf[key] = val
35
+ return new_wf
36
+
37
+
38
+ class Exporter:
39
+
40
+ def __init__(self, workflow):
41
+ self.wf = workflow
42
+
43
+ def __enter__(self):
44
+ return self
45
+
46
+ def parse(self) -> Dict[str, Any]:
47
+ """Reads a resources_pb2.Workflow object (e.g. from a GetWorkflow response)
48
+
49
+ Returns:
50
+ dict: A dict representation of the workflow.
51
+ """
52
+ if isinstance(self.wf, list):
53
+ self.wf = self.wf[0]
54
+ wf = {"workflow": MessageToDict(self.wf, preserving_proto_field_name=True)}
55
+ clean_wf = clean_up_unused_keys(wf)
56
+ self.wf_dict = clean_wf
57
+ return clean_wf
58
+
59
+ def export(self, out_path):
60
+ with open(out_path, 'w') as out_file:
61
+ yaml.dump(self.wf_dict["workflow"], out_file, default_flow_style=False)
62
+
63
+ def __exit__(self, *args):
64
+ self.close()
65
+
66
+ def close(self):
67
+ del self.wf
68
+ del self.wf_dict
@@ -0,0 +1,59 @@
1
+ from typing import Dict, Optional, Set
2
+
3
+ from clarifai_grpc.grpc.api import resources_pb2
4
+ from google.protobuf import struct_pb2
5
+ from google.protobuf.json_format import MessageToDict
6
+
7
+
8
+ def get_yaml_output_info_proto(yaml_model_output_info: Dict) -> Optional[resources_pb2.OutputInfo]:
9
+ """Converts a yaml model output info to an api model output info."""
10
+ if not yaml_model_output_info:
11
+ return None
12
+
13
+ return resources_pb2.OutputInfo(
14
+ params=convert_yaml_params_to_api_params(yaml_model_output_info.get('params')))
15
+
16
+
17
+ def convert_yaml_params_to_api_params(yaml_params: Dict) -> Optional[struct_pb2.Struct]:
18
+ """Converts a yaml model output info params to an api model output info params."""
19
+ if not yaml_params:
20
+ return None
21
+
22
+ s = struct_pb2.Struct()
23
+ s.update(yaml_params)
24
+
25
+ return s
26
+
27
+
28
+ def is_same_yaml_model(api_model: resources_pb2.Model, yaml_model: Dict) -> bool:
29
+ """Compares a model from the API with a model from a yaml file."""
30
+ api_model = MessageToDict(api_model, preserving_proto_field_name=True)
31
+
32
+ yaml_model_from_api = dict()
33
+ for k, _ in yaml_model.items():
34
+ if k == "output_info" and api_model["model_version"].get("output_info", "") != "":
35
+ yaml_model_from_api[k] = dict(params=api_model["model_version"]["output_info"].get("params"))
36
+ else:
37
+ yaml_model_from_api[k] = api_model.get(k)
38
+ yaml_model_from_api.update({"model_id": api_model.get("id")})
39
+
40
+ ignore_keys = {}
41
+
42
+ return is_dict_in_dict(yaml_model, yaml_model_from_api, ignore_keys)
43
+
44
+
45
+ def is_dict_in_dict(d1: Dict, d2: Dict, ignore_keys: Set = None) -> bool:
46
+ """Compares two dicts recursively."""
47
+ for k, v in d1.items():
48
+ if ignore_keys and k in ignore_keys:
49
+ continue
50
+ if k not in d2:
51
+ return False
52
+ if isinstance(v, dict):
53
+ if not isinstance(d2[k], dict):
54
+ return False
55
+ return is_dict_in_dict(d1[k], d2[k], None)
56
+ elif v != d2[k]:
57
+ return False
58
+
59
+ return True
@@ -0,0 +1,67 @@
1
+ from schema import And, Optional, Regex, Schema, SchemaError, Use
2
+
3
+ # Non-empty, up to 32-character ASCII strings with internal dashes and underscores.
4
+ _id_validator = And(str, lambda s: 0 < len(s) <= 48, Regex(r'^[0-9A-Za-z]+([-_][0-9A-Za-z]+)*$'))
5
+
6
+ # 32-character hex string, converted to lower-case.
7
+ _hex_id_validator = And(str, Use(str.lower), Regex(r'^[0-9a-f]{32}'))
8
+
9
+
10
+ def _model_does_not_have_model_version_id_and_other_fields(m):
11
+ """ Validate that model does not have model_version_id and other model fields."""
12
+ if ('model_version_id' in m) and _model_has_other_fields(m):
13
+ raise SchemaError(f"model should not set model_version_id and other model fields: {m};"
14
+ f" please remove model_version_id or other model fields.")
15
+ return True
16
+
17
+
18
+ def _model_has_other_fields(m):
19
+ return any(k not in ['model_id', 'model_version_id'] for k in m.keys())
20
+
21
+
22
+ def _workflow_nodes_have_valid_dependencies(nodes):
23
+ """Validate that all inputs to a node are declared before it."""
24
+ node_ids = set()
25
+ for node in nodes:
26
+ for node_input in node.get("node_inputs", []):
27
+ if node_input["node_id"] not in node_ids:
28
+ raise SchemaError(f"missing input '{node_input['node_id']}' for node '{node['id']}'")
29
+ node_ids.add(node["id"])
30
+
31
+ return True
32
+
33
+
34
+ _data_schema = Schema({
35
+ "workflow": {
36
+ "id":
37
+ _id_validator,
38
+ "nodes":
39
+ And(
40
+ len,
41
+ [{
42
+ "id":
43
+ And(str, len), # Node IDs are not validated as IDs by the API.
44
+ "model":
45
+ And({
46
+ "model_id": _id_validator,
47
+ Optional("app_id"): _id_validator,
48
+ Optional("user_id"): _id_validator,
49
+ Optional("model_version_id"): _hex_id_validator,
50
+ Optional("model_type_id"): _id_validator,
51
+ Optional("description"): str,
52
+ Optional("output_info"): {
53
+ Optional("params"): dict,
54
+ },
55
+ }, _model_does_not_have_model_version_id_and_other_fields),
56
+ Optional("node_inputs"):
57
+ And(len, [{
58
+ "node_id": And(str, len),
59
+ }]),
60
+ }],
61
+ _workflow_nodes_have_valid_dependencies),
62
+ },
63
+ })
64
+
65
+
66
+ def validate(data):
67
+ return _data_schema.validate(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clarifai
3
- Version: 9.8.1
3
+ Version: 9.9.0
4
4
  Summary: Clarifai Python SDK
5
5
  Home-page: https://github.com/Clarifai/clarifai-python
6
6
  Author: Clarifai
@@ -18,6 +18,8 @@ Requires-Dist: tritonclient (==2.34.0)
18
18
  Requires-Dist: packaging
19
19
  Requires-Dist: tqdm (==4.64.1)
20
20
  Requires-Dist: rich (==13.4.2)
21
+ Requires-Dist: PyYAML (==6.0.1)
22
+ Requires-Dist: schema (==0.7.5)
21
23
 
22
24
  <h1 align="center">
23
25
  <a href="https://www.clarifai.com/"><img alt="Clarifai" title="Clarifai" src="https://upload.wikimedia.org/wikipedia/commons/b/bc/Clarifai_Logo_FC_Web.png"></a>
@@ -51,7 +53,7 @@ This is the official Python client for interacting with our powerful [API](https
51
53
 
52
54
  **Clarifai Community**: [https://clarifai.com/explore](https://clarifai.com/explore)
53
55
 
54
- **Python SDK Docs**: [https://clarifai-python.readthedocs.io/en/latest/index.html](https://clarifai-python.readthedocs.io/en/latest/index.html)
56
+ **Python SDK Docs**: [https://docs.clarifai.com/python-sdk/api-reference](https://docs.clarifai.com/python-sdk/api-reference)
55
57
 
56
58
 
57
59
  ---
@@ -216,7 +218,23 @@ all_workflow = app.list_workflow()
216
218
  # List all workflow in community filtered by description
217
219
  all_face_community_workflows = App().list_workflows(filter_by={"query": "face"}, only_in_app=False) # Get all face related workflows
218
220
  ```
221
+ #### Workflow Create
222
+ Create a new workflow specified by a yaml config file.
223
+ ```python
224
+ # Note: CLARIFAI_PAT must be set as env variable.
225
+ from clarifai.client.app import App
226
+ app = App(app_id="app_id", user_id="user_id")
227
+ workflow = app.create_workflow(config_filepath="config.yml")
228
+ ```
219
229
 
230
+ #### Workflow Export
231
+ Export an existing workflow from Clarifai as a local yaml file.
232
+ ```python
233
+ # Note: CLARIFAI_PAT must be set as env variable.
234
+ from clarifai.client.workflow import Workflow
235
+ workflow = Workflow("https://clarifai.com/clarifai/main/workflows/Demographics")
236
+ workflow.export('demographics_workflow.yml')
237
+ ```
220
238
 
221
239
  ## More Examples
222
240
  See many more code examples in this [repo](https://github.com/Clarifai/examples).