pixeltable 0.2.26__py3-none-any.whl → 0.5.7__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 (245) hide show
  1. pixeltable/__init__.py +83 -19
  2. pixeltable/_query.py +1444 -0
  3. pixeltable/_version.py +1 -0
  4. pixeltable/catalog/__init__.py +7 -4
  5. pixeltable/catalog/catalog.py +2394 -119
  6. pixeltable/catalog/column.py +225 -104
  7. pixeltable/catalog/dir.py +38 -9
  8. pixeltable/catalog/globals.py +53 -34
  9. pixeltable/catalog/insertable_table.py +265 -115
  10. pixeltable/catalog/path.py +80 -17
  11. pixeltable/catalog/schema_object.py +28 -43
  12. pixeltable/catalog/table.py +1270 -677
  13. pixeltable/catalog/table_metadata.py +103 -0
  14. pixeltable/catalog/table_version.py +1270 -751
  15. pixeltable/catalog/table_version_handle.py +109 -0
  16. pixeltable/catalog/table_version_path.py +137 -42
  17. pixeltable/catalog/tbl_ops.py +53 -0
  18. pixeltable/catalog/update_status.py +191 -0
  19. pixeltable/catalog/view.py +251 -134
  20. pixeltable/config.py +215 -0
  21. pixeltable/env.py +736 -285
  22. pixeltable/exceptions.py +26 -2
  23. pixeltable/exec/__init__.py +7 -2
  24. pixeltable/exec/aggregation_node.py +39 -21
  25. pixeltable/exec/cache_prefetch_node.py +87 -109
  26. pixeltable/exec/cell_materialization_node.py +268 -0
  27. pixeltable/exec/cell_reconstruction_node.py +168 -0
  28. pixeltable/exec/component_iteration_node.py +25 -28
  29. pixeltable/exec/data_row_batch.py +11 -46
  30. pixeltable/exec/exec_context.py +26 -11
  31. pixeltable/exec/exec_node.py +35 -27
  32. pixeltable/exec/expr_eval/__init__.py +3 -0
  33. pixeltable/exec/expr_eval/evaluators.py +365 -0
  34. pixeltable/exec/expr_eval/expr_eval_node.py +413 -0
  35. pixeltable/exec/expr_eval/globals.py +200 -0
  36. pixeltable/exec/expr_eval/row_buffer.py +74 -0
  37. pixeltable/exec/expr_eval/schedulers.py +413 -0
  38. pixeltable/exec/globals.py +35 -0
  39. pixeltable/exec/in_memory_data_node.py +35 -27
  40. pixeltable/exec/object_store_save_node.py +293 -0
  41. pixeltable/exec/row_update_node.py +44 -29
  42. pixeltable/exec/sql_node.py +414 -115
  43. pixeltable/exprs/__init__.py +8 -5
  44. pixeltable/exprs/arithmetic_expr.py +79 -45
  45. pixeltable/exprs/array_slice.py +5 -5
  46. pixeltable/exprs/column_property_ref.py +40 -26
  47. pixeltable/exprs/column_ref.py +254 -61
  48. pixeltable/exprs/comparison.py +14 -9
  49. pixeltable/exprs/compound_predicate.py +9 -10
  50. pixeltable/exprs/data_row.py +213 -72
  51. pixeltable/exprs/expr.py +270 -104
  52. pixeltable/exprs/expr_dict.py +6 -5
  53. pixeltable/exprs/expr_set.py +20 -11
  54. pixeltable/exprs/function_call.py +383 -284
  55. pixeltable/exprs/globals.py +18 -5
  56. pixeltable/exprs/in_predicate.py +7 -7
  57. pixeltable/exprs/inline_expr.py +37 -37
  58. pixeltable/exprs/is_null.py +8 -4
  59. pixeltable/exprs/json_mapper.py +120 -54
  60. pixeltable/exprs/json_path.py +90 -60
  61. pixeltable/exprs/literal.py +61 -16
  62. pixeltable/exprs/method_ref.py +7 -6
  63. pixeltable/exprs/object_ref.py +19 -8
  64. pixeltable/exprs/row_builder.py +238 -75
  65. pixeltable/exprs/rowid_ref.py +53 -15
  66. pixeltable/exprs/similarity_expr.py +65 -50
  67. pixeltable/exprs/sql_element_cache.py +5 -5
  68. pixeltable/exprs/string_op.py +107 -0
  69. pixeltable/exprs/type_cast.py +25 -13
  70. pixeltable/exprs/variable.py +2 -2
  71. pixeltable/func/__init__.py +9 -5
  72. pixeltable/func/aggregate_function.py +197 -92
  73. pixeltable/func/callable_function.py +119 -35
  74. pixeltable/func/expr_template_function.py +101 -48
  75. pixeltable/func/function.py +375 -62
  76. pixeltable/func/function_registry.py +20 -19
  77. pixeltable/func/globals.py +6 -5
  78. pixeltable/func/mcp.py +74 -0
  79. pixeltable/func/query_template_function.py +151 -35
  80. pixeltable/func/signature.py +178 -49
  81. pixeltable/func/tools.py +164 -0
  82. pixeltable/func/udf.py +176 -53
  83. pixeltable/functions/__init__.py +44 -4
  84. pixeltable/functions/anthropic.py +226 -47
  85. pixeltable/functions/audio.py +148 -11
  86. pixeltable/functions/bedrock.py +137 -0
  87. pixeltable/functions/date.py +188 -0
  88. pixeltable/functions/deepseek.py +113 -0
  89. pixeltable/functions/document.py +81 -0
  90. pixeltable/functions/fal.py +76 -0
  91. pixeltable/functions/fireworks.py +72 -20
  92. pixeltable/functions/gemini.py +249 -0
  93. pixeltable/functions/globals.py +208 -53
  94. pixeltable/functions/groq.py +108 -0
  95. pixeltable/functions/huggingface.py +1088 -95
  96. pixeltable/functions/image.py +155 -84
  97. pixeltable/functions/json.py +8 -11
  98. pixeltable/functions/llama_cpp.py +31 -19
  99. pixeltable/functions/math.py +169 -0
  100. pixeltable/functions/mistralai.py +50 -75
  101. pixeltable/functions/net.py +70 -0
  102. pixeltable/functions/ollama.py +29 -36
  103. pixeltable/functions/openai.py +548 -160
  104. pixeltable/functions/openrouter.py +143 -0
  105. pixeltable/functions/replicate.py +15 -14
  106. pixeltable/functions/reve.py +250 -0
  107. pixeltable/functions/string.py +310 -85
  108. pixeltable/functions/timestamp.py +37 -19
  109. pixeltable/functions/together.py +77 -120
  110. pixeltable/functions/twelvelabs.py +188 -0
  111. pixeltable/functions/util.py +7 -2
  112. pixeltable/functions/uuid.py +30 -0
  113. pixeltable/functions/video.py +1528 -117
  114. pixeltable/functions/vision.py +26 -26
  115. pixeltable/functions/voyageai.py +289 -0
  116. pixeltable/functions/whisper.py +19 -10
  117. pixeltable/functions/whisperx.py +179 -0
  118. pixeltable/functions/yolox.py +112 -0
  119. pixeltable/globals.py +716 -236
  120. pixeltable/index/__init__.py +3 -1
  121. pixeltable/index/base.py +17 -21
  122. pixeltable/index/btree.py +32 -22
  123. pixeltable/index/embedding_index.py +155 -92
  124. pixeltable/io/__init__.py +12 -7
  125. pixeltable/io/datarows.py +140 -0
  126. pixeltable/io/external_store.py +83 -125
  127. pixeltable/io/fiftyone.py +24 -33
  128. pixeltable/io/globals.py +47 -182
  129. pixeltable/io/hf_datasets.py +96 -127
  130. pixeltable/io/label_studio.py +171 -156
  131. pixeltable/io/lancedb.py +3 -0
  132. pixeltable/io/pandas.py +136 -115
  133. pixeltable/io/parquet.py +40 -153
  134. pixeltable/io/table_data_conduit.py +702 -0
  135. pixeltable/io/utils.py +100 -0
  136. pixeltable/iterators/__init__.py +8 -4
  137. pixeltable/iterators/audio.py +207 -0
  138. pixeltable/iterators/base.py +9 -3
  139. pixeltable/iterators/document.py +144 -87
  140. pixeltable/iterators/image.py +17 -38
  141. pixeltable/iterators/string.py +15 -12
  142. pixeltable/iterators/video.py +523 -127
  143. pixeltable/metadata/__init__.py +33 -8
  144. pixeltable/metadata/converters/convert_10.py +2 -3
  145. pixeltable/metadata/converters/convert_13.py +2 -2
  146. pixeltable/metadata/converters/convert_15.py +15 -11
  147. pixeltable/metadata/converters/convert_16.py +4 -5
  148. pixeltable/metadata/converters/convert_17.py +4 -5
  149. pixeltable/metadata/converters/convert_18.py +4 -6
  150. pixeltable/metadata/converters/convert_19.py +6 -9
  151. pixeltable/metadata/converters/convert_20.py +3 -6
  152. pixeltable/metadata/converters/convert_21.py +6 -8
  153. pixeltable/metadata/converters/convert_22.py +3 -2
  154. pixeltable/metadata/converters/convert_23.py +33 -0
  155. pixeltable/metadata/converters/convert_24.py +55 -0
  156. pixeltable/metadata/converters/convert_25.py +19 -0
  157. pixeltable/metadata/converters/convert_26.py +23 -0
  158. pixeltable/metadata/converters/convert_27.py +29 -0
  159. pixeltable/metadata/converters/convert_28.py +13 -0
  160. pixeltable/metadata/converters/convert_29.py +110 -0
  161. pixeltable/metadata/converters/convert_30.py +63 -0
  162. pixeltable/metadata/converters/convert_31.py +11 -0
  163. pixeltable/metadata/converters/convert_32.py +15 -0
  164. pixeltable/metadata/converters/convert_33.py +17 -0
  165. pixeltable/metadata/converters/convert_34.py +21 -0
  166. pixeltable/metadata/converters/convert_35.py +9 -0
  167. pixeltable/metadata/converters/convert_36.py +38 -0
  168. pixeltable/metadata/converters/convert_37.py +15 -0
  169. pixeltable/metadata/converters/convert_38.py +39 -0
  170. pixeltable/metadata/converters/convert_39.py +124 -0
  171. pixeltable/metadata/converters/convert_40.py +73 -0
  172. pixeltable/metadata/converters/convert_41.py +12 -0
  173. pixeltable/metadata/converters/convert_42.py +9 -0
  174. pixeltable/metadata/converters/convert_43.py +44 -0
  175. pixeltable/metadata/converters/util.py +44 -18
  176. pixeltable/metadata/notes.py +21 -0
  177. pixeltable/metadata/schema.py +185 -42
  178. pixeltable/metadata/utils.py +74 -0
  179. pixeltable/mypy/__init__.py +3 -0
  180. pixeltable/mypy/mypy_plugin.py +123 -0
  181. pixeltable/plan.py +616 -225
  182. pixeltable/share/__init__.py +3 -0
  183. pixeltable/share/packager.py +797 -0
  184. pixeltable/share/protocol/__init__.py +33 -0
  185. pixeltable/share/protocol/common.py +165 -0
  186. pixeltable/share/protocol/operation_types.py +33 -0
  187. pixeltable/share/protocol/replica.py +119 -0
  188. pixeltable/share/publish.py +349 -0
  189. pixeltable/store.py +398 -232
  190. pixeltable/type_system.py +730 -267
  191. pixeltable/utils/__init__.py +40 -0
  192. pixeltable/utils/arrow.py +201 -29
  193. pixeltable/utils/av.py +298 -0
  194. pixeltable/utils/azure_store.py +346 -0
  195. pixeltable/utils/coco.py +26 -27
  196. pixeltable/utils/code.py +4 -4
  197. pixeltable/utils/console_output.py +46 -0
  198. pixeltable/utils/coroutine.py +24 -0
  199. pixeltable/utils/dbms.py +92 -0
  200. pixeltable/utils/description_helper.py +11 -12
  201. pixeltable/utils/documents.py +60 -61
  202. pixeltable/utils/exception_handler.py +36 -0
  203. pixeltable/utils/filecache.py +38 -22
  204. pixeltable/utils/formatter.py +88 -51
  205. pixeltable/utils/gcs_store.py +295 -0
  206. pixeltable/utils/http.py +133 -0
  207. pixeltable/utils/http_server.py +14 -13
  208. pixeltable/utils/iceberg.py +13 -0
  209. pixeltable/utils/image.py +17 -0
  210. pixeltable/utils/lancedb.py +90 -0
  211. pixeltable/utils/local_store.py +322 -0
  212. pixeltable/utils/misc.py +5 -0
  213. pixeltable/utils/object_stores.py +573 -0
  214. pixeltable/utils/pydantic.py +60 -0
  215. pixeltable/utils/pytorch.py +20 -20
  216. pixeltable/utils/s3_store.py +527 -0
  217. pixeltable/utils/sql.py +32 -5
  218. pixeltable/utils/system.py +30 -0
  219. pixeltable/utils/transactional_directory.py +4 -3
  220. pixeltable-0.5.7.dist-info/METADATA +579 -0
  221. pixeltable-0.5.7.dist-info/RECORD +227 -0
  222. {pixeltable-0.2.26.dist-info → pixeltable-0.5.7.dist-info}/WHEEL +1 -1
  223. pixeltable-0.5.7.dist-info/entry_points.txt +2 -0
  224. pixeltable/__version__.py +0 -3
  225. pixeltable/catalog/named_function.py +0 -36
  226. pixeltable/catalog/path_dict.py +0 -141
  227. pixeltable/dataframe.py +0 -894
  228. pixeltable/exec/expr_eval_node.py +0 -232
  229. pixeltable/ext/__init__.py +0 -14
  230. pixeltable/ext/functions/__init__.py +0 -8
  231. pixeltable/ext/functions/whisperx.py +0 -77
  232. pixeltable/ext/functions/yolox.py +0 -157
  233. pixeltable/tool/create_test_db_dump.py +0 -311
  234. pixeltable/tool/create_test_video.py +0 -81
  235. pixeltable/tool/doc_plugins/griffe.py +0 -50
  236. pixeltable/tool/doc_plugins/mkdocstrings.py +0 -6
  237. pixeltable/tool/doc_plugins/templates/material/udf.html.jinja +0 -135
  238. pixeltable/tool/embed_udf.py +0 -9
  239. pixeltable/tool/mypy_plugin.py +0 -55
  240. pixeltable/utils/media_store.py +0 -76
  241. pixeltable/utils/s3.py +0 -16
  242. pixeltable-0.2.26.dist-info/METADATA +0 -400
  243. pixeltable-0.2.26.dist-info/RECORD +0 -156
  244. pixeltable-0.2.26.dist-info/entry_points.txt +0 -3
  245. {pixeltable-0.2.26.dist-info → pixeltable-0.5.7.dist-info/licenses}/LICENSE +0 -0
@@ -1,26 +1,29 @@
1
+ # ruff: noqa: F401
2
+
1
3
  from .arithmetic_expr import ArithmeticExpr
2
4
  from .array_slice import ArraySlice
3
5
  from .column_property_ref import ColumnPropertyRef
4
6
  from .column_ref import ColumnRef
5
7
  from .comparison import Comparison
6
8
  from .compound_predicate import CompoundPredicate
7
- from .data_row import DataRow
9
+ from .data_row import ArrayMd, BinaryMd, CellMd, DataRow
8
10
  from .expr import Expr
9
11
  from .expr_dict import ExprDict
10
12
  from .expr_set import ExprSet
11
13
  from .function_call import FunctionCall
14
+ from .globals import ArithmeticOperator, ComparisonOperator, LogicalOperator
12
15
  from .in_predicate import InPredicate
13
16
  from .inline_expr import InlineArray, InlineDict, InlineList
14
17
  from .is_null import IsNull
15
- from .json_mapper import JsonMapper
16
- from .json_path import RELATIVE_PATH_ROOT, JsonPath
18
+ from .json_mapper import JsonMapper, JsonMapperDispatch
19
+ from .json_path import JsonPath
17
20
  from .literal import Literal
18
21
  from .method_ref import MethodRef
19
22
  from .object_ref import ObjectRef
20
- from .row_builder import RowBuilder, ColumnSlotIdx, ExecProfile
23
+ from .row_builder import ColumnSlotIdx, ExecProfile, RowBuilder
21
24
  from .rowid_ref import RowidRef
22
25
  from .similarity_expr import SimilarityExpr
23
26
  from .sql_element_cache import SqlElementCache
27
+ from .string_op import StringOp
24
28
  from .type_cast import TypeCast
25
29
  from .variable import Variable
26
- from .globals import ComparisonOperator, LogicalOperator, ArithmeticOperator
@@ -1,15 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Optional
3
+ from typing import Any
4
4
 
5
5
  import sqlalchemy as sql
6
6
 
7
- import pixeltable.exceptions as excs
8
- import pixeltable.type_system as ts
7
+ from pixeltable import env, exceptions as excs, type_system as ts
9
8
 
10
9
  from .data_row import DataRow
11
10
  from .expr import Expr
12
11
  from .globals import ArithmeticOperator
12
+ from .literal import Literal
13
13
  from .row_builder import RowBuilder
14
14
  from .sql_element_cache import SqlElementCache
15
15
 
@@ -18,6 +18,9 @@ class ArithmeticExpr(Expr):
18
18
  """
19
19
  Allows arithmetic exprs on json paths
20
20
  """
21
+
22
+ operator: ArithmeticOperator
23
+
21
24
  def __init__(self, operator: ArithmeticOperator, op1: Expr, op2: Expr):
22
25
  if op1.col_type.is_json_type() or op2.col_type.is_json_type() or operator == ArithmeticOperator.DIV:
23
26
  # we assume it's a float
@@ -35,49 +38,56 @@ class ArithmeticExpr(Expr):
35
38
 
36
39
  self.id = self._create_id()
37
40
 
41
+ @property
42
+ def _op1(self) -> Expr:
43
+ return self.components[0]
44
+
45
+ @property
46
+ def _op2(self) -> Expr:
47
+ return self.components[1]
48
+
38
49
  def __repr__(self) -> str:
39
50
  # add parentheses around operands that are ArithmeticExprs to express precedence
40
51
  op1_str = f'({self._op1})' if isinstance(self._op1, ArithmeticExpr) else str(self._op1)
41
52
  op2_str = f'({self._op2})' if isinstance(self._op2, ArithmeticExpr) else str(self._op2)
42
- return f'{op1_str} {str(self.operator)} {op2_str}'
53
+ return f'{op1_str} {self.operator} {op2_str}'
43
54
 
44
55
  def _equals(self, other: ArithmeticExpr) -> bool:
45
56
  return self.operator == other.operator
46
57
 
47
58
  def _id_attrs(self) -> list[tuple[str, Any]]:
48
- return super()._id_attrs() + [('operator', self.operator.value)]
49
-
50
- @property
51
- def _op1(self) -> Expr:
52
- return self.components[0]
59
+ return [*super()._id_attrs(), ('operator', self.operator.value)]
53
60
 
54
- @property
55
- def _op2(self) -> Expr:
56
- return self.components[1]
57
-
58
- def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
61
+ def sql_expr(self, sql_elements: SqlElementCache) -> sql.ColumnElement | None:
59
62
  assert self.col_type.is_int_type() or self.col_type.is_float_type() or self.col_type.is_json_type()
60
63
  left = sql_elements.get(self._op1)
61
64
  right = sql_elements.get(self._op2)
62
65
  if left is None or right is None:
63
66
  return None
64
- if self.operator == ArithmeticOperator.ADD:
65
- return left + right
66
- if self.operator == ArithmeticOperator.SUB:
67
- return left - right
68
- if self.operator == ArithmeticOperator.MUL:
69
- return left * right
67
+ if self.operator in (ArithmeticOperator.ADD, ArithmeticOperator.SUB, ArithmeticOperator.MUL):
68
+ if env.Env.get().is_using_cockroachdb and self._op1.col_type != self._op2.col_type:
69
+ if self._op1.col_type != self.col_type:
70
+ left = sql.cast(left, self.col_type.to_sa_type())
71
+ if self._op2.col_type != self.col_type:
72
+ right = sql.cast(right, self.col_type.to_sa_type())
73
+ if self.operator == ArithmeticOperator.ADD:
74
+ return left + right
75
+ if self.operator == ArithmeticOperator.SUB:
76
+ return left - right
77
+ if self.operator == ArithmeticOperator.MUL:
78
+ return left * right
70
79
  if self.operator == ArithmeticOperator.DIV:
71
80
  assert self.col_type.is_float_type()
72
- # Avoid DivisionByZero: if right is 0, make this a NULL
81
+ # Avoid division by zero errors by converting any zero divisor to NULL.
73
82
  # TODO: Should we cast the NULLs to NaNs when they are retrieved back into Python?
74
- nullif = sql.sql.func.nullif(right, 0)
75
- # We have to cast to a `float`, or else we'll get a `Decimal`
76
- return sql.sql.expression.cast(left / nullif, sql.Float)
83
+ # These casts cause the computation to take place in float units, rather than DECIMAL.
84
+ nullif = sql.cast(sql.func.nullif(right, 0), self.col_type.to_sa_type())
85
+ return sql.cast(left, self.col_type.to_sa_type()) / nullif
77
86
  if self.operator == ArithmeticOperator.MOD:
78
87
  if self.col_type.is_int_type():
79
- nullif = sql.sql.func.nullif(right, 0)
80
- return left % nullif
88
+ # Avoid division by zero errors by converting any zero divisor to NULL.
89
+ nullif1 = sql.cast(sql.func.nullif(right, 0), self.col_type.to_sa_type())
90
+ return left % nullif1
81
91
  if self.col_type.is_float_type():
82
92
  # Postgres does not support modulus for floats
83
93
  return None
@@ -87,42 +97,66 @@ class ArithmeticExpr(Expr):
87
97
  # We need the behavior to be consistent, so that expressions will evaluate the same way
88
98
  # whether or not their operands can be translated to SQL. These SQL clauses should
89
99
  # mimic the behavior of Python's // operator.
90
- nullif = sql.sql.func.nullif(right, 0)
91
- if self.col_type.is_int_type():
92
- return sql.sql.expression.cast(sql.func.floor(left / nullif), sql.Integer)
93
- if self.col_type.is_float_type():
94
- return sql.sql.expression.cast(sql.func.floor(left / nullif), sql.Float)
95
- assert False
100
+ # Avoid division by zero errors by converting any zero divisor to NULL.
101
+ nullif = sql.cast(sql.func.nullif(right, 0), self.col_type.to_sa_type())
102
+ return sql.func.floor(sql.cast(left, self.col_type.to_sa_type()) / nullif)
103
+ raise AssertionError()
96
104
 
97
105
  def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
98
106
  op1_val = data_row[self._op1.slot_idx]
99
107
  op2_val = data_row[self._op2.slot_idx]
100
108
 
101
109
  # if one or both columns is JsonTyped, we need a dynamic check that they are numeric
102
- if self._op1.col_type.is_json_type() and not isinstance(op1_val, int) and not isinstance(op1_val, float):
110
+ if self._op1.col_type.is_json_type() and op1_val is not None and not isinstance(op1_val, (int, float)):
103
111
  raise excs.Error(
104
- f'{self.operator} requires numeric type, but {self._op1} has type {type(op1_val).__name__}')
105
- if self._op2.col_type.is_json_type() and not isinstance(op2_val, int) and not isinstance(op2_val, float):
112
+ f'{self.operator} requires numeric types, but {self._op1} has type {type(op1_val).__name__}'
113
+ )
114
+ if self._op2.col_type.is_json_type() and op2_val is not None and not isinstance(op2_val, (int, float)):
106
115
  raise excs.Error(
107
- f'{self.operator} requires numeric type, but {self._op2} has type {type(op2_val).__name__}')
116
+ f'{self.operator} requires numeric types, but {self._op2} has type {type(op2_val).__name__}'
117
+ )
108
118
 
109
- # if either operand is None, always return None
119
+ data_row[self.slot_idx] = self.eval_nullable(op1_val, op2_val)
120
+
121
+ def eval_nullable(self, op1_val: float | None, op2_val: float | None) -> float | None:
122
+ """
123
+ Return the result of evaluating the expression on two nullable int/float operands,
124
+ None is interpreted as SQL NULL
125
+ """
110
126
  if op1_val is None or op2_val is None:
111
- data_row[self.slot_idx] = None
112
- return
127
+ return None
128
+ return self.eval_non_null(op1_val, op2_val)
113
129
 
130
+ def eval_non_null(self, op1_val: float, op2_val: float) -> float:
131
+ """
132
+ Return the result of evaluating the expression on two int/float operands
133
+ """
114
134
  if self.operator == ArithmeticOperator.ADD:
115
- data_row[self.slot_idx] = op1_val + op2_val
135
+ return op1_val + op2_val
116
136
  elif self.operator == ArithmeticOperator.SUB:
117
- data_row[self.slot_idx] = op1_val - op2_val
137
+ return op1_val - op2_val
118
138
  elif self.operator == ArithmeticOperator.MUL:
119
- data_row[self.slot_idx] = op1_val * op2_val
139
+ return op1_val * op2_val
120
140
  elif self.operator == ArithmeticOperator.DIV:
121
- data_row[self.slot_idx] = op1_val / op2_val
141
+ return op1_val / op2_val
122
142
  elif self.operator == ArithmeticOperator.MOD:
123
- data_row[self.slot_idx] = op1_val % op2_val
143
+ return op1_val % op2_val
124
144
  elif self.operator == ArithmeticOperator.FLOORDIV:
125
- data_row[self.slot_idx] = op1_val // op2_val
145
+ return op1_val // op2_val
146
+
147
+ def as_literal(self) -> Literal | None:
148
+ op1_lit = self._op1.as_literal()
149
+ if op1_lit is None:
150
+ return None
151
+ op2_lit = self._op2.as_literal()
152
+ if op2_lit is None:
153
+ return None
154
+ op1_val = op1_lit.val
155
+ assert op1_lit.col_type.is_numeric_type() or op1_val is None
156
+ op2_val = op2_lit.val
157
+ assert op2_lit.col_type.is_numeric_type() or op2_val is None
158
+
159
+ return Literal(self.eval_nullable(op1_val, op2_val), self.col_type) # type: ignore[arg-type]
126
160
 
127
161
  def _as_dict(self) -> dict:
128
162
  return {'operator': self.operator.value, **super()._as_dict()}
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Optional, Union
3
+ from typing import Any
4
4
 
5
5
  import sqlalchemy as sql
6
6
 
@@ -15,7 +15,8 @@ class ArraySlice(Expr):
15
15
  """
16
16
  Slice operation on an array, eg, t.array_col[:, 1:2].
17
17
  """
18
- def __init__(self, arr: Expr, index: tuple[Union[int, slice], ...]):
18
+
19
+ def __init__(self, arr: Expr, index: tuple[int | slice, ...]):
19
20
  assert arr.col_type.is_array_type()
20
21
  # determine result type
21
22
  super().__init__(arr.col_type)
@@ -40,9 +41,9 @@ class ArraySlice(Expr):
40
41
  return self.index == other.index
41
42
 
42
43
  def _id_attrs(self) -> list[tuple[str, Any]]:
43
- return super()._id_attrs() + [('index', self.index)]
44
+ return [*super()._id_attrs(), ('index', self.index)]
44
45
 
45
- def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
46
+ def sql_expr(self, _: SqlElementCache) -> sql.ColumnElement | None:
46
47
  return None
47
48
 
48
49
  def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
@@ -68,4 +69,3 @@ class ArraySlice(Expr):
68
69
  else:
69
70
  index.append(el)
70
71
  return cls(components[0], tuple(index))
71
-
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import enum
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
 
6
6
  import sqlalchemy as sql
7
7
 
8
8
  import pixeltable.type_system as ts
9
9
  from pixeltable import catalog
10
+
10
11
  from .column_ref import ColumnRef
11
12
  from .data_row import DataRow
12
13
  from .expr import Expr
@@ -19,11 +20,13 @@ class ColumnPropertyRef(Expr):
19
20
 
20
21
  The properties themselves are type-specific and may or may not need to reference the underlying column data.
21
22
  """
23
+
22
24
  class Property(enum.Enum):
23
25
  ERRORTYPE = 0
24
26
  ERRORMSG = 1
25
27
  FILEURL = 2
26
28
  LOCALPATH = 3
29
+ CELLMD = 4 # JSON metadata for the cell, e.g. errortype, errormsg for media columns
27
30
 
28
31
  def __init__(self, col_ref: ColumnRef, prop: Property):
29
32
  super().__init__(ts.StringType(nullable=True))
@@ -31,69 +34,81 @@ class ColumnPropertyRef(Expr):
31
34
  self.prop = prop
32
35
  self.id = self._create_id()
33
36
 
34
- def default_column_name(self) -> Optional[str]:
37
+ def default_column_name(self) -> str | None:
35
38
  return str(self).replace('.', '_')
36
39
 
37
40
  def _equals(self, other: ColumnPropertyRef) -> bool:
38
41
  return self.prop == other.prop
39
42
 
40
43
  def _id_attrs(self) -> list[tuple[str, Any]]:
41
- return super()._id_attrs() + [('prop', self.prop.value)]
44
+ return [*super()._id_attrs(), ('prop', self.prop.value)]
42
45
 
43
46
  @property
44
- def _col_ref(self) -> ColumnRef:
47
+ def col_ref(self) -> ColumnRef:
45
48
  col_ref = self.components[0]
46
49
  assert isinstance(col_ref, ColumnRef)
47
50
  return col_ref
48
51
 
49
52
  def __repr__(self) -> str:
50
- return f'{self._col_ref}.{self.prop.name.lower()}'
53
+ return f'{self.col_ref}.{self.prop.name.lower()}'
51
54
 
52
- def is_error_prop(self) -> bool:
53
- return self.prop == self.Property.ERRORTYPE or self.prop == self.Property.ERRORMSG
55
+ def is_cellmd_prop(self) -> bool:
56
+ return self.prop in (self.Property.ERRORTYPE, self.Property.ERRORMSG, self.Property.CELLMD)
54
57
 
55
- def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
56
- if not self._col_ref.col.is_stored:
58
+ def sql_expr(self, sql_elements: SqlElementCache) -> sql.ColumnElement | None:
59
+ if not self.col_ref.col_handle.get().is_stored:
57
60
  return None
61
+ col = self.col_ref.col_handle.get()
58
62
 
59
63
  # the errortype/-msg properties of a read-validated media column need to be extracted from the DataRow
60
64
  if (
61
- self._col_ref.col.col_type.is_media_type()
62
- and self._col_ref.col.media_validation == catalog.MediaValidation.ON_READ
63
- and self.is_error_prop()
65
+ col.col_type.is_media_type()
66
+ and col.media_validation == catalog.MediaValidation.ON_READ
67
+ and self.is_cellmd_prop()
64
68
  ):
65
69
  return None
66
70
 
67
71
  if self.prop == self.Property.ERRORTYPE:
68
- assert self._col_ref.col.sa_errortype_col is not None
69
- return self._col_ref.col.sa_errortype_col
72
+ return col.sa_cellmd_col.op('->>')('errortype')
70
73
  if self.prop == self.Property.ERRORMSG:
71
- assert self._col_ref.col.sa_errormsg_col is not None
72
- return self._col_ref.col.sa_errormsg_col
74
+ return col.sa_cellmd_col.op('->>')('errormsg')
75
+ if self.prop == self.Property.CELLMD:
76
+ assert col.sa_cellmd_col is not None
77
+ return col.sa_cellmd_col
73
78
  if self.prop == self.Property.FILEURL:
74
79
  # the file url is stored as the column value
75
- return sql_elements.get(self._col_ref)
80
+ return sql_elements.get(self.col_ref)
76
81
  return None
77
82
 
83
+ @classmethod
84
+ def create_cellmd_exc(cls, exc: Exception) -> dict[str, str]:
85
+ """Create a cellmd value from an exception."""
86
+ return {'errortype': type(exc).__name__, 'errormsg': str(exc)}
87
+
78
88
  def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
79
89
  if self.prop == self.Property.FILEURL:
80
- assert data_row.has_val[self._col_ref.slot_idx]
81
- data_row[self.slot_idx] = data_row.file_urls[self._col_ref.slot_idx]
90
+ assert data_row.has_val[self.col_ref.slot_idx]
91
+ data_row[self.slot_idx] = data_row.file_urls[self.col_ref.slot_idx]
82
92
  return
83
93
  elif self.prop == self.Property.LOCALPATH:
84
- assert data_row.has_val[self._col_ref.slot_idx]
85
- data_row[self.slot_idx] = data_row.file_paths[self._col_ref.slot_idx]
94
+ assert data_row.has_val[self.col_ref.slot_idx]
95
+ data_row[self.slot_idx] = data_row.file_paths[self.col_ref.slot_idx]
86
96
  return
87
- elif self.is_error_prop():
88
- exc = data_row.get_exc(self._col_ref.slot_idx)
97
+ elif self.is_cellmd_prop():
98
+ exc = data_row.get_exc(self.col_ref.slot_idx)
89
99
  if exc is None:
90
100
  data_row[self.slot_idx] = None
91
101
  elif self.prop == self.Property.ERRORTYPE:
92
102
  data_row[self.slot_idx] = type(exc).__name__
93
- else:
103
+ elif self.prop == self.Property.ERRORMSG:
94
104
  data_row[self.slot_idx] = str(exc)
105
+ elif self.prop == self.Property.CELLMD:
106
+ data_row[self.slot_idx] = self.create_cellmd_exc(exc)
107
+ else:
108
+ raise AssertionError(f'Unknown property {self.prop}')
109
+ return
95
110
  else:
96
- assert False
111
+ raise AssertionError()
97
112
 
98
113
  def _as_dict(self) -> dict:
99
114
  return {'prop': self.prop.value, **super()._as_dict()}
@@ -103,4 +118,3 @@ class ColumnPropertyRef(Expr):
103
118
  assert 'prop' in d
104
119
  assert isinstance(components[0], ColumnRef)
105
120
  return cls(components[0], cls.Property(d['prop']))
106
-