clarifai 11.5.3rc2__py3-none-any.whl → 11.5.5__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 (179) hide show
  1. clarifai/__init__.py +1 -1
  2. clarifai/client/model.py +4 -1
  3. clarifai/runners/models/model_builder.py +4 -4
  4. clarifai/runners/models/model_class.py +90 -22
  5. clarifai/runners/models/model_runner.py +50 -8
  6. clarifai/runners/models/model_servicer.py +26 -3
  7. clarifai/runners/utils/code_script.py +5 -1
  8. clarifai/runners/utils/data_types/data_types.py +5 -22
  9. clarifai/runners/utils/model_utils.py +49 -0
  10. clarifai/runners/utils/url_fetcher.py +55 -10
  11. {clarifai-11.5.3rc2.dist-info → clarifai-11.5.5.dist-info}/METADATA +2 -2
  12. clarifai-11.5.5.dist-info/RECORD +126 -0
  13. clarifai/__pycache__/__init__.cpython-312.pyc +0 -0
  14. clarifai/__pycache__/__init__.cpython-38.pyc +0 -0
  15. clarifai/__pycache__/__init__.cpython-39.pyc +0 -0
  16. clarifai/__pycache__/errors.cpython-312.pyc +0 -0
  17. clarifai/__pycache__/errors.cpython-38.pyc +0 -0
  18. clarifai/__pycache__/errors.cpython-39.pyc +0 -0
  19. clarifai/__pycache__/versions.cpython-312.pyc +0 -0
  20. clarifai/__pycache__/versions.cpython-38.pyc +0 -0
  21. clarifai/__pycache__/versions.cpython-39.pyc +0 -0
  22. clarifai/client/__pycache__/__init__.cpython-312.pyc +0 -0
  23. clarifai/client/__pycache__/__init__.cpython-38.pyc +0 -0
  24. clarifai/client/__pycache__/__init__.cpython-39.pyc +0 -0
  25. clarifai/client/__pycache__/app.cpython-312.pyc +0 -0
  26. clarifai/client/__pycache__/app.cpython-38.pyc +0 -0
  27. clarifai/client/__pycache__/app.cpython-39.pyc +0 -0
  28. clarifai/client/__pycache__/base.cpython-312.pyc +0 -0
  29. clarifai/client/__pycache__/base.cpython-38.pyc +0 -0
  30. clarifai/client/__pycache__/base.cpython-39.pyc +0 -0
  31. clarifai/client/__pycache__/compute_cluster.cpython-312.pyc +0 -0
  32. clarifai/client/__pycache__/compute_cluster.cpython-38.pyc +0 -0
  33. clarifai/client/__pycache__/compute_cluster.cpython-39.pyc +0 -0
  34. clarifai/client/__pycache__/dataset.cpython-312.pyc +0 -0
  35. clarifai/client/__pycache__/dataset.cpython-38.pyc +0 -0
  36. clarifai/client/__pycache__/dataset.cpython-39.pyc +0 -0
  37. clarifai/client/__pycache__/deployment.cpython-312.pyc +0 -0
  38. clarifai/client/__pycache__/deployment.cpython-38.pyc +0 -0
  39. clarifai/client/__pycache__/deployment.cpython-39.pyc +0 -0
  40. clarifai/client/__pycache__/input.cpython-312.pyc +0 -0
  41. clarifai/client/__pycache__/input.cpython-38.pyc +0 -0
  42. clarifai/client/__pycache__/input.cpython-39.pyc +0 -0
  43. clarifai/client/__pycache__/lister.cpython-312.pyc +0 -0
  44. clarifai/client/__pycache__/lister.cpython-38.pyc +0 -0
  45. clarifai/client/__pycache__/lister.cpython-39.pyc +0 -0
  46. clarifai/client/__pycache__/model.cpython-312.pyc +0 -0
  47. clarifai/client/__pycache__/model.cpython-38.pyc +0 -0
  48. clarifai/client/__pycache__/model.cpython-39.pyc +0 -0
  49. clarifai/client/__pycache__/model_client.cpython-39.pyc +0 -0
  50. clarifai/client/__pycache__/module.cpython-312.pyc +0 -0
  51. clarifai/client/__pycache__/module.cpython-38.pyc +0 -0
  52. clarifai/client/__pycache__/module.cpython-39.pyc +0 -0
  53. clarifai/client/__pycache__/nodepool.cpython-312.pyc +0 -0
  54. clarifai/client/__pycache__/nodepool.cpython-38.pyc +0 -0
  55. clarifai/client/__pycache__/nodepool.cpython-39.pyc +0 -0
  56. clarifai/client/__pycache__/runner.cpython-39.pyc +0 -0
  57. clarifai/client/__pycache__/search.cpython-312.pyc +0 -0
  58. clarifai/client/__pycache__/search.cpython-38.pyc +0 -0
  59. clarifai/client/__pycache__/search.cpython-39.pyc +0 -0
  60. clarifai/client/__pycache__/user.cpython-312.pyc +0 -0
  61. clarifai/client/__pycache__/user.cpython-38.pyc +0 -0
  62. clarifai/client/__pycache__/user.cpython-39.pyc +0 -0
  63. clarifai/client/__pycache__/workflow.cpython-312.pyc +0 -0
  64. clarifai/client/__pycache__/workflow.cpython-38.pyc +0 -0
  65. clarifai/client/__pycache__/workflow.cpython-39.pyc +0 -0
  66. clarifai/client/auth/__pycache__/__init__.cpython-312.pyc +0 -0
  67. clarifai/client/auth/__pycache__/__init__.cpython-38.pyc +0 -0
  68. clarifai/client/auth/__pycache__/__init__.cpython-39.pyc +0 -0
  69. clarifai/client/auth/__pycache__/helper.cpython-312.pyc +0 -0
  70. clarifai/client/auth/__pycache__/helper.cpython-38.pyc +0 -0
  71. clarifai/client/auth/__pycache__/helper.cpython-39.pyc +0 -0
  72. clarifai/client/auth/__pycache__/register.cpython-312.pyc +0 -0
  73. clarifai/client/auth/__pycache__/register.cpython-38.pyc +0 -0
  74. clarifai/client/auth/__pycache__/register.cpython-39.pyc +0 -0
  75. clarifai/client/auth/__pycache__/stub.cpython-312.pyc +0 -0
  76. clarifai/client/auth/__pycache__/stub.cpython-38.pyc +0 -0
  77. clarifai/client/auth/__pycache__/stub.cpython-39.pyc +0 -0
  78. clarifai/constants/__pycache__/base.cpython-312.pyc +0 -0
  79. clarifai/constants/__pycache__/base.cpython-38.pyc +0 -0
  80. clarifai/constants/__pycache__/base.cpython-39.pyc +0 -0
  81. clarifai/constants/__pycache__/dataset.cpython-312.pyc +0 -0
  82. clarifai/constants/__pycache__/dataset.cpython-38.pyc +0 -0
  83. clarifai/constants/__pycache__/dataset.cpython-39.pyc +0 -0
  84. clarifai/constants/__pycache__/input.cpython-312.pyc +0 -0
  85. clarifai/constants/__pycache__/input.cpython-38.pyc +0 -0
  86. clarifai/constants/__pycache__/input.cpython-39.pyc +0 -0
  87. clarifai/constants/__pycache__/model.cpython-312.pyc +0 -0
  88. clarifai/constants/__pycache__/model.cpython-38.pyc +0 -0
  89. clarifai/constants/__pycache__/model.cpython-39.pyc +0 -0
  90. clarifai/constants/__pycache__/search.cpython-312.pyc +0 -0
  91. clarifai/constants/__pycache__/search.cpython-38.pyc +0 -0
  92. clarifai/constants/__pycache__/search.cpython-39.pyc +0 -0
  93. clarifai/constants/__pycache__/workflow.cpython-312.pyc +0 -0
  94. clarifai/constants/__pycache__/workflow.cpython-38.pyc +0 -0
  95. clarifai/constants/__pycache__/workflow.cpython-39.pyc +0 -0
  96. clarifai/datasets/__pycache__/__init__.cpython-312.pyc +0 -0
  97. clarifai/datasets/__pycache__/__init__.cpython-38.pyc +0 -0
  98. clarifai/datasets/__pycache__/__init__.cpython-39.pyc +0 -0
  99. clarifai/datasets/export/__pycache__/__init__.cpython-312.pyc +0 -0
  100. clarifai/datasets/export/__pycache__/__init__.cpython-38.pyc +0 -0
  101. clarifai/datasets/export/__pycache__/__init__.cpython-39.pyc +0 -0
  102. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-312.pyc +0 -0
  103. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-38.pyc +0 -0
  104. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-39.pyc +0 -0
  105. clarifai/datasets/upload/__pycache__/__init__.cpython-312.pyc +0 -0
  106. clarifai/datasets/upload/__pycache__/__init__.cpython-38.pyc +0 -0
  107. clarifai/datasets/upload/__pycache__/__init__.cpython-39.pyc +0 -0
  108. clarifai/datasets/upload/__pycache__/base.cpython-312.pyc +0 -0
  109. clarifai/datasets/upload/__pycache__/base.cpython-38.pyc +0 -0
  110. clarifai/datasets/upload/__pycache__/base.cpython-39.pyc +0 -0
  111. clarifai/datasets/upload/__pycache__/features.cpython-312.pyc +0 -0
  112. clarifai/datasets/upload/__pycache__/features.cpython-38.pyc +0 -0
  113. clarifai/datasets/upload/__pycache__/features.cpython-39.pyc +0 -0
  114. clarifai/datasets/upload/__pycache__/image.cpython-312.pyc +0 -0
  115. clarifai/datasets/upload/__pycache__/image.cpython-38.pyc +0 -0
  116. clarifai/datasets/upload/__pycache__/image.cpython-39.pyc +0 -0
  117. clarifai/datasets/upload/__pycache__/multimodal.cpython-312.pyc +0 -0
  118. clarifai/datasets/upload/__pycache__/multimodal.cpython-38.pyc +0 -0
  119. clarifai/datasets/upload/__pycache__/multimodal.cpython-39.pyc +0 -0
  120. clarifai/datasets/upload/__pycache__/text.cpython-312.pyc +0 -0
  121. clarifai/datasets/upload/__pycache__/text.cpython-38.pyc +0 -0
  122. clarifai/datasets/upload/__pycache__/text.cpython-39.pyc +0 -0
  123. clarifai/datasets/upload/__pycache__/utils.cpython-312.pyc +0 -0
  124. clarifai/datasets/upload/__pycache__/utils.cpython-38.pyc +0 -0
  125. clarifai/datasets/upload/__pycache__/utils.cpython-39.pyc +0 -0
  126. clarifai/runners/__pycache__/__init__.cpython-39.pyc +0 -0
  127. clarifai/runners/models/__pycache__/__init__.cpython-39.pyc +0 -0
  128. clarifai/runners/models/__pycache__/mcp_class.cpython-39.pyc +0 -0
  129. clarifai/runners/models/__pycache__/model_class.cpython-39.pyc +0 -0
  130. clarifai/runners/models/__pycache__/model_runner.cpython-39.pyc +0 -0
  131. clarifai/runners/models/__pycache__/openai_class.cpython-39.pyc +0 -0
  132. clarifai/runners/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  133. clarifai/runners/utils/__pycache__/code_script.cpython-39.pyc +0 -0
  134. clarifai/runners/utils/__pycache__/data_utils.cpython-39.pyc +0 -0
  135. clarifai/runners/utils/__pycache__/method_signatures.cpython-39.pyc +0 -0
  136. clarifai/runners/utils/__pycache__/openai_convertor.cpython-39.pyc +0 -0
  137. clarifai/runners/utils/__pycache__/serializers.cpython-39.pyc +0 -0
  138. clarifai/runners/utils/__pycache__/url_fetcher.cpython-39.pyc +0 -0
  139. clarifai/runners/utils/data_types/__pycache__/__init__.cpython-39.pyc +0 -0
  140. clarifai/runners/utils/data_types/__pycache__/data_types.cpython-39.pyc +0 -0
  141. clarifai/schema/__pycache__/search.cpython-312.pyc +0 -0
  142. clarifai/schema/__pycache__/search.cpython-38.pyc +0 -0
  143. clarifai/schema/__pycache__/search.cpython-39.pyc +0 -0
  144. clarifai/urls/__pycache__/helper.cpython-312.pyc +0 -0
  145. clarifai/urls/__pycache__/helper.cpython-38.pyc +0 -0
  146. clarifai/urls/__pycache__/helper.cpython-39.pyc +0 -0
  147. clarifai/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  148. clarifai/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  149. clarifai/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  150. clarifai/utils/__pycache__/constants.cpython-312.pyc +0 -0
  151. clarifai/utils/__pycache__/constants.cpython-38.pyc +0 -0
  152. clarifai/utils/__pycache__/constants.cpython-39.pyc +0 -0
  153. clarifai/utils/__pycache__/logging.cpython-312.pyc +0 -0
  154. clarifai/utils/__pycache__/logging.cpython-38.pyc +0 -0
  155. clarifai/utils/__pycache__/logging.cpython-39.pyc +0 -0
  156. clarifai/utils/__pycache__/misc.cpython-312.pyc +0 -0
  157. clarifai/utils/__pycache__/misc.cpython-38.pyc +0 -0
  158. clarifai/utils/__pycache__/misc.cpython-39.pyc +0 -0
  159. clarifai/utils/__pycache__/model_train.cpython-312.pyc +0 -0
  160. clarifai/utils/__pycache__/model_train.cpython-38.pyc +0 -0
  161. clarifai/utils/__pycache__/model_train.cpython-39.pyc +0 -0
  162. clarifai/utils/__pycache__/protobuf.cpython-39.pyc +0 -0
  163. clarifai/workflows/__pycache__/__init__.cpython-312.pyc +0 -0
  164. clarifai/workflows/__pycache__/__init__.cpython-38.pyc +0 -0
  165. clarifai/workflows/__pycache__/__init__.cpython-39.pyc +0 -0
  166. clarifai/workflows/__pycache__/export.cpython-312.pyc +0 -0
  167. clarifai/workflows/__pycache__/export.cpython-38.pyc +0 -0
  168. clarifai/workflows/__pycache__/export.cpython-39.pyc +0 -0
  169. clarifai/workflows/__pycache__/utils.cpython-312.pyc +0 -0
  170. clarifai/workflows/__pycache__/utils.cpython-38.pyc +0 -0
  171. clarifai/workflows/__pycache__/utils.cpython-39.pyc +0 -0
  172. clarifai/workflows/__pycache__/validate.cpython-312.pyc +0 -0
  173. clarifai/workflows/__pycache__/validate.cpython-38.pyc +0 -0
  174. clarifai/workflows/__pycache__/validate.cpython-39.pyc +0 -0
  175. clarifai-11.5.3rc2.dist-info/RECORD +0 -288
  176. {clarifai-11.5.3rc2.dist-info → clarifai-11.5.5.dist-info}/WHEEL +0 -0
  177. {clarifai-11.5.3rc2.dist-info → clarifai-11.5.5.dist-info}/entry_points.txt +0 -0
  178. {clarifai-11.5.3rc2.dist-info → clarifai-11.5.5.dist-info}/licenses/LICENSE +0 -0
  179. {clarifai-11.5.3rc2.dist-info → clarifai-11.5.5.dist-info}/top_level.txt +0 -0
clarifai/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "11.5.3rc2"
1
+ __version__ = "11.5.5"
clarifai/client/model.py CHANGED
@@ -528,7 +528,10 @@ class Model(Lister, BaseClient):
528
528
  inference_params = kwargs.get('inference_params', {})
529
529
  output_config = kwargs.get('output_config', {})
530
530
  return self.client._predict_by_proto(
531
- inputs=inputs, inference_params=inference_params, output_config=output_config
531
+ inputs=inputs,
532
+ # method_name="PostModelOutputs",
533
+ inference_params=inference_params,
534
+ output_config=output_config,
532
535
  )
533
536
 
534
537
  return self.client.predict(*args, **kwargs)
@@ -457,8 +457,8 @@ class ModelBuilder:
457
457
  Returns the method signatures for the model class in YAML format.
458
458
  """
459
459
  model_class = self.load_model_class(mocking=True)
460
- method_info = model_class._get_method_info()
461
- signatures = {method.name: method.signature for method in method_info.values()}
460
+ method_infos = model_class._get_method_infos()
461
+ signatures = {method.name: method.signature for method in method_infos.values()}
462
462
  return signatures_to_yaml(signatures)
463
463
 
464
464
  def get_method_signatures(self, mocking=True):
@@ -472,8 +472,8 @@ class ModelBuilder:
472
472
  list: A list of method signatures for the model class.
473
473
  """
474
474
  model_class = self.load_model_class(mocking=mocking)
475
- method_info = model_class._get_method_info()
476
- signatures = [method.signature for method in method_info.values()]
475
+ method_infos = model_class._get_method_infos()
476
+ signatures = [method.signature for method in method_infos.values()]
477
477
  return signatures
478
478
 
479
479
  @property
@@ -1,6 +1,5 @@
1
1
  import inspect
2
2
  import itertools
3
- import logging
4
3
  import os
5
4
  import traceback
6
5
  from abc import ABC
@@ -20,11 +19,16 @@ from clarifai.runners.utils.method_signatures import (
20
19
  serialize,
21
20
  signatures_to_json,
22
21
  )
22
+ from clarifai.runners.utils.model_utils import is_proto_style_method
23
+ from clarifai.utils.logging import logger
23
24
 
24
25
  _METHOD_INFO_ATTR = '_cf_method_info'
25
26
 
26
27
  _RAISE_EXCEPTIONS = os.getenv("RAISE_EXCEPTIONS", "false").lower() in ("true", "1")
27
28
 
29
+ FALLBACK_METHOD_PROTO = 'PostModelOutputs'
30
+ FALLBACK_METHOD_PYTHON = 'predict'
31
+
28
32
 
29
33
  class ModelClass(ABC):
30
34
  '''
@@ -69,8 +73,12 @@ class ModelClass(ABC):
69
73
  """Load the model."""
70
74
 
71
75
  def _handle_get_signatures_request(self) -> service_pb2.MultiOutputResponse:
72
- methods = self._get_method_info()
73
- signatures = {method.name: method.signature for method in methods.values()}
76
+ methods = self._get_method_infos()
77
+ signatures = {
78
+ method.name: method.signature
79
+ for method in methods.values()
80
+ if method.signature is not None
81
+ }
74
82
  resp = service_pb2.MultiOutputResponse(
75
83
  status=status_pb2.Status(code=status_code_pb2.SUCCESS)
76
84
  )
@@ -99,18 +107,61 @@ class ModelClass(ABC):
99
107
  outputs = []
100
108
  try:
101
109
  # TODO add method name field to proto
102
- method_name = 'predict'
110
+ # to support old callers who might not pass in the method name we have a few defaults.
111
+ # first we look for a PostModelOutputs method that is implemented as protos and use that
112
+ # if it exists.
113
+ # if not we default to 'predict'.
114
+ method_name = None
103
115
  if len(request.inputs) > 0 and '_method_name' in request.inputs[0].data.metadata:
104
116
  method_name = request.inputs[0].data.metadata['_method_name']
117
+ if method_name is None and FALLBACK_METHOD_PROTO in self._get_method_infos():
118
+ _info = self._get_method_infos(FALLBACK_METHOD_PROTO)
119
+ if _info.proto_method:
120
+ method_name = FALLBACK_METHOD_PROTO
121
+ if method_name is None:
122
+ method_name = FALLBACK_METHOD_PYTHON
105
123
  if (
106
124
  method_name == '_GET_SIGNATURES'
107
125
  ): # special case to fetch signatures, TODO add endpoint for this
108
126
  return self._handle_get_signatures_request()
109
- if method_name not in self._get_method_info():
127
+ if method_name not in self._get_method_infos():
110
128
  raise ValueError(f"Method {method_name} not found in model class")
111
129
  method = getattr(self, method_name)
112
- method_info = method._cf_method_info
130
+ method_info = self._get_method_infos(method_name)
113
131
  signature = method_info.signature
132
+ proto_method = method_info.proto_method
133
+
134
+ # If this is an old predict(proto) -> proto method, just call it and return
135
+ # the response.
136
+ if proto_method:
137
+ out_proto = method(request)
138
+ # if we already have out_proto.status.code set then return
139
+ if out_proto.status.code != status_code_pb2.ZERO:
140
+ return out_proto
141
+
142
+ successes = [
143
+ out.status.code == status_code_pb2.SUCCESS for out in out_proto.outputs
144
+ ]
145
+ if all(successes):
146
+ # If all outputs are successful, we can return the response.
147
+ out_proto.status.CopyFrom(
148
+ status_pb2.Status(code=status_code_pb2.SUCCESS, description='Success')
149
+ )
150
+ return out_proto
151
+ if any(successes):
152
+ # If some outputs are successful and some are not, we return a mixed status.
153
+ out_proto.status.CopyFrom(
154
+ status_pb2.Status(
155
+ code=status_code_pb2.MIXED_STATUS, description='Mixed Status'
156
+ )
157
+ )
158
+ return out_proto
159
+ # If all outputs are failures, we return a failure status.
160
+ out_proto.status.CopyFrom(
161
+ status_pb2.Status(code=status_code_pb2.FAILURE, description='Failed')
162
+ )
163
+ return out_proto
164
+
114
165
  python_param_types = method_info.python_param_types
115
166
  for input in request.inputs:
116
167
  # check if input is in old format
@@ -121,6 +172,7 @@ class ModelClass(ABC):
121
172
  input.data, signature.input_fields
122
173
  )
123
174
  input.data.CopyFrom(new_data)
175
+
124
176
  # convert inputs to python types
125
177
  inputs = self._convert_input_protos_to_python(
126
178
  request.inputs, signature.input_fields, python_param_types
@@ -148,7 +200,7 @@ class ModelClass(ABC):
148
200
  except Exception as e:
149
201
  if _RAISE_EXCEPTIONS:
150
202
  raise
151
- logging.exception("Error in predict")
203
+ logger.exception("Error in predict")
152
204
  return service_pb2.MultiOutputResponse(
153
205
  status=status_pb2.Status(
154
206
  code=status_code_pb2.FAILURE,
@@ -165,7 +217,7 @@ class ModelClass(ABC):
165
217
  if len(request.inputs) > 0 and '_method_name' in request.inputs[0].data.metadata:
166
218
  method_name = request.inputs[0].data.metadata['_method_name']
167
219
  method = getattr(self, method_name)
168
- method_info = method._cf_method_info
220
+ method_info = self._get_method_infos(method_name)
169
221
  signature = method_info.signature
170
222
  python_param_types = method_info.python_param_types
171
223
  for input in request.inputs:
@@ -207,7 +259,7 @@ class ModelClass(ABC):
207
259
  except Exception as e:
208
260
  if _RAISE_EXCEPTIONS:
209
261
  raise
210
- logging.exception("Error in generate")
262
+ logger.exception("Error in generate")
211
263
  yield service_pb2.MultiOutputResponse(
212
264
  status=status_pb2.Status(
213
265
  code=status_code_pb2.FAILURE,
@@ -227,7 +279,7 @@ class ModelClass(ABC):
227
279
  if len(request.inputs) > 0 and '_method_name' in request.inputs[0].data.metadata:
228
280
  method_name = request.inputs[0].data.metadata['_method_name']
229
281
  method = getattr(self, method_name)
230
- method_info = method._cf_method_info
282
+ method_info = self._get_method_infos(method_name)
231
283
  signature = method_info.signature
232
284
  python_param_types = method_info.python_param_types
233
285
 
@@ -282,7 +334,7 @@ class ModelClass(ABC):
282
334
  except Exception as e:
283
335
  if _RAISE_EXCEPTIONS:
284
336
  raise
285
- logging.exception("Error in stream")
337
+ logger.exception("Error in stream")
286
338
  yield service_pb2.MultiOutputResponse(
287
339
  status=status_pb2.Status(
288
340
  code=status_code_pb2.FAILURE,
@@ -359,28 +411,44 @@ class ModelClass(ABC):
359
411
  continue
360
412
  methods[name] = method_info
361
413
  # check for generic predict(request) -> response, etc. methods
362
- # for name in ('predict', 'generate', 'stream'):
363
- # if hasattr(cls, name):
364
- # method = getattr(cls, name)
365
- # if not hasattr(method, _METHOD_INFO_ATTR): # not already put in registry
366
- # methods[name] = _MethodInfo(method)
414
+ # older models never had generate or stream so don't bother with them.
415
+ for name in [FALLBACK_METHOD_PROTO]: # , 'GenerateModelOutputs', 'StreamModelOutputs'):
416
+ if hasattr(cls, name) and name not in methods:
417
+ method = getattr(cls, name)
418
+ if not callable(method):
419
+ continue
420
+ if is_proto_style_method(method):
421
+ # If this is a proto-style method, we can add it to the registry as a special case.
422
+ methods[name] = _MethodInfo(method, proto_method=True)
367
423
  # set method table for this class in the registry
368
424
  return methods
369
425
 
370
426
  @classmethod
371
- def _get_method_info(cls, func_name=None):
427
+ def _get_method_infos(cls, func_name=None):
428
+ # FIXME: this is a re-use of the _METHOD_INFO_ATTR attribute to store the method info
429
+ # for all methods on the class. Should use a different attribute name to avoid confusion.
372
430
  if not hasattr(cls, _METHOD_INFO_ATTR):
373
431
  setattr(cls, _METHOD_INFO_ATTR, cls._register_model_methods())
374
- method_info = getattr(cls, _METHOD_INFO_ATTR)
432
+ method_infos = getattr(cls, _METHOD_INFO_ATTR)
375
433
  if func_name:
376
- return method_info[func_name]
377
- return method_info
434
+ return method_infos[func_name]
435
+ return method_infos
378
436
 
379
437
 
380
438
  class _MethodInfo:
381
- def __init__(self, method):
439
+ def __init__(self, method, proto_method=False):
440
+ """Initialize a MethodInfo instance.
441
+
442
+ Args:
443
+ method: The method to wrap.
444
+ old_method: If True, this is an old proto-style method that returns a proto directly.
445
+ """
382
446
  self.name = method.__name__
383
- self.signature = build_function_signature(method)
447
+ self.proto_method = proto_method
448
+ if not proto_method:
449
+ self.signature = build_function_signature(method)
450
+ else:
451
+ self.signature = None
384
452
  self.python_param_types = {
385
453
  p.name: p.annotation
386
454
  for p in inspect.signature(method).parameters.values()
@@ -5,6 +5,8 @@ from clarifai_grpc.grpc.api.status import status_code_pb2, status_pb2
5
5
  from clarifai_protocol import BaseRunner
6
6
  from clarifai_protocol.utils.health import HealthProbeRequestHandler
7
7
 
8
+ from clarifai.client.auth.helper import ClarifaiAuthHelper
9
+
8
10
  from ..utils.url_fetcher import ensure_urls_downloaded
9
11
  from .model_class import ModelClass
10
12
 
@@ -42,6 +44,28 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
42
44
  )
43
45
  self.model = model
44
46
 
47
+ # Store authentication parameters for URL fetching
48
+ self._user_id = user_id
49
+ self._pat = pat
50
+ self._token = token
51
+ self._base_url = base_url
52
+
53
+ # Create auth helper if we have sufficient authentication information
54
+ self._auth_helper = None
55
+ if self._user_id and (self._pat or self._token):
56
+ try:
57
+ self._auth_helper = ClarifaiAuthHelper(
58
+ user_id=self._user_id,
59
+ app_id="", # app_id not needed for URL fetching
60
+ pat=self._pat or "",
61
+ token=self._token or "",
62
+ base=self._base_url,
63
+ validate=False, # Don't validate since app_id is empty
64
+ )
65
+ except Exception:
66
+ # If auth helper creation fails, proceed without authentication
67
+ self._auth_helper = None
68
+
45
69
  # After model load successfully set the health probe to ready and startup
46
70
  HealthProbeRequestHandler.is_ready = True
47
71
  HealthProbeRequestHandler.is_startup = True
@@ -81,12 +105,22 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
81
105
  if not runner_item.HasField('post_model_outputs_request'):
82
106
  raise Exception("Unexpected work item type: {}".format(runner_item))
83
107
  request = runner_item.post_model_outputs_request
84
- ensure_urls_downloaded(request)
108
+ ensure_urls_downloaded(request, auth_helper=self._auth_helper)
85
109
 
86
110
  resp = self.model.predict_wrapper(request)
87
- if resp.status.code != status_code_pb2.SUCCESS:
111
+ # if we have any non-successful code already it's an error we can return.
112
+ if (
113
+ resp.status.code != status_code_pb2.SUCCESS
114
+ and resp.status.code != status_code_pb2.ZERO
115
+ ):
88
116
  return service_pb2.RunnerItemOutput(multi_output_response=resp)
89
- successes = [o.status.code == status_code_pb2.SUCCESS for o in resp.outputs]
117
+ successes = []
118
+ for output in resp.outputs:
119
+ if not output.HasField('status') or not output.status.code:
120
+ raise Exception(
121
+ "Output must have a status code, please check the model implementation."
122
+ )
123
+ successes.append(output.status.code == status_code_pb2.SUCCESS)
90
124
  if all(successes):
91
125
  status = status_pb2.Status(
92
126
  code=status_code_pb2.SUCCESS,
@@ -114,10 +148,14 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
114
148
  if not runner_item.HasField('post_model_outputs_request'):
115
149
  raise Exception("Unexpected work item type: {}".format(runner_item))
116
150
  request = runner_item.post_model_outputs_request
117
- ensure_urls_downloaded(request)
151
+ ensure_urls_downloaded(request, auth_helper=self._auth_helper)
118
152
 
119
153
  for resp in self.model.generate_wrapper(request):
120
- if resp.status.code != status_code_pb2.SUCCESS:
154
+ # if we have any non-successful code already it's an error we can return.
155
+ if (
156
+ resp.status.code != status_code_pb2.SUCCESS
157
+ and resp.status.code != status_code_pb2.ZERO
158
+ ):
121
159
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
122
160
  continue
123
161
  successes = []
@@ -151,7 +189,11 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
151
189
  ) -> Iterator[service_pb2.RunnerItemOutput]:
152
190
  # Call the generate() method the underlying model implements.
153
191
  for resp in self.model.stream_wrapper(pmo_iterator(runner_item_iterator)):
154
- if resp.status.code != status_code_pb2.SUCCESS:
192
+ # if we have any non-successful code already it's an error we can return.
193
+ if (
194
+ resp.status.code != status_code_pb2.SUCCESS
195
+ and resp.status.code != status_code_pb2.ZERO
196
+ ):
155
197
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
156
198
  continue
157
199
  successes = []
@@ -181,9 +223,9 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
181
223
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
182
224
 
183
225
 
184
- def pmo_iterator(runner_item_iterator):
226
+ def pmo_iterator(runner_item_iterator, auth_helper=None):
185
227
  for runner_item in runner_item_iterator:
186
228
  if not runner_item.HasField('post_model_outputs_request'):
187
229
  raise Exception("Unexpected work item type: {}".format(runner_item))
188
- ensure_urls_downloaded(runner_item.post_model_outputs_request)
230
+ ensure_urls_downloaded(runner_item.post_model_outputs_request, auth_helper=auth_helper)
189
231
  yield runner_item.post_model_outputs_request
@@ -5,6 +5,8 @@ from typing import Iterator
5
5
  from clarifai_grpc.grpc.api import service_pb2, service_pb2_grpc
6
6
  from clarifai_grpc.grpc.api.status import status_code_pb2, status_pb2
7
7
 
8
+ from clarifai.client.auth.helper import ClarifaiAuthHelper
9
+
8
10
  from ..utils.url_fetcher import ensure_urls_downloaded
9
11
 
10
12
  _RAISE_EXCEPTIONS = os.getenv("RAISE_EXCEPTIONS", "false").lower() in ("true", "1")
@@ -23,6 +25,27 @@ class ModelServicer(service_pb2_grpc.V2Servicer):
23
25
  """
24
26
  self.model = model
25
27
 
28
+ # Try to create auth helper from environment variables if available
29
+ self._auth_helper = None
30
+ try:
31
+ user_id = os.environ.get("CLARIFAI_USER_ID")
32
+ pat = os.environ.get("CLARIFAI_PAT")
33
+ token = os.environ.get("CLARIFAI_SESSION_TOKEN")
34
+ base_url = os.environ.get("CLARIFAI_API_BASE", "https://api.clarifai.com")
35
+
36
+ if user_id and (pat or token):
37
+ self._auth_helper = ClarifaiAuthHelper(
38
+ user_id=user_id,
39
+ app_id="", # app_id not needed for URL fetching
40
+ pat=pat or "",
41
+ token=token or "",
42
+ base=base_url,
43
+ validate=False, # Don't validate since app_id is empty
44
+ )
45
+ except Exception:
46
+ # If auth helper creation fails, proceed without authentication
47
+ self._auth_helper = None
48
+
26
49
  def PostModelOutputs(
27
50
  self, request: service_pb2.PostModelOutputsRequest, context=None
28
51
  ) -> service_pb2.MultiOutputResponse:
@@ -32,7 +55,7 @@ class ModelServicer(service_pb2_grpc.V2Servicer):
32
55
  """
33
56
 
34
57
  # Download any urls that are not already bytes.
35
- ensure_urls_downloaded(request)
58
+ ensure_urls_downloaded(request, auth_helper=self._auth_helper)
36
59
 
37
60
  try:
38
61
  return self.model.predict_wrapper(request)
@@ -56,7 +79,7 @@ class ModelServicer(service_pb2_grpc.V2Servicer):
56
79
  returns an output.
57
80
  """
58
81
  # Download any urls that are not already bytes.
59
- ensure_urls_downloaded(request)
82
+ ensure_urls_downloaded(request, auth_helper=self._auth_helper)
60
83
 
61
84
  try:
62
85
  yield from self.model.generate_wrapper(request)
@@ -84,7 +107,7 @@ class ModelServicer(service_pb2_grpc.V2Servicer):
84
107
 
85
108
  # Download any urls that are not already bytes.
86
109
  for req in request:
87
- ensure_urls_downloaded(req)
110
+ ensure_urls_downloaded(req, auth_helper=self._auth_helper)
88
111
 
89
112
  try:
90
113
  yield from self.model.stream_wrapper(request_copy)
@@ -18,7 +18,9 @@ def has_signature_method(
18
18
  :param method_signatures: List of MethodSignature objects to search in.
19
19
  :return: True if a method with the given name exists, False otherwise.
20
20
  """
21
- return any(method_signature.name == name for method_signature in method_signatures)
21
+ return any(
22
+ method_signature.name == name for method_signature in method_signatures if method_signature
23
+ )
22
24
 
23
25
 
24
26
  def generate_client_script(
@@ -153,6 +155,8 @@ model = Model("{model_ui_url}",
153
155
  # Generate method signatures
154
156
  method_signatures_str = []
155
157
  for method_signature in method_signatures:
158
+ if method_signature is None:
159
+ continue
156
160
  method_name = method_signature.name
157
161
  client_script_str = f'response = model.{method_name}('
158
162
  annotations = _get_annotations_source(method_signature)
@@ -174,13 +174,10 @@ class Text(MessageData):
174
174
 
175
175
 
176
176
  class Concept(MessageData):
177
- def __init__(self, name: str, value: float = 1.0, id: str = None,
178
- user_id: str = None, app_id: str = None):
177
+ def __init__(self, name: str, value: float = 1.0, id: str = None):
179
178
  self.name = name
180
179
  self.value = value
181
180
  self.id = id or Concept._concept_name_to_id(name)
182
- self.user_id = user_id
183
- self.app_id = app_id
184
181
 
185
182
  @staticmethod
186
183
  def _concept_name_to_id(name: str):
@@ -189,14 +186,14 @@ class Concept(MessageData):
189
186
  return _name
190
187
 
191
188
  def __repr__(self) -> str:
192
- return f"Concept(id={self.id!r}, name={self.name!r}, value={self.value!r}, user_id={self.user_id!r}, app_id={self.app_id!r})"
189
+ return f"Concept(id={self.id!r}, name={self.name!r}, value={self.value})"
193
190
 
194
191
  def to_proto(self):
195
- return ConceptProto(id=self.id, name=self.name, value=self.value, user_id=self.user_id, app_id=self.app_id)
192
+ return ConceptProto(id=self.id, name=self.name, value=self.value)
196
193
 
197
194
  @classmethod
198
195
  def from_proto(cls, proto: ConceptProto) -> "Concept":
199
- return cls(id=proto.id, name=proto.name, value=proto.value, user_id=proto.user_id, app_id=proto.app_id)
196
+ return cls(id=proto.id, name=proto.name, value=proto.value)
200
197
 
201
198
 
202
199
  class Region(MessageData):
@@ -210,7 +207,6 @@ class Region(MessageData):
210
207
  track_id: str = None,
211
208
  text: str = None,
212
209
  id: str = None,
213
- value: float = None,
214
210
  ):
215
211
  if proto_region is None:
216
212
  self.proto = RegionProto()
@@ -227,8 +223,6 @@ class Region(MessageData):
227
223
  self.track_id = track_id
228
224
  if text:
229
225
  self.text = text
230
- if value:
231
- self.value = value
232
226
  self.id = id if id is not None else uuid.uuid4().hex
233
227
  elif isinstance(proto_region, RegionProto):
234
228
  self.proto = proto_region
@@ -328,19 +322,8 @@ class Region(MessageData):
328
322
  point_proto = PointProto(col=x, row=y, z=z)
329
323
  self.proto.region_info.point.CopyFrom(point_proto)
330
324
 
331
- @property
332
- def value(self) -> float:
333
- """Value of the region, typically used for confidence scores."""
334
- return self.proto.value
335
-
336
- @value.setter
337
- def value(self, value: float):
338
- if not isinstance(value, (int, float)):
339
- raise TypeError(f"Expected int or float for value, got {type(value).__name__}")
340
- self.proto.value = float(value)
341
-
342
325
  def __repr__(self) -> str:
343
- return f"Region(id={self.id},box={self.box or []}, concepts={self.concepts or None}, point={self.point or None}, mask={self.mask or None}, track_id={self.track_id or None}, value={self.value or None})"
326
+ return f"Region(id={self.id},box={self.box or []}, concepts={self.concepts or None}, point={self.point or None}, mask={self.mask or None}, track_id={self.track_id or None})"
344
327
 
345
328
  def to_proto(self) -> RegionProto:
346
329
  return self.proto
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  import os
2
3
  import shlex
3
4
  import signal
@@ -8,6 +9,7 @@ import time
8
9
 
9
10
  import psutil
10
11
  import requests
12
+ from clarifai_grpc.grpc.api import service_pb2
11
13
 
12
14
  from clarifai.utils.logging import logger
13
15
 
@@ -133,3 +135,50 @@ def wait_for_server(base_url: str, timeout: int = None) -> None:
133
135
  raise TimeoutError("Server did not become ready within timeout period")
134
136
  except requests.exceptions.RequestException:
135
137
  time.sleep(1)
138
+
139
+
140
+ def is_proto_style_method(method):
141
+ """
142
+ Determines if the given method is likely an old-style proto method:
143
+ - Has a 'request' parameter after 'self'
144
+ - Optionally, returns a known proto response type
145
+ """
146
+ try:
147
+ sig = inspect.signature(method)
148
+ params = list(sig.parameters.values())
149
+
150
+ # Must have at least 'self' and one argument
151
+ if len(params) < 2:
152
+ return False
153
+
154
+ # First parameter should be 'self'
155
+ if params[0].name != 'self':
156
+ return False
157
+ # Second param typically should be named 'request'
158
+ request_param = params[1]
159
+ if request_param.name != 'request':
160
+ return False
161
+ # Optionally: check annotation is a proto type
162
+ # (If signature is incomplete, this part will gracefully fall through)
163
+ return_annotation = sig.return_annotation
164
+ # If type annotation is available, check it's PostModelOutputsRequest
165
+ if (
166
+ request_param.annotation != inspect.Parameter.empty
167
+ and request_param.annotation != service_pb2.PostModelOutputsRequest
168
+ ):
169
+ return False
170
+ # If return annotation is available, check it's MultiOutputResponse
171
+ if (
172
+ return_annotation != inspect.Signature.empty
173
+ and return_annotation != service_pb2.MultiOutputResponse
174
+ ):
175
+ return False
176
+ if (
177
+ request_param.annotation is inspect.Parameter.empty
178
+ and return_annotation is inspect.Signature.empty
179
+ ):
180
+ return True # signature OK, even if signature is empty
181
+ return True
182
+
183
+ except (ValueError, TypeError):
184
+ return False
@@ -5,43 +5,88 @@ import fsspec
5
5
  from clarifai.utils.logging import logger
6
6
 
7
7
 
8
- def download_input(input):
9
- _download_input_data(input.data)
8
+ def download_input(input, auth_helper=None):
9
+ _download_input_data(input.data, auth_helper=auth_helper)
10
10
  if input.data.parts:
11
11
  for i in range(len(input.data.parts)):
12
- _download_input_data(input.data.parts[i].data)
12
+ _download_input_data(input.data.parts[i].data, auth_helper=auth_helper)
13
13
 
14
14
 
15
- def _download_input_data(input_data):
15
+ def _download_input_data(input_data, auth_helper=None):
16
16
  """
17
17
  This function will download any urls that are not already bytes.
18
+
19
+ Args:
20
+ input_data: The input data containing URLs to download
21
+ auth_helper: Optional ClarifaiAuthHelper instance for authentication
18
22
  """
23
+ # Get auth headers if auth_helper is provided
24
+ auth_kwargs = {}
25
+ if auth_helper is not None:
26
+ auth_kwargs = _get_auth_kwargs(auth_helper)
27
+
19
28
  if input_data.image.url and not input_data.image.base64:
20
29
  # Download the image
21
- with fsspec.open(input_data.image.url, 'rb') as f:
30
+ with fsspec.open(input_data.image.url, 'rb', **auth_kwargs) as f:
22
31
  input_data.image.base64 = f.read()
23
32
  if input_data.video.url and not input_data.video.base64:
24
33
  # Download the video
25
- with fsspec.open(input_data.video.url, 'rb') as f:
34
+ with fsspec.open(input_data.video.url, 'rb', **auth_kwargs) as f:
26
35
  input_data.video.base64 = f.read()
27
36
  if input_data.audio.url and not input_data.audio.base64:
28
37
  # Download the audio
29
- with fsspec.open(input_data.audio.url, 'rb') as f:
38
+ with fsspec.open(input_data.audio.url, 'rb', **auth_kwargs) as f:
30
39
  input_data.audio.base64 = f.read()
31
40
  if input_data.text.url and not input_data.text.raw:
32
41
  # Download the text
33
- with fsspec.open(input_data.text.url, 'r') as f:
42
+ with fsspec.open(input_data.text.url, 'r', **auth_kwargs) as f:
34
43
  input_data.text.raw = f.read()
35
44
 
36
45
 
37
- def ensure_urls_downloaded(request, max_threads=128):
46
+ def _get_auth_kwargs(auth_helper):
47
+ """
48
+ Convert ClarifaiAuthHelper metadata to fsspec-compatible kwargs.
49
+
50
+ Args:
51
+ auth_helper: ClarifaiAuthHelper instance
52
+
53
+ Returns:
54
+ dict: kwargs to pass to fsspec.open() for authentication
55
+ """
56
+ if auth_helper is None:
57
+ return {}
58
+
59
+ try:
60
+ # Get authentication metadata from the helper
61
+ metadata = auth_helper.metadata
62
+
63
+ # Convert gRPC metadata tuples to HTTP headers dict
64
+ headers = {}
65
+ for key, value in metadata:
66
+ # Skip non-auth headers
67
+ if key in ('authorization', 'x-clarifai-session-token'):
68
+ headers[key] = value
69
+
70
+ # Return fsspec-compatible kwargs
71
+ return {'client_kwargs': {'headers': headers}}
72
+ except Exception as e:
73
+ logger.warning(f"Failed to get authentication headers: {e}")
74
+ return {}
75
+
76
+
77
+ def ensure_urls_downloaded(request, max_threads=128, auth_helper=None):
38
78
  """
39
79
  This function will download any urls that are not already bytes and parallelize with a thread pool.
80
+
81
+ Args:
82
+ request: The request containing inputs with URLs to download
83
+ max_threads: Maximum number of threads to use for parallel downloads
84
+ auth_helper: Optional ClarifaiAuthHelper instance for authentication
40
85
  """
41
86
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
42
87
  futures = []
43
88
  for input in request.inputs:
44
- futures.append(executor.submit(download_input, input))
89
+ futures.append(executor.submit(download_input, input, auth_helper))
45
90
  for future in concurrent.futures.as_completed(futures):
46
91
  try:
47
92
  future.result()