clarifai 11.1.7__py3-none-any.whl → 11.1.7rc1__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 (121) hide show
  1. clarifai/__init__.py +1 -1
  2. clarifai/__pycache__/__init__.cpython-310.pyc +0 -0
  3. clarifai/__pycache__/errors.cpython-310.pyc +0 -0
  4. clarifai/__pycache__/versions.cpython-310.pyc +0 -0
  5. clarifai/cli/__main__.py~ +4 -0
  6. clarifai/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  7. clarifai/cli/__pycache__/__main__.cpython-310.pyc +0 -0
  8. clarifai/cli/__pycache__/base.cpython-310.pyc +0 -0
  9. clarifai/cli/__pycache__/compute_cluster.cpython-310.pyc +0 -0
  10. clarifai/cli/__pycache__/deployment.cpython-310.pyc +0 -0
  11. clarifai/cli/__pycache__/model.cpython-310.pyc +0 -0
  12. clarifai/cli/__pycache__/nodepool.cpython-310.pyc +0 -0
  13. clarifai/cli/model.py +25 -0
  14. clarifai/client/#model_client.py# +430 -0
  15. clarifai/client/__pycache__/__init__.cpython-310.pyc +0 -0
  16. clarifai/client/__pycache__/app.cpython-310.pyc +0 -0
  17. clarifai/client/__pycache__/base.cpython-310.pyc +0 -0
  18. clarifai/client/__pycache__/dataset.cpython-310.pyc +0 -0
  19. clarifai/client/__pycache__/input.cpython-310.pyc +0 -0
  20. clarifai/client/__pycache__/lister.cpython-310.pyc +0 -0
  21. clarifai/client/__pycache__/model.cpython-310.pyc +0 -0
  22. clarifai/client/__pycache__/module.cpython-310.pyc +0 -0
  23. clarifai/client/__pycache__/runner.cpython-310.pyc +0 -0
  24. clarifai/client/__pycache__/search.cpython-310.pyc +0 -0
  25. clarifai/client/__pycache__/user.cpython-310.pyc +0 -0
  26. clarifai/client/__pycache__/workflow.cpython-310.pyc +0 -0
  27. clarifai/client/auth/__pycache__/__init__.cpython-310.pyc +0 -0
  28. clarifai/client/auth/__pycache__/helper.cpython-310.pyc +0 -0
  29. clarifai/client/auth/__pycache__/register.cpython-310.pyc +0 -0
  30. clarifai/client/auth/__pycache__/stub.cpython-310.pyc +0 -0
  31. clarifai/client/model.py +158 -393
  32. clarifai/client/model_client.py +449 -0
  33. clarifai/constants/__pycache__/dataset.cpython-310.pyc +0 -0
  34. clarifai/constants/__pycache__/model.cpython-310.pyc +0 -0
  35. clarifai/constants/__pycache__/search.cpython-310.pyc +0 -0
  36. clarifai/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
  37. clarifai/datasets/export/__pycache__/__init__.cpython-310.pyc +0 -0
  38. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-310.pyc +0 -0
  39. clarifai/datasets/upload/__pycache__/__init__.cpython-310.pyc +0 -0
  40. clarifai/datasets/upload/__pycache__/base.cpython-310.pyc +0 -0
  41. clarifai/datasets/upload/__pycache__/features.cpython-310.pyc +0 -0
  42. clarifai/datasets/upload/__pycache__/image.cpython-310.pyc +0 -0
  43. clarifai/datasets/upload/__pycache__/text.cpython-310.pyc +0 -0
  44. clarifai/datasets/upload/__pycache__/utils.cpython-310.pyc +0 -0
  45. clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-310.pyc +0 -0
  46. clarifai/datasets/upload/loaders/__pycache__/coco_detection.cpython-310.pyc +0 -0
  47. clarifai/models/__pycache__/__init__.cpython-310.pyc +0 -0
  48. clarifai/models/model_serving/__pycache__/__init__.cpython-310.pyc +0 -0
  49. clarifai/models/model_serving/__pycache__/constants.cpython-310.pyc +0 -0
  50. clarifai/models/model_serving/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  51. clarifai/models/model_serving/cli/__pycache__/_utils.cpython-310.pyc +0 -0
  52. clarifai/models/model_serving/cli/__pycache__/base.cpython-310.pyc +0 -0
  53. clarifai/models/model_serving/cli/__pycache__/build.cpython-310.pyc +0 -0
  54. clarifai/models/model_serving/cli/__pycache__/create.cpython-310.pyc +0 -0
  55. clarifai/models/model_serving/model_config/__pycache__/__init__.cpython-310.pyc +0 -0
  56. clarifai/models/model_serving/model_config/__pycache__/base.cpython-310.pyc +0 -0
  57. clarifai/models/model_serving/model_config/__pycache__/config.cpython-310.pyc +0 -0
  58. clarifai/models/model_serving/model_config/__pycache__/inference_parameter.cpython-310.pyc +0 -0
  59. clarifai/models/model_serving/model_config/__pycache__/output.cpython-310.pyc +0 -0
  60. clarifai/models/model_serving/model_config/triton/__pycache__/__init__.cpython-310.pyc +0 -0
  61. clarifai/models/model_serving/model_config/triton/__pycache__/serializer.cpython-310.pyc +0 -0
  62. clarifai/models/model_serving/model_config/triton/__pycache__/triton_config.cpython-310.pyc +0 -0
  63. clarifai/models/model_serving/model_config/triton/__pycache__/wrappers.cpython-310.pyc +0 -0
  64. clarifai/models/model_serving/repo_build/__pycache__/__init__.cpython-310.pyc +0 -0
  65. clarifai/models/model_serving/repo_build/__pycache__/build.cpython-310.pyc +0 -0
  66. clarifai/models/model_serving/repo_build/static_files/__pycache__/base_test.cpython-310-pytest-7.2.0.pyc +0 -0
  67. clarifai/rag/__pycache__/__init__.cpython-310.pyc +0 -0
  68. clarifai/rag/__pycache__/rag.cpython-310.pyc +0 -0
  69. clarifai/rag/__pycache__/utils.cpython-310.pyc +0 -0
  70. clarifai/runners/__init__.py +2 -7
  71. clarifai/runners/__pycache__/__init__.cpython-310.pyc +0 -0
  72. clarifai/runners/__pycache__/server.cpython-310.pyc +0 -0
  73. clarifai/runners/dockerfile_template/Dockerfile.debug +11 -0
  74. clarifai/runners/dockerfile_template/Dockerfile.debug~ +9 -0
  75. clarifai/runners/dockerfile_template/Dockerfile.template +3 -0
  76. clarifai/runners/models/__pycache__/__init__.cpython-310.pyc +0 -0
  77. clarifai/runners/models/__pycache__/base_typed_model.cpython-310.pyc +0 -0
  78. clarifai/runners/models/__pycache__/model_builder.cpython-310.pyc +0 -0
  79. clarifai/runners/models/__pycache__/model_class.cpython-310.pyc +0 -0
  80. clarifai/runners/models/__pycache__/model_run_locally.cpython-310.pyc +0 -0
  81. clarifai/runners/models/__pycache__/model_runner.cpython-310.pyc +0 -0
  82. clarifai/runners/models/__pycache__/model_servicer.cpython-310.pyc +0 -0
  83. clarifai/runners/models/__pycache__/model_upload.cpython-310.pyc +0 -0
  84. clarifai/runners/models/model_builder.py +24 -7
  85. clarifai/runners/models/model_class.py +256 -28
  86. clarifai/runners/models/model_run_locally.py +3 -78
  87. clarifai/runners/models/model_runner.py +2 -0
  88. clarifai/runners/models/model_servicer.py +11 -2
  89. clarifai/runners/utils/__pycache__/__init__.cpython-310.pyc +0 -0
  90. clarifai/runners/utils/__pycache__/const.cpython-310.pyc +0 -0
  91. clarifai/runners/utils/__pycache__/data_handler.cpython-310.pyc +0 -0
  92. clarifai/runners/utils/__pycache__/data_types.cpython-310.pyc +0 -0
  93. clarifai/runners/utils/__pycache__/data_utils.cpython-310.pyc +0 -0
  94. clarifai/runners/utils/__pycache__/loader.cpython-310.pyc +0 -0
  95. clarifai/runners/utils/__pycache__/logging.cpython-310.pyc +0 -0
  96. clarifai/runners/utils/__pycache__/method_signatures.cpython-310.pyc +0 -0
  97. clarifai/runners/utils/__pycache__/serializers.cpython-310.pyc +0 -0
  98. clarifai/runners/utils/__pycache__/url_fetcher.cpython-310.pyc +0 -0
  99. clarifai/runners/utils/data_types.py +427 -0
  100. clarifai/runners/utils/method_signatures.py +500 -0
  101. clarifai/runners/utils/serializers.py +222 -0
  102. clarifai/schema/__pycache__/search.cpython-310.pyc +0 -0
  103. clarifai/urls/__pycache__/helper.cpython-310.pyc +0 -0
  104. clarifai/utils/__pycache__/__init__.cpython-310.pyc +0 -0
  105. clarifai/utils/__pycache__/logging.cpython-310.pyc +0 -0
  106. clarifai/utils/__pycache__/misc.cpython-310.pyc +0 -0
  107. clarifai/utils/__pycache__/model_train.cpython-310.pyc +0 -0
  108. clarifai/utils/evaluation/__pycache__/__init__.cpython-310.pyc +0 -0
  109. clarifai/utils/evaluation/__pycache__/helpers.cpython-310.pyc +0 -0
  110. clarifai/utils/evaluation/__pycache__/main.cpython-310.pyc +0 -0
  111. clarifai/workflows/__pycache__/__init__.cpython-310.pyc +0 -0
  112. clarifai/workflows/__pycache__/export.cpython-310.pyc +0 -0
  113. clarifai/workflows/__pycache__/utils.cpython-310.pyc +0 -0
  114. clarifai/workflows/__pycache__/validate.cpython-310.pyc +0 -0
  115. {clarifai-11.1.7.dist-info → clarifai-11.1.7rc1.dist-info}/METADATA +16 -26
  116. clarifai-11.1.7rc1.dist-info/RECORD +205 -0
  117. {clarifai-11.1.7.dist-info → clarifai-11.1.7rc1.dist-info}/WHEEL +1 -1
  118. clarifai-11.1.7.dist-info/RECORD +0 -101
  119. {clarifai-11.1.7.dist-info → clarifai-11.1.7rc1.dist-info}/LICENSE +0 -0
  120. {clarifai-11.1.7.dist-info → clarifai-11.1.7rc1.dist-info}/entry_points.txt +0 -0
  121. {clarifai-11.1.7.dist-info → clarifai-11.1.7rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,427 @@
1
+ import io
2
+ import json
3
+ from typing import Iterable, List, get_args, get_origin
4
+
5
+ import numpy as np
6
+ from clarifai_grpc.grpc.api.resources_pb2 import Audio as AudioProto
7
+ from clarifai_grpc.grpc.api.resources_pb2 import Concept as ConceptProto
8
+ from clarifai_grpc.grpc.api.resources_pb2 import Frame as FrameProto
9
+ from clarifai_grpc.grpc.api.resources_pb2 import Image as ImageProto
10
+ from clarifai_grpc.grpc.api.resources_pb2 import Region as RegionProto
11
+ from clarifai_grpc.grpc.api.resources_pb2 import Text as TextProto
12
+ from clarifai_grpc.grpc.api.resources_pb2 import Video as VideoProto
13
+ from PIL import Image as PILImage
14
+
15
+
16
+ class MessageData:
17
+
18
+ def to_proto(self):
19
+ raise NotImplementedError
20
+
21
+ @classmethod
22
+ def from_proto(cls, proto):
23
+ raise NotImplementedError
24
+
25
+ @classmethod
26
+ def from_value(cls, value):
27
+ if isinstance(value, cls):
28
+ return value
29
+ return cls(value)
30
+
31
+ def cast(self, python_type):
32
+ if python_type == self.__class__:
33
+ return self
34
+ raise TypeError(f'Incompatible type for {self.__class__.__name__}: {python_type}')
35
+
36
+
37
+ class NamedFieldsMeta(type):
38
+ """Metaclass to create NamedFields subclasses with __annotations__ when fields are specified."""
39
+
40
+ def __call__(cls, *args, **kwargs):
41
+ # Check if keyword arguments are types (used in type annotations)
42
+ if kwargs and all(isinstance(v, type) for v in kwargs.values()):
43
+ # Dynamically create a subclass with __annotations__
44
+ name = f"NamedFields({', '.join(f'{k}:{v.__name__}' for k, v in kwargs.items())})"
45
+ return type(name, (cls,), {'__annotations__': kwargs})
46
+ else:
47
+ # Create a normal instance for runtime data
48
+ return super().__call__(*args, **kwargs)
49
+
50
+
51
+ class NamedFields(metaclass=NamedFieldsMeta):
52
+ """A class that can be used to store named fields with values."""
53
+
54
+ def __init__(self, **kwargs):
55
+ for key, value in kwargs.items():
56
+ setattr(self, key, value)
57
+
58
+ def items(self):
59
+ return self.__dict__.items()
60
+
61
+ def keys(self):
62
+ return self.__dict__.keys()
63
+
64
+ def values(self):
65
+ return self.__dict__.values()
66
+
67
+ def __contains__(self, key):
68
+ return key in self.__dict__
69
+
70
+ def __getitem__(self, key):
71
+ return getattr(self, key)
72
+
73
+ def __setitem__(self, key, value):
74
+ setattr(self, key, value)
75
+
76
+ def __repr__(self):
77
+ return f"{self.__class__.__name__}({', '.join(f'{key}={value!r}' for key, value in self.__dict__.items())})"
78
+
79
+ def __origin__(self):
80
+ return self
81
+
82
+ def __args__(self):
83
+ return list(self.keys())
84
+
85
+
86
+ class Stream(Iterable):
87
+ pass
88
+
89
+
90
+ class JSON:
91
+
92
+ def __init__(self, value):
93
+ self.value = value
94
+
95
+ def __eq__(self, other):
96
+ return self.value == other
97
+
98
+ def __bool__(self):
99
+ return bool(self.value)
100
+
101
+ def to_json(self):
102
+ return json.dumps(self.value)
103
+
104
+ @classmethod
105
+ def from_json(cls, json_str):
106
+ return cls(json.loads(json_str))
107
+
108
+ @classmethod
109
+ def from_value(cls, value):
110
+ return cls(value)
111
+
112
+ def cast(self, python_type):
113
+ if not isinstance(self.value, python_type):
114
+ raise TypeError(f'Incompatible type {type(self.value)} for {python_type}')
115
+ return self.value
116
+
117
+
118
+ class Text(MessageData):
119
+
120
+ def __init__(self, text: str, url: str = None):
121
+ self.text = text
122
+ self.url = url
123
+
124
+ def __eq__(self, other):
125
+ if isinstance(other, Text):
126
+ return self.text == other.text and self.url == other.url
127
+ if isinstance(other, str):
128
+ return self.text == other
129
+ return False
130
+
131
+ def __bool__(self):
132
+ return bool(self.text) or bool(self.url)
133
+
134
+ def to_proto(self) -> TextProto:
135
+ return TextProto(raw=self.text or '', url=self.url or '')
136
+
137
+ @classmethod
138
+ def from_proto(cls, proto: TextProto) -> "Text":
139
+ return cls(proto.raw, proto.url or None)
140
+
141
+ @classmethod
142
+ def from_value(cls, value):
143
+ if isinstance(value, str):
144
+ return cls(value)
145
+ if isinstance(value, Text):
146
+ return value
147
+ if isinstance(value, dict):
148
+ return cls(value.get('text'), value.get('url'))
149
+ raise TypeError(f'Incompatible type for Text: {type(value)}')
150
+
151
+ def cast(self, python_type):
152
+ if python_type == str:
153
+ return self.text
154
+ if python_type == Text:
155
+ return self
156
+ raise TypeError(f'Incompatible type for Text: {python_type}')
157
+
158
+
159
+ class Concept(MessageData):
160
+
161
+ def __init__(self, name: str, value: float = 0):
162
+ self.name = name
163
+ self.value = value
164
+
165
+ def __repr__(self) -> str:
166
+ return f"Concept(name={self.name!r}, value={self.value})"
167
+
168
+ def to_proto(self):
169
+ return ConceptProto(name=self.name, value=self.value)
170
+
171
+ @classmethod
172
+ def from_proto(cls, proto: ConceptProto) -> "Concept":
173
+ return cls(proto.name, proto.value)
174
+
175
+
176
+ class Region(MessageData):
177
+
178
+ def __init__(self, proto_region: RegionProto):
179
+ self.proto = proto_region
180
+
181
+ @property
182
+ def box(self) -> List[float]:
183
+ bbox = self.proto.region_info.bounding_box
184
+ return [bbox.left_col, bbox.top_row, bbox.right_col, bbox.bottom_row] # x1, y1, x2, y2
185
+
186
+ @box.setter
187
+ def box(self, value: List[float]):
188
+ bbox = self.proto.region_info.bounding_box
189
+ bbox.left_col, bbox.top_row, bbox.right_col, bbox.bottom_row = value
190
+
191
+ @property
192
+ def concepts(self) -> List[Concept]:
193
+ return [Concept.from_proto(proto) for proto in self.proto.data.concepts]
194
+
195
+ @concepts.setter
196
+ def concepts(self, value: List[Concept]):
197
+ self.proto.data.concepts.extend([concept.to_proto() for concept in value])
198
+
199
+ def __repr__(self) -> str:
200
+ return f"Region(box={self.box}, concepts={self.concepts})"
201
+
202
+ def to_proto(self) -> RegionProto:
203
+ return self.proto
204
+
205
+ @classmethod
206
+ def from_proto(cls, proto: RegionProto) -> "Region":
207
+ return cls(proto)
208
+
209
+
210
+ class Image(MessageData):
211
+
212
+ def __init__(self, proto_image: ImageProto = None, url: str = None, bytes: bytes = None):
213
+ if proto_image is None:
214
+ proto_image = ImageProto()
215
+ self.proto = proto_image
216
+ # use setters for init vals
217
+ if url:
218
+ self.url = url
219
+ if bytes:
220
+ self.bytes = bytes
221
+
222
+ @property
223
+ def url(self) -> str:
224
+ return self.proto.url
225
+
226
+ @url.setter
227
+ def url(self, value: str):
228
+ self.proto.url = value
229
+
230
+ @property
231
+ def bytes(self) -> bytes:
232
+ return self.proto.base64
233
+
234
+ @bytes.setter
235
+ def bytes(self, value: bytes):
236
+ self.proto.base64 = value
237
+
238
+ def __repr__(self) -> str:
239
+ attrs = []
240
+ if self.url:
241
+ attrs.append(f"url={self.url!r}")
242
+ if self.bytes:
243
+ attrs.append(f"bytes=<{len(self.bytes)} bytes>")
244
+ return f"Image({', '.join(attrs)})"
245
+
246
+ @classmethod
247
+ def from_url(cls, url: str) -> "Image":
248
+ proto_image = ImageProto(url=url)
249
+ return cls(proto_image)
250
+
251
+ @classmethod
252
+ def from_pil(cls, pil_image: PILImage.Image) -> "Image":
253
+ with io.BytesIO() as output:
254
+ pil_image.save(output, format="PNG")
255
+ image_bytes = output.getvalue()
256
+ proto_image = ImageProto(base64=image_bytes)
257
+ return cls(proto_image)
258
+
259
+ def to_pil(self) -> PILImage.Image:
260
+ if not self.proto.base64:
261
+ raise ValueError("Image has no bytes")
262
+ return PILImage.open(io.BytesIO(self.proto.base64))
263
+
264
+ def to_numpy(self) -> np.ndarray:
265
+ return np.asarray(self.to_pil())
266
+
267
+ def to_proto(self) -> ImageProto:
268
+ return self.proto
269
+
270
+ @classmethod
271
+ def from_proto(cls, proto: ImageProto) -> "Image":
272
+ return cls(proto)
273
+
274
+ @classmethod
275
+ def from_value(cls, value):
276
+ if isinstance(value, PILImage.Image):
277
+ return cls.from_pil(value)
278
+ if isinstance(value, Image):
279
+ return value
280
+ raise TypeError(f'Incompatible type for Image: {type(value)}')
281
+
282
+ def cast(self, python_type):
283
+ if python_type == Image:
284
+ return self
285
+ if python_type in (PILImage.Image, PILImage):
286
+ return self.to_pil()
287
+ if python_type == np.ndarray or get_origin(python_type) == np.ndarray:
288
+ return self.to_numpy()
289
+ raise TypeError(f'Incompatible type for Image: {python_type}')
290
+
291
+
292
+ class Audio(MessageData):
293
+
294
+ def __init__(self, proto_audio: AudioProto):
295
+ self.proto = proto_audio
296
+
297
+ @property
298
+ def url(self) -> str:
299
+ return self.proto.url
300
+
301
+ @url.setter
302
+ def url(self, value: str):
303
+ self.proto.url = value
304
+
305
+ @property
306
+ def bytes(self) -> bytes:
307
+ return self.proto.base64
308
+
309
+ @bytes.setter
310
+ def bytes(self, value: bytes):
311
+ self.proto.base64 = value
312
+
313
+ @classmethod
314
+ def from_url(cls, url: str) -> "Audio":
315
+ proto_audio = AudioProto(url=url)
316
+ return cls(proto_audio)
317
+
318
+ def __repr__(self) -> str:
319
+ attrs = []
320
+ if self.url:
321
+ attrs.append(f"url={self.url!r}")
322
+ if self.bytes:
323
+ attrs.append(f"bytes=<{len(self.bytes)} bytes>")
324
+ return f"Audio({', '.join(attrs)})"
325
+
326
+ def to_proto(self) -> AudioProto:
327
+ return self.proto
328
+
329
+ @classmethod
330
+ def from_proto(cls, proto: AudioProto) -> "Audio":
331
+ return cls(proto)
332
+
333
+
334
+ class Frame(MessageData):
335
+
336
+ def __init__(self, proto_frame: FrameProto):
337
+ self.proto = proto_frame
338
+
339
+ @property
340
+ def time(self) -> float:
341
+ # TODO: time is a uint32, so this will overflow at 49.7 days
342
+ # we should be using double or uint64 in the proto instead
343
+ return self.proto.frame_info.time / 1000.0
344
+
345
+ @time.setter
346
+ def time(self, value: float):
347
+ self.proto.frame_info.time = int(value * 1000)
348
+
349
+ @property
350
+ def image(self) -> Image:
351
+ return Image.from_proto(self.proto.data.image)
352
+
353
+ @image.setter
354
+ def image(self, value: Image):
355
+ self.proto.data.image.CopyFrom(value.to_proto())
356
+
357
+ @property
358
+ def regions(self) -> List[Region]:
359
+ return [Region(region) for region in self.proto.data.regions]
360
+
361
+ @regions.setter
362
+ def regions(self, value: List[Region]):
363
+ self.proto.data.regions.extend([region.proto for region in value])
364
+
365
+ def to_proto(self) -> FrameProto:
366
+ return self.proto
367
+
368
+ @classmethod
369
+ def from_proto(cls, proto: FrameProto) -> "Frame":
370
+ return cls(proto)
371
+
372
+
373
+ class Video(MessageData):
374
+
375
+ def __init__(self, proto_video: VideoProto):
376
+ self.proto = proto_video
377
+
378
+ @property
379
+ def url(self) -> str:
380
+ return self.proto.url
381
+
382
+ @url.setter
383
+ def url(self, value: str):
384
+ self.proto.url = value
385
+
386
+ @property
387
+ def bytes(self) -> bytes:
388
+ return self.proto.base64
389
+
390
+ @bytes.setter
391
+ def bytes(self, value: bytes):
392
+ self.proto.base64 = value
393
+
394
+ @classmethod
395
+ def from_url(cls, url: str) -> "Video":
396
+ proto_video = VideoProto(url=url)
397
+ return cls(proto_video)
398
+
399
+ def __repr__(self) -> str:
400
+ attrs = []
401
+ if self.url:
402
+ attrs.append(f"url={self.url!r}")
403
+ if self.bytes:
404
+ attrs.append(f"bytes=<{len(self.bytes)} bytes>")
405
+ return f"Video({', '.join(attrs)})"
406
+
407
+ def to_proto(self) -> VideoProto:
408
+ return self.proto
409
+
410
+ @classmethod
411
+ def from_proto(cls, proto: VideoProto) -> "Video":
412
+ return cls(proto)
413
+
414
+
415
+ def cast(value, python_type):
416
+ list_type = (get_origin(python_type) == list)
417
+ if isinstance(value, MessageData):
418
+ return value.cast(python_type)
419
+ if list_type and isinstance(value, np.ndarray):
420
+ return value.tolist()
421
+ if list_type and isinstance(value, list):
422
+ if get_args(python_type):
423
+ inner_type = get_args(python_type)[0]
424
+ return [cast(item, inner_type) for item in value]
425
+ if not isinstance(value, Iterable):
426
+ raise TypeError(f'Expected list, got {type(value)}')
427
+ return value