clarifai 11.8.2__tar.gz → 11.8.3__tar.gz

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 (154) hide show
  1. {clarifai-11.8.2/clarifai.egg-info → clarifai-11.8.3}/PKG-INFO +1 -1
  2. clarifai-11.8.3/clarifai/__init__.py +1 -0
  3. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/model.py +62 -24
  4. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/pipeline.py +84 -6
  5. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/model.py +2 -1
  6. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/pipeline_steps/pipeline_step_builder.py +97 -1
  7. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/pipelines/pipeline_builder.py +196 -34
  8. clarifai-11.8.3/clarifai/utils/hashing.py +117 -0
  9. {clarifai-11.8.2 → clarifai-11.8.3/clarifai.egg-info}/PKG-INFO +1 -1
  10. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai.egg-info/SOURCES.txt +1 -0
  11. clarifai-11.8.2/clarifai/__init__.py +0 -1
  12. {clarifai-11.8.2 → clarifai-11.8.3}/LICENSE +0 -0
  13. {clarifai-11.8.2 → clarifai-11.8.3}/MANIFEST.in +0 -0
  14. {clarifai-11.8.2 → clarifai-11.8.3}/README.md +0 -0
  15. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/README.md +0 -0
  16. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/__init__.py +0 -0
  17. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/__main__.py +0 -0
  18. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/base.py +0 -0
  19. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/compute_cluster.py +0 -0
  20. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/deployment.py +0 -0
  21. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/nodepool.py +0 -0
  22. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/pipeline_step.py +0 -0
  23. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/templates/__init__.py +0 -0
  24. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/templates/model_templates.py +0 -0
  25. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/templates/pipeline_step_templates.py +0 -0
  26. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli/templates/pipeline_templates.py +0 -0
  27. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/cli.py +0 -0
  28. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/__init__.py +0 -0
  29. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/app.py +0 -0
  30. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/auth/__init__.py +0 -0
  31. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/auth/helper.py +0 -0
  32. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/auth/register.py +0 -0
  33. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/auth/stub.py +0 -0
  34. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/base.py +0 -0
  35. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/compute_cluster.py +0 -0
  36. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/dataset.py +0 -0
  37. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/deployment.py +0 -0
  38. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/input.py +0 -0
  39. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/lister.py +0 -0
  40. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/model_client.py +0 -0
  41. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/module.py +0 -0
  42. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/nodepool.py +0 -0
  43. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/pipeline.py +0 -0
  44. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/pipeline_step.py +0 -0
  45. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/runner.py +0 -0
  46. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/search.py +0 -0
  47. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/user.py +0 -0
  48. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/client/workflow.py +0 -0
  49. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/base.py +0 -0
  50. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/dataset.py +0 -0
  51. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/input.py +0 -0
  52. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/model.py +0 -0
  53. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/rag.py +0 -0
  54. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/search.py +0 -0
  55. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/constants/workflow.py +0 -0
  56. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/__init__.py +0 -0
  57. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/export/__init__.py +0 -0
  58. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/export/inputs_annotations.py +0 -0
  59. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/__init__.py +0 -0
  60. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/base.py +0 -0
  61. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/features.py +0 -0
  62. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/image.py +0 -0
  63. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/README.md +0 -0
  64. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/__init__.py +0 -0
  65. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/coco_captions.py +0 -0
  66. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/coco_detection.py +0 -0
  67. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/imagenet_classification.py +0 -0
  68. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/loaders/xview_detection.py +0 -0
  69. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/multimodal.py +0 -0
  70. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/text.py +0 -0
  71. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/datasets/upload/utils.py +0 -0
  72. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/errors.py +0 -0
  73. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/models/__init__.py +0 -0
  74. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/models/api.py +0 -0
  75. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/modules/README.md +0 -0
  76. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/modules/__init__.py +0 -0
  77. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/modules/css.py +0 -0
  78. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/modules/pages.py +0 -0
  79. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/modules/style.css +0 -0
  80. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/rag/__init__.py +0 -0
  81. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/rag/rag.py +0 -0
  82. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/rag/utils.py +0 -0
  83. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/__init__.py +0 -0
  84. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/dockerfile_template/Dockerfile.template +0 -0
  85. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/__init__.py +0 -0
  86. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/dummy_openai_model.py +0 -0
  87. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/mcp_class.py +0 -0
  88. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/model_builder.py +0 -0
  89. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/model_class.py +0 -0
  90. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/model_run_locally.py +0 -0
  91. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/model_runner.py +0 -0
  92. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/model_servicer.py +0 -0
  93. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/openai_class.py +0 -0
  94. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/visual_classifier_class.py +0 -0
  95. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/models/visual_detector_class.py +0 -0
  96. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/pipeline_steps/__init__.py +0 -0
  97. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/pipelines/__init__.py +0 -0
  98. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/server.py +0 -0
  99. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/__init__.py +0 -0
  100. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/code_script.py +0 -0
  101. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/const.py +0 -0
  102. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/data_types/__init__.py +0 -0
  103. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/data_types/data_types.py +0 -0
  104. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/data_utils.py +0 -0
  105. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/loader.py +0 -0
  106. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/method_signatures.py +0 -0
  107. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/model_utils.py +0 -0
  108. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/openai_convertor.py +0 -0
  109. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/pipeline_validation.py +0 -0
  110. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/serializers.py +0 -0
  111. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/runners/utils/url_fetcher.py +0 -0
  112. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/schema/search.py +0 -0
  113. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/urls/helper.py +0 -0
  114. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/__init__.py +0 -0
  115. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/cli.py +0 -0
  116. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/config.py +0 -0
  117. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/constants.py +0 -0
  118. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/evaluation/__init__.py +0 -0
  119. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/evaluation/helpers.py +0 -0
  120. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/evaluation/main.py +0 -0
  121. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/evaluation/testset_annotation_parser.py +0 -0
  122. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/logging.py +0 -0
  123. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/misc.py +0 -0
  124. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/model_train.py +0 -0
  125. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/protobuf.py +0 -0
  126. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/utils/secrets.py +0 -0
  127. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/versions.py +0 -0
  128. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/workflows/__init__.py +0 -0
  129. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/workflows/export.py +0 -0
  130. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/workflows/utils.py +0 -0
  131. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai/workflows/validate.py +0 -0
  132. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai.egg-info/dependency_links.txt +0 -0
  133. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai.egg-info/entry_points.txt +0 -0
  134. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai.egg-info/requires.txt +0 -0
  135. {clarifai-11.8.2 → clarifai-11.8.3}/clarifai.egg-info/top_level.txt +0 -0
  136. {clarifai-11.8.2 → clarifai-11.8.3}/pyproject.toml +0 -0
  137. {clarifai-11.8.2 → clarifai-11.8.3}/requirements.txt +0 -0
  138. {clarifai-11.8.2 → clarifai-11.8.3}/setup.cfg +0 -0
  139. {clarifai-11.8.2 → clarifai-11.8.3}/setup.py +0 -0
  140. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_app.py +0 -0
  141. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_async_stub.py +0 -0
  142. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_auth.py +0 -0
  143. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_data_upload.py +0 -0
  144. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_eval.py +0 -0
  145. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_list_models.py +0 -0
  146. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_misc.py +0 -0
  147. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_model_predict.py +0 -0
  148. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_model_train.py +0 -0
  149. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_modules.py +0 -0
  150. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_pipeline_client.py +0 -0
  151. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_rag.py +0 -0
  152. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_search.py +0 -0
  153. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_secrets.py +0 -0
  154. {clarifai-11.8.2 → clarifai-11.8.3}/tests/test_stub.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarifai
3
- Version: 11.8.2
3
+ Version: 11.8.3
4
4
  Home-page: https://github.com/Clarifai/clarifai-python
5
5
  Author: Clarifai
6
6
  Author-email: support@clarifai.com
@@ -0,0 +1 @@
1
+ __version__ = "11.8.3"
@@ -231,6 +231,44 @@ def init(
231
231
  repo_url = format_github_repo_url(github_url)
232
232
  repo_url = f"https://github.com/{owner}/{repo}"
233
233
 
234
+ try:
235
+ # Create a temporary directory for cloning
236
+ with tempfile.TemporaryDirectory(prefix="clarifai_model_") as clone_dir:
237
+ # Clone the repository with explicit branch parameter
238
+ if not clone_github_repo(repo_url, clone_dir, github_pat, branch):
239
+ logger.error(f"Failed to clone repository from {repo_url}")
240
+ github_url = None # Fall back to template mode
241
+
242
+ else:
243
+ # Copy the entire repository content to target directory (excluding .git)
244
+ for item in os.listdir(clone_dir):
245
+ if item == '.git':
246
+ continue
247
+
248
+ source_path = os.path.join(clone_dir, item)
249
+ target_path = os.path.join(model_path, item)
250
+
251
+ if os.path.isdir(source_path):
252
+ shutil.copytree(source_path, target_path, dirs_exist_ok=True)
253
+ else:
254
+ shutil.copy2(source_path, target_path)
255
+
256
+ logger.info(f"Successfully cloned repository to {model_path}")
257
+ logger.info(
258
+ "Model initialization complete with GitHub repository clone"
259
+ )
260
+ logger.info("Next steps:")
261
+ logger.info("1. Review the model configuration")
262
+ logger.info("2. Install any required dependencies manually")
263
+ logger.info(
264
+ "3. Test the model locally using 'clarifai model local-test'"
265
+ )
266
+ return
267
+
268
+ except Exception as e:
269
+ logger.error(f"Failed to clone GitHub repository: {e}")
270
+ github_url = None # Fall back to template mode
271
+
234
272
  if toolkit:
235
273
  logger.info(f"Initializing model from GitHub repository: {github_url}")
236
274
 
@@ -240,31 +278,31 @@ def init(
240
278
  else:
241
279
  repo_url = format_github_repo_url(github_url)
242
280
 
243
- try:
244
- # Create a temporary directory for cloning
245
- with tempfile.TemporaryDirectory(prefix="clarifai_model_") as clone_dir:
246
- # Clone the repository with explicit branch parameter
247
- if not clone_github_repo(repo_url, clone_dir, github_pat, branch):
248
- logger.error(f"Failed to clone repository from {repo_url}")
249
- github_url = None # Fall back to template mode
250
-
251
- else:
252
- # Copy the entire repository content to target directory (excluding .git)
253
- for item in os.listdir(clone_dir):
254
- if item == '.git':
255
- continue
256
-
257
- source_path = os.path.join(clone_dir, item)
258
- target_path = os.path.join(model_path, item)
259
-
260
- if os.path.isdir(source_path):
261
- shutil.copytree(source_path, target_path, dirs_exist_ok=True)
262
- else:
263
- shutil.copy2(source_path, target_path)
281
+ try:
282
+ # Create a temporary directory for cloning
283
+ with tempfile.TemporaryDirectory(prefix="clarifai_model_") as clone_dir:
284
+ # Clone the repository with explicit branch parameter
285
+ if not clone_github_repo(repo_url, clone_dir, github_pat, branch):
286
+ logger.error(f"Failed to clone repository from {repo_url}")
287
+ github_url = None # Fall back to template mode
264
288
 
265
- except Exception as e:
266
- logger.error(f"Failed to clone GitHub repository: {e}")
267
- github_url = None
289
+ else:
290
+ # Copy the entire repository content to target directory (excluding .git)
291
+ for item in os.listdir(clone_dir):
292
+ if item == '.git':
293
+ continue
294
+
295
+ source_path = os.path.join(clone_dir, item)
296
+ target_path = os.path.join(model_path, item)
297
+
298
+ if os.path.isdir(source_path):
299
+ shutil.copytree(source_path, target_path, dirs_exist_ok=True)
300
+ else:
301
+ shutil.copy2(source_path, target_path)
302
+
303
+ except Exception as e:
304
+ logger.error(f"Failed to clone GitHub repository: {e}")
305
+ github_url = None
268
306
 
269
307
  if (model_name or port or context_length) and (toolkit == 'ollama'):
270
308
  customize_ollama_model(model_path, model_name, port, context_length)
@@ -26,14 +26,19 @@ def pipeline():
26
26
 
27
27
  @pipeline.command()
28
28
  @click.argument("path", type=click.Path(exists=True), required=False, default=".")
29
- def upload(path):
29
+ @click.option(
30
+ '--no-lockfile',
31
+ is_flag=True,
32
+ help='Skip creating config-lock.yaml file.',
33
+ )
34
+ def upload(path, no_lockfile):
30
35
  """Upload a pipeline with associated pipeline steps to Clarifai.
31
36
 
32
37
  PATH: Path to the pipeline configuration file or directory containing config.yaml. If not specified, the current directory is used by default.
33
38
  """
34
39
  from clarifai.runners.pipelines.pipeline_builder import upload_pipeline
35
40
 
36
- upload_pipeline(path)
41
+ upload_pipeline(path, no_lockfile=no_lockfile)
37
42
 
38
43
 
39
44
  @pipeline.command()
@@ -106,15 +111,32 @@ def run(
106
111
 
107
112
  validate_context(ctx)
108
113
 
114
+ # Try to load from config-lock.yaml first if no config is specified
115
+ lockfile_path = os.path.join(os.getcwd(), "config-lock.yaml")
116
+ if not config and os.path.exists(lockfile_path):
117
+ logger.info("Found config-lock.yaml, using it as default config source")
118
+ config = lockfile_path
119
+
109
120
  if config:
110
121
  config_data = from_yaml(config)
111
- pipeline_id = config_data.get('pipeline_id', pipeline_id)
112
- pipeline_version_id = config_data.get('pipeline_version_id', pipeline_version_id)
122
+
123
+ # Handle both regular config format and lockfile format
124
+ if 'pipeline' in config_data and isinstance(config_data['pipeline'], dict):
125
+ pipeline_config = config_data['pipeline']
126
+ pipeline_id = pipeline_config.get('id', pipeline_id)
127
+ pipeline_version_id = pipeline_config.get('version_id', pipeline_version_id)
128
+ user_id = pipeline_config.get('user_id', user_id)
129
+ app_id = pipeline_config.get('app_id', app_id)
130
+ else:
131
+ # Fallback to flat config structure
132
+ pipeline_id = config_data.get('pipeline_id', pipeline_id)
133
+ pipeline_version_id = config_data.get('pipeline_version_id', pipeline_version_id)
134
+ user_id = config_data.get('user_id', user_id)
135
+ app_id = config_data.get('app_id', app_id)
136
+
113
137
  pipeline_version_run_id = config_data.get(
114
138
  'pipeline_version_run_id', pipeline_version_run_id
115
139
  )
116
- user_id = config_data.get('user_id', user_id)
117
- app_id = config_data.get('app_id', app_id)
118
140
  nodepool_id = config_data.get('nodepool_id', nodepool_id)
119
141
  compute_cluster_id = config_data.get('compute_cluster_id', compute_cluster_id)
120
142
  pipeline_url = config_data.get('pipeline_url', pipeline_url)
@@ -319,6 +341,62 @@ def init(pipeline_path):
319
341
  logger.info("3. Run 'clarifai pipeline upload config.yaml' to upload your pipeline")
320
342
 
321
343
 
344
+ @pipeline.command()
345
+ @click.argument(
346
+ "lockfile_path", type=click.Path(exists=True), required=False, default="config-lock.yaml"
347
+ )
348
+ def validate_lock(lockfile_path):
349
+ """Validate a config-lock.yaml file for schema and reference consistency.
350
+
351
+ LOCKFILE_PATH: Path to the config-lock.yaml file. If not specified, looks for config-lock.yaml in current directory.
352
+ """
353
+ from clarifai.runners.utils.pipeline_validation import PipelineConfigValidator
354
+ from clarifai.utils.cli import from_yaml
355
+
356
+ try:
357
+ # Load the lockfile
358
+ lockfile_data = from_yaml(lockfile_path)
359
+
360
+ # Validate required fields
361
+ if "pipeline" not in lockfile_data:
362
+ raise ValueError("'pipeline' section not found in lockfile")
363
+
364
+ pipeline = lockfile_data["pipeline"]
365
+ required_fields = ["id", "user_id", "app_id", "version_id"]
366
+
367
+ for field in required_fields:
368
+ if field not in pipeline:
369
+ raise ValueError(f"Required field '{field}' not found in pipeline section")
370
+ if not pipeline[field]:
371
+ raise ValueError(f"Required field '{field}' cannot be empty")
372
+
373
+ # Validate orchestration spec if present
374
+ if "orchestration_spec" in pipeline:
375
+ # Create a temporary config structure for validation
376
+ temp_config = {
377
+ "pipeline": {
378
+ "id": pipeline["id"],
379
+ "user_id": pipeline["user_id"],
380
+ "app_id": pipeline["app_id"],
381
+ "orchestration_spec": pipeline["orchestration_spec"],
382
+ }
383
+ }
384
+
385
+ # Use existing validator to check orchestration spec
386
+ validator = PipelineConfigValidator()
387
+ validator._validate_orchestration_spec(temp_config)
388
+
389
+ logger.info(f"✅ Lockfile {lockfile_path} is valid")
390
+ logger.info(f"Pipeline: {pipeline['id']}")
391
+ logger.info(f"User: {pipeline['user_id']}")
392
+ logger.info(f"App: {pipeline['app_id']}")
393
+ logger.info(f"Version: {pipeline['version_id']}")
394
+
395
+ except Exception as e:
396
+ logger.error(f"❌ Lockfile validation failed: {e}")
397
+ raise click.Abort()
398
+
399
+
322
400
  @pipeline.command(['ls'])
323
401
  @click.option('--page_no', required=False, help='Page number to list.', default=1)
324
402
  @click.option('--per_page', required=False, help='Number of items per page.', default=16)
@@ -522,8 +522,9 @@ class Model(Lister, BaseClient):
522
522
  model=self.model_info,
523
523
  runner_selector=self._runner_selector,
524
524
  )
525
+ # Pass in None for async stub will create it.
525
526
  self._client = ModelClient(
526
- stub=self.STUB, async_stub=self.async_stub, request_template=request_template
527
+ stub=self.STUB, async_stub=None, request_template=request_template
527
528
  )
528
529
  return self._client
529
530
 
@@ -4,6 +4,7 @@ import sys
4
4
  import tarfile
5
5
  import time
6
6
  from string import Template
7
+ from typing import List, Optional
7
8
 
8
9
  import yaml
9
10
  from clarifai_grpc.grpc.api import resources_pb2, service_pb2
@@ -11,6 +12,7 @@ from clarifai_grpc.grpc.api.status import status_code_pb2
11
12
  from google.protobuf import json_format
12
13
 
13
14
  from clarifai.client.base import BaseClient
15
+ from clarifai.utils.hashing import hash_directory
14
16
  from clarifai.utils.logging import logger
15
17
  from clarifai.utils.misc import get_uuid
16
18
  from clarifai.versions import CLIENT_VERSION
@@ -22,12 +24,13 @@ UPLOAD_CHUNK_SIZE = 14 * 1024 * 1024
22
24
  class PipelineStepBuilder:
23
25
  """Pipeline Step Builder class for managing pipeline step upload to Clarifai."""
24
26
 
25
- def __init__(self, folder: str):
27
+ def __init__(self, folder: str, hash_exclusions: Optional[List[str]] = None):
26
28
  """
27
29
  Initialize PipelineStepBuilder.
28
30
 
29
31
  :param folder: The folder containing the pipeline step files (config.yaml, requirements.txt,
30
32
  dockerfile, and pipeline_step.py in 1/ subdirectory)
33
+ :param hash_exclusions: List of file names to exclude from hash calculation (defaults to ['config-lock.yaml'])
31
34
  """
32
35
  self._client = None
33
36
  self.folder = self._validate_folder(folder)
@@ -37,6 +40,10 @@ class PipelineStepBuilder:
37
40
  self.pipeline_step_id = self.pipeline_step_proto.id
38
41
  self.pipeline_step_version_id = None
39
42
  self.pipeline_step_compute_info = self._get_pipeline_step_compute_info()
43
+ # Configure files to exclude from hash calculation
44
+ self.hash_exclusions = (
45
+ hash_exclusions if hash_exclusions is not None else ['config-lock.yaml']
46
+ )
40
47
 
41
48
  @property
42
49
  def client(self):
@@ -490,6 +497,95 @@ COPY --link=true requirements.txt config.yaml /home/nonroot/main/
490
497
 
491
498
  raise TimeoutError("Pipeline step build did not finish in time")
492
499
 
500
+ def load_config_lock(self):
501
+ """
502
+ Load existing config-lock.yaml if it exists.
503
+
504
+ :return: Dictionary with config-lock data or None if file doesn't exist
505
+ """
506
+ config_lock_path = os.path.join(self.folder, "config-lock.yaml")
507
+ if os.path.exists(config_lock_path):
508
+ try:
509
+ with open(config_lock_path, 'r', encoding='utf-8') as f:
510
+ return yaml.safe_load(f)
511
+ except Exception as e:
512
+ logger.warning(f"Failed to load config-lock.yaml: {e}")
513
+ return None
514
+ return None
515
+
516
+ def should_upload_step(self, algo="md5"):
517
+ """
518
+ Check if the pipeline step should be uploaded based on hash comparison.
519
+
520
+ :param algo: Hash algorithm to use
521
+ :return: True if step should be uploaded, False otherwise
522
+ """
523
+ config_lock = self.load_config_lock()
524
+
525
+ # If no config-lock.yaml exists, upload the step (first time upload)
526
+ if config_lock is None:
527
+ logger.info("No config-lock.yaml found, will upload pipeline step")
528
+ return True
529
+
530
+ # Compare stored hash with freshly computed one
531
+ current_hash = hash_directory(self.folder, algo=algo, exclude_files=self.hash_exclusions)
532
+ stored_hash_info = config_lock.get("hash", {})
533
+ stored_hash = stored_hash_info.get("value", "")
534
+ stored_algo = stored_hash_info.get("algo", "md5")
535
+
536
+ # If algorithm changed, re-upload to update hash
537
+ if stored_algo != algo:
538
+ logger.info(
539
+ f"Hash algorithm changed from {stored_algo} to {algo}, will upload pipeline step"
540
+ )
541
+ return True
542
+
543
+ # If hash changed, upload
544
+ if current_hash != stored_hash:
545
+ logger.info(
546
+ f"Hash changed (was: {stored_hash}, now: {current_hash}), will upload pipeline step"
547
+ )
548
+ return True
549
+
550
+ logger.info(f"Hash unchanged ({current_hash}), skipping pipeline step upload")
551
+ return False
552
+
553
+ def generate_config_lock(self, version_id, algo="md5"):
554
+ """
555
+ Generate config-lock.yaml content for the pipeline step.
556
+
557
+ :param version_id: Pipeline step version ID
558
+ :param algo: Hash algorithm used
559
+ :return: Dictionary with config-lock data
560
+ """
561
+ # Compute hash
562
+ hash_value = hash_directory(self.folder, algo=algo, exclude_files=self.hash_exclusions)
563
+
564
+ # Create config-lock structure
565
+ config_lock = {"id": version_id, "hash": {"algo": algo, "value": hash_value}}
566
+
567
+ # Append the original config.yaml contents
568
+ config_lock.update(self.config)
569
+
570
+ return config_lock
571
+
572
+ def save_config_lock(self, version_id, algo="md5"):
573
+ """
574
+ Save config-lock.yaml file with pipeline step metadata.
575
+
576
+ :param version_id: Pipeline step version ID
577
+ :param algo: Hash algorithm used
578
+ """
579
+ config_lock_data = self.generate_config_lock(version_id, algo)
580
+ config_lock_path = os.path.join(self.folder, "config-lock.yaml")
581
+
582
+ try:
583
+ with open(config_lock_path, 'w', encoding='utf-8') as f:
584
+ yaml.dump(config_lock_data, f, default_flow_style=False, allow_unicode=True)
585
+ logger.info(f"Generated config-lock.yaml at {config_lock_path}")
586
+ except Exception as e:
587
+ logger.error(f"Failed to save config-lock.yaml: {e}")
588
+
493
589
 
494
590
  def upload_pipeline_step(folder, skip_dockerfile=False):
495
591
  """