Skip to content

Commit c186bf3

Browse files
committed
Merge branch 'master' into update-region
2 parents c15899c + 7155d1b commit c186bf3

15 files changed

+442
-52
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [[11.3.0]](https://github.com/Clarifai/clarifai-python/releases/tag/11.3.0) - [PyPI](https://pypi.org/project/clarifai/11.3.0/) - 2025-04-22
2+
3+
### Changed
4+
- We support pythonic models now. See [runners-examples](https://github.com/clarifai/runners-examples) [(#525)](https://github.com/Clarifai/clarifai-python/pull/525)
5+
- Fixed failing tests. [(#559)](https://github.com/Clarifai/clarifai-python/pull/559)
6+
17
## [[11.2.3]](https://github.com/Clarifai/clarifai-python/releases/tag/11.2.3) - [PyPI](https://pypi.org/project/clarifai/11.2.3/) - 2025-04-08
28

39
### Changed

clarifai/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "11.2.3"
1+
__version__ = "11.3.0"

clarifai/cli/model.py

+26
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,32 @@ def download_checkpoints(model_path, out_path, stage):
6969
builder.download_checkpoints(stage=stage, checkpoint_path_override=out_path)
7070

7171

72+
@model.command()
73+
@click.argument(
74+
"model_path",
75+
type=click.Path(exists=True),
76+
required=False,
77+
default=".",
78+
)
79+
@click.option(
80+
'--out_path',
81+
type=click.Path(exists=False),
82+
required=False,
83+
default=None,
84+
help='Path to write the method signature defitions to. If not provided, use stdout.')
85+
def signatures(model_path, out_path):
86+
"""Generate method signatures for the model."""
87+
88+
from clarifai.runners.models.model_builder import ModelBuilder
89+
builder = ModelBuilder(model_path, download_validation_only=True)
90+
signatures = builder.method_signatures_yaml()
91+
if out_path:
92+
with open(out_path, 'w') as f:
93+
f.write(signatures)
94+
else:
95+
click.echo(signatures)
96+
97+
7298
@model.command()
7399
@click.argument(
74100
"model_path",

clarifai/client/deployment.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from clarifai.client.base import BaseClient
44
from clarifai.client.lister import Lister
55
from clarifai.utils.logging import logger
6+
from clarifai.utils.protobuf import dict_to_protobuf
67

78

89
class Deployment(Lister, BaseClient):
@@ -28,7 +29,8 @@ def __init__(self,
2829
**kwargs: Additional keyword arguments to be passed to the deployment.
2930
"""
3031
self.kwargs = {**kwargs, 'id': deployment_id, 'user_id': user_id}
31-
self.deployment_info = resources_pb2.Deployment(**self.kwargs)
32+
self.deployment_info = resources_pb2.Deployment()
33+
dict_to_protobuf(self.deployment_info, self.kwargs)
3234
self.logger = logger
3335
BaseClient.__init__(
3436
self,

clarifai/client/model.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from clarifai.utils.model_train import (find_and_replace_key, params_parser,
3333
response_to_model_params, response_to_param_info,
3434
response_to_templates)
35-
35+
from clarifai.utils.protobuf import dict_to_protobuf
3636
MAX_SIZE_PER_STREAM = int(89_128_960) # 85GiB
3737
MIN_CHUNK_FOR_UPLOAD_FILE = int(5_242_880) # 5MiB
3838
MAX_CHUNK_FOR_UPLOAD_FILE = int(5_242_880_000) # 5GiB
@@ -73,8 +73,11 @@ def __init__(self,
7373
user_id, app_id, _, model_id, model_version_id = ClarifaiUrlHelper.split_clarifai_url(url)
7474
model_version = {'id': model_version_id}
7575
kwargs = {'user_id': user_id, 'app_id': app_id}
76+
7677
self.kwargs = {**kwargs, 'id': model_id, 'model_version': model_version, }
77-
self.model_info = resources_pb2.Model(**self.kwargs)
78+
self.model_info = resources_pb2.Model()
79+
dict_to_protobuf(self.model_info, self.kwargs)
80+
7881
self.logger = logger
7982
self.training_params = {}
8083
self.input_types = None
@@ -983,7 +986,8 @@ def load_info(self) -> None:
983986

984987
dict_response = MessageToDict(response, preserving_proto_field_name=True)
985988
self.kwargs = self.process_response_keys(dict_response['model'])
986-
self.model_info = resources_pb2.Model(**self.kwargs)
989+
self.model_info = resources_pb2.Model()
990+
dict_to_protobuf(self.model_info, self.kwargs)
987991

988992
def __str__(self):
989993
if len(self.kwargs) < 10:

clarifai/client/model_client.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ def _predict(
259259
inputs, # TODO set up functions according to fetched signatures?
260260
method_name: str = 'predict',
261261
) -> Any:
262-
263262
input_signature = self._method_signatures[method_name].input_fields
264263
output_signature = self._method_signatures[method_name].output_fields
265264

@@ -328,7 +327,7 @@ def _predict_by_proto(
328327
response = self.STUB.PostModelOutputs(request)
329328
if status_is_retryable(
330329
response.status.code) and time.time() - start_time < 60 * 10: # 10 minutes
331-
logger.info(f"Model predict failed with response {response!r}")
330+
logger.info("Model is still deploying, please wait...")
332331
time.sleep(next(backoff_iterator))
333332
continue
334333

clarifai/runners/models/model_builder.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ def _clear_line(n: int = 1) -> None:
4343
print(LINE_UP, end=LINE_CLEAR, flush=True)
4444

4545

46+
def is_related(object_class, main_class):
47+
# Check if the object_class is a subclass of main_class
48+
if issubclass(object_class, main_class):
49+
return True
50+
51+
# Check if the object_class is a subclass of any of the parent classes of main_class
52+
parent_classes = object_class.__bases__
53+
for parent in parent_classes:
54+
if main_class in parent.__bases__:
55+
return True
56+
return False
57+
58+
4659
class ModelBuilder:
4760
DEFAULT_CHECKPOINT_SIZE = 50 * 1024**3 # 50 GiB
4861

@@ -125,7 +138,7 @@ def custom_import(name, globals=None, locals=None, fromlist=(), level=0):
125138
# Find all classes in the model.py file that are subclasses of ModelClass
126139
classes = [
127140
cls for _, cls in inspect.getmembers(module, inspect.isclass)
128-
if issubclass(cls, ModelClass) and cls.__module__ == module.__name__
141+
if is_related(cls, ModelClass) and cls.__module__ == module.__name__
129142
]
130143
# Ensure there is exactly one subclass of BaseRunner in the model.py file
131144
if len(classes) != 1:

clarifai/runners/models/model_class.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections.abc as abc
12
import inspect
23
import itertools
34
import logging
@@ -271,8 +272,9 @@ def _convert_input_protos_to_python(self, inputs: List[resources_pb2.Input],
271272
if k not in python_param_types:
272273
continue
273274

274-
if hasattr(python_param_types[k], "__args__") and getattr(python_param_types[k],
275-
"__origin__", None) == Iterator:
275+
if hasattr(python_param_types[k],
276+
"__args__") and (getattr(python_param_types[k], "__origin__",
277+
None) in [abc.Iterator, abc.Generator, abc.Iterable]):
276278
# get the type of the items in the stream
277279
stream_type = python_param_types[k].__args__[0]
278280

clarifai/runners/utils/data_utils.py

+65-16
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,7 @@ def to_proto(self, proto=None) -> InputFieldProto:
115115
# proto.is_param = self.is_param
116116

117117
if self.default is not None:
118-
if isinstance(self.default, str) or isinstance(self.default, bool) or isinstance(
119-
self.default, (int, float)):
120-
proto.default = str(self.default)
121-
else:
122-
import json
123-
proto.default = json.dumps(self.default)
118+
proto = self.set_default(proto, self.default)
124119

125120
return proto
126121

@@ -169,17 +164,57 @@ def from_proto(cls, proto):
169164

170165
@classmethod
171166
def set_default(cls, proto=None, default=None):
172-
173-
if proto is None:
174-
proto = InputFieldProto()
175-
if default is not None:
176-
if isinstance(default, str) or isinstance(default, bool) or isinstance(
177-
default, (int, float)):
178-
proto.default = str(default)
179-
else:
180-
import json
167+
try:
168+
import json
169+
if proto is None:
170+
proto = InputFieldProto()
171+
if default is not None:
181172
proto.default = json.dumps(default)
182-
return proto
173+
return proto
174+
except Exception:
175+
if default is not None:
176+
proto.default = str(default)
177+
return proto
178+
except Exception as e:
179+
raise ValueError(
180+
f"Error setting default value of type, {type(default)} and value: {default}: {e}")
181+
182+
@classmethod
183+
def get_default(cls, proto):
184+
default_str = proto.default
185+
default = None
186+
import json
187+
try:
188+
# Attempt to parse as JSON first (for complex types)
189+
return json.loads(default_str)
190+
except json.JSONDecodeError:
191+
pass
192+
# Check for boolean values stored as "True" or "False"
193+
if proto.type == resources_pb2.ModelTypeField.DataType.BOOL:
194+
try:
195+
default = bool(default_str)
196+
except ValueError:
197+
pass
198+
# Try to parse as integer
199+
elif proto.type == resources_pb2.ModelTypeField.DataType.INT:
200+
try:
201+
default = int(default_str)
202+
except ValueError:
203+
pass
204+
205+
# Try to parse as float
206+
elif proto.type == resources_pb2.ModelTypeField.DataType.FLOAT:
207+
try:
208+
default = float(default_str)
209+
except ValueError:
210+
pass
211+
elif proto.type == resources_pb2.ModelTypeField.DataType.STR:
212+
default = default_str
213+
214+
if default is None:
215+
# If all parsing fails, return the string value
216+
default = default_str
217+
return default
183218

184219

185220
class DataConverter:
@@ -211,54 +246,68 @@ def _convert_field(cls, old_data: resources_pb2.Data,
211246
if data_type == resources_pb2.ModelTypeField.DataType.STR:
212247
if old_data.HasField('text'):
213248
new_data.string_value = old_data.text.raw
249+
old_data.ClearField('text')
214250
return new_data
215251
elif data_type == resources_pb2.ModelTypeField.DataType.IMAGE:
216252
if old_data.HasField('image'):
217253
new_data.image.CopyFrom(old_data.image)
254+
# Clear the old field to avoid duplication
255+
old_data.ClearField('image')
218256
return new_data
219257
elif data_type == resources_pb2.ModelTypeField.DataType.VIDEO:
220258
if old_data.HasField('video'):
221259
new_data.video.CopyFrom(old_data.video)
260+
old_data.ClearField('video')
222261
return new_data
223262
elif data_type == resources_pb2.ModelTypeField.DataType.BOOL:
224263
if old_data.bool_value is not False:
225264
new_data.bool_value = old_data.bool_value
265+
old_data.bool_value = False
226266
return new_data
227267
elif data_type == resources_pb2.ModelTypeField.DataType.INT:
228268
if old_data.int_value != 0:
229269
new_data.int_value = old_data.int_value
270+
old_data.int_value = 0
230271
return new_data
231272
elif data_type == resources_pb2.ModelTypeField.DataType.FLOAT:
232273
if old_data.float_value != 0.0:
233274
new_data.float_value = old_data.float_value
275+
old_data.float_value = 0.0
234276
return new_data
235277
elif data_type == resources_pb2.ModelTypeField.DataType.BYTES:
236278
if old_data.bytes_value != b"":
237279
new_data.bytes_value = old_data.bytes_value
280+
old_data.bytes_value = b""
238281
return new_data
239282
elif data_type == resources_pb2.ModelTypeField.DataType.NDARRAY:
240283
if old_data.HasField('ndarray'):
241284
new_data.ndarray.CopyFrom(old_data.ndarray)
285+
old_data.ClearField('ndarray')
242286
return new_data
243287
elif data_type == resources_pb2.ModelTypeField.DataType.TEXT:
244288
if old_data.HasField('text'):
245289
new_data.text.CopyFrom(old_data.text)
290+
old_data.ClearField('text')
246291
return new_data
247292
elif data_type == resources_pb2.ModelTypeField.DataType.AUDIO:
248293
if old_data.HasField('audio'):
249294
new_data.audio.CopyFrom(old_data.audio)
295+
old_data.ClearField('audio')
250296
return new_data
251297
elif data_type == resources_pb2.ModelTypeField.DataType.CONCEPT:
252298
if old_data.concepts:
253299
new_data.concepts.extend(old_data.concepts)
300+
old_data.ClearField('concepts')
254301
return new_data
255302
elif data_type == resources_pb2.ModelTypeField.DataType.REGION:
256303
if old_data.regions:
257304
new_data.regions.extend(old_data.regions)
305+
old_data.ClearField('regions')
258306
return new_data
259307
elif data_type == resources_pb2.ModelTypeField.DataType.FRAME:
260308
if old_data.frames:
261309
new_data.frames.extend(old_data.frames)
310+
old_data.ClearField('frames')
262311
return new_data
263312
elif data_type == resources_pb2.ModelTypeField.DataType.LIST:
264313
if not field.type_args:

0 commit comments

Comments
 (0)