pixeltable 0.2.21__py3-none-any.whl → 0.2.23__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.
- pixeltable/__init__.py +2 -2
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/__init__.py +1 -1
- pixeltable/catalog/column.py +41 -29
- pixeltable/catalog/globals.py +18 -0
- pixeltable/catalog/insertable_table.py +30 -10
- pixeltable/catalog/table.py +198 -86
- pixeltable/catalog/table_version.py +47 -53
- pixeltable/catalog/table_version_path.py +2 -2
- pixeltable/catalog/view.py +17 -18
- pixeltable/dataframe.py +27 -36
- pixeltable/env.py +7 -0
- pixeltable/exec/__init__.py +0 -1
- pixeltable/exec/aggregation_node.py +6 -3
- pixeltable/exec/cache_prefetch_node.py +189 -43
- pixeltable/exec/data_row_batch.py +5 -22
- pixeltable/exec/exec_context.py +2 -2
- pixeltable/exec/exec_node.py +3 -2
- pixeltable/exec/expr_eval_node.py +23 -16
- pixeltable/exec/in_memory_data_node.py +6 -3
- pixeltable/exec/sql_node.py +24 -25
- pixeltable/exprs/arithmetic_expr.py +12 -5
- pixeltable/exprs/array_slice.py +7 -7
- pixeltable/exprs/column_property_ref.py +37 -10
- pixeltable/exprs/column_ref.py +97 -14
- pixeltable/exprs/comparison.py +10 -5
- pixeltable/exprs/compound_predicate.py +8 -7
- pixeltable/exprs/data_row.py +27 -18
- pixeltable/exprs/expr.py +53 -52
- pixeltable/exprs/expr_set.py +5 -0
- pixeltable/exprs/function_call.py +32 -16
- pixeltable/exprs/globals.py +4 -1
- pixeltable/exprs/in_predicate.py +8 -7
- pixeltable/exprs/inline_expr.py +4 -4
- pixeltable/exprs/is_null.py +4 -4
- pixeltable/exprs/json_mapper.py +11 -12
- pixeltable/exprs/json_path.py +6 -11
- pixeltable/exprs/literal.py +5 -5
- pixeltable/exprs/method_ref.py +5 -4
- pixeltable/exprs/object_ref.py +2 -1
- pixeltable/exprs/row_builder.py +88 -36
- pixeltable/exprs/rowid_ref.py +12 -11
- pixeltable/exprs/similarity_expr.py +12 -7
- pixeltable/exprs/sql_element_cache.py +7 -5
- pixeltable/exprs/type_cast.py +8 -6
- pixeltable/exprs/variable.py +5 -4
- pixeltable/func/aggregate_function.py +9 -9
- pixeltable/func/expr_template_function.py +6 -5
- pixeltable/func/function.py +11 -10
- pixeltable/func/udf.py +6 -11
- pixeltable/functions/__init__.py +2 -2
- pixeltable/functions/globals.py +5 -7
- pixeltable/functions/huggingface.py +155 -45
- pixeltable/functions/llama_cpp.py +107 -0
- pixeltable/functions/mistralai.py +1 -1
- pixeltable/functions/ollama.py +147 -0
- pixeltable/functions/openai.py +1 -1
- pixeltable/functions/replicate.py +72 -0
- pixeltable/functions/string.py +9 -0
- pixeltable/functions/together.py +1 -1
- pixeltable/functions/util.py +5 -2
- pixeltable/globals.py +67 -26
- pixeltable/index/btree.py +16 -3
- pixeltable/index/embedding_index.py +4 -4
- pixeltable/io/__init__.py +1 -2
- pixeltable/io/fiftyone.py +178 -0
- pixeltable/io/globals.py +96 -2
- pixeltable/iterators/base.py +3 -2
- pixeltable/iterators/document.py +1 -1
- pixeltable/iterators/video.py +120 -63
- pixeltable/metadata/__init__.py +1 -1
- pixeltable/metadata/converters/convert_21.py +34 -0
- pixeltable/metadata/converters/util.py +45 -4
- pixeltable/metadata/notes.py +1 -0
- pixeltable/metadata/schema.py +8 -0
- pixeltable/plan.py +17 -15
- pixeltable/py.typed +0 -0
- pixeltable/store.py +7 -2
- pixeltable/tool/create_test_db_dump.py +1 -1
- pixeltable/tool/create_test_video.py +1 -1
- pixeltable/tool/embed_udf.py +1 -1
- pixeltable/tool/mypy_plugin.py +28 -5
- pixeltable/type_system.py +100 -36
- pixeltable/utils/coco.py +5 -5
- pixeltable/utils/documents.py +15 -1
- pixeltable/utils/formatter.py +12 -13
- pixeltable/utils/s3.py +6 -3
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.23.dist-info}/METADATA +158 -49
- pixeltable-0.2.23.dist-info/RECORD +153 -0
- pixeltable/exec/media_validation_node.py +0 -43
- pixeltable-0.2.21.dist-info/RECORD +0 -148
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.23.dist-info}/LICENSE +0 -0
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.23.dist-info}/WHEEL +0 -0
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.23.dist-info}/entry_points.txt +0 -0
pixeltable/exprs/expr.py
CHANGED
|
@@ -13,7 +13,6 @@ from uuid import UUID
|
|
|
13
13
|
import sqlalchemy as sql
|
|
14
14
|
from typing_extensions import _AnnotatedAlias, Self
|
|
15
15
|
|
|
16
|
-
import pixeltable
|
|
17
16
|
import pixeltable.catalog as catalog
|
|
18
17
|
import pixeltable.exceptions as excs
|
|
19
18
|
import pixeltable.func as func
|
|
@@ -91,7 +90,7 @@ class Expr(abc.ABC):
|
|
|
91
90
|
result = c_scope
|
|
92
91
|
return result
|
|
93
92
|
|
|
94
|
-
def bind_rel_paths(self, mapper: Optional['
|
|
93
|
+
def bind_rel_paths(self, mapper: Optional['exprs.JsonMapper'] = None) -> None:
|
|
95
94
|
"""
|
|
96
95
|
Binds relative JsonPaths to mapper.
|
|
97
96
|
This needs to be done in a separate phase after __init__(), because RelativeJsonPath()(-1) cannot be resolved
|
|
@@ -121,7 +120,7 @@ class Expr(abc.ABC):
|
|
|
121
120
|
return False
|
|
122
121
|
return self._equals(other)
|
|
123
122
|
|
|
124
|
-
def _equals(self, other:
|
|
123
|
+
def _equals(self, other: Self) -> bool:
|
|
125
124
|
# we already compared the type and components in equals(); subclasses that require additional comparisons
|
|
126
125
|
# override this
|
|
127
126
|
return True
|
|
@@ -232,12 +231,6 @@ class Expr(abc.ABC):
|
|
|
232
231
|
return self._retarget(tbl_versions)
|
|
233
232
|
|
|
234
233
|
def _retarget(self, tbl_versions: dict[UUID, catalog.TableVersion]) -> Self:
|
|
235
|
-
from .column_ref import ColumnRef
|
|
236
|
-
if isinstance(self, ColumnRef):
|
|
237
|
-
target = tbl_versions[self.col.tbl.id]
|
|
238
|
-
assert self.col.id in target.cols_by_id
|
|
239
|
-
col = target.cols_by_id[self.col.id]
|
|
240
|
-
return ColumnRef(col)
|
|
241
234
|
for i in range (len(self.components)):
|
|
242
235
|
self.components[i] = self.components[i]._retarget(tbl_versions)
|
|
243
236
|
return self
|
|
@@ -289,22 +282,24 @@ class Expr(abc.ABC):
|
|
|
289
282
|
for c in self.components:
|
|
290
283
|
yield from c.subexprs(expr_class=expr_class, filter=filter, traverse_matches=traverse_matches)
|
|
291
284
|
if is_match:
|
|
292
|
-
yield self
|
|
285
|
+
yield self # type: ignore[misc]
|
|
293
286
|
|
|
294
287
|
@overload
|
|
288
|
+
@classmethod
|
|
295
289
|
def list_subexprs(
|
|
296
|
-
expr_list: Iterable[Expr], *, filter: Optional[Callable[[Expr], bool]] = None, traverse_matches: bool = True
|
|
290
|
+
cls, expr_list: Iterable[Expr], *, filter: Optional[Callable[[Expr], bool]] = None, traverse_matches: bool = True
|
|
297
291
|
) -> Iterator[Expr]: ...
|
|
298
292
|
|
|
299
293
|
@overload
|
|
294
|
+
@classmethod
|
|
300
295
|
def list_subexprs(
|
|
301
|
-
expr_list:
|
|
296
|
+
cls, expr_list: Iterable[Expr], expr_class: type[T], filter: Optional[Callable[[Expr], bool]] = None,
|
|
302
297
|
traverse_matches: bool = True
|
|
303
298
|
) -> Iterator[T]: ...
|
|
304
299
|
|
|
305
300
|
@classmethod
|
|
306
301
|
def list_subexprs(
|
|
307
|
-
cls, expr_list:
|
|
302
|
+
cls, expr_list: Iterable[Expr], expr_class: Optional[type[T]] = None,
|
|
308
303
|
filter: Optional[Callable[[Expr], bool]] = None, traverse_matches: bool = True
|
|
309
304
|
) -> Iterator[T]:
|
|
310
305
|
"""Produce subexprs for all exprs in list. Can contain duplicates."""
|
|
@@ -329,11 +324,8 @@ class Expr(abc.ABC):
|
|
|
329
324
|
return {ref.col.tbl.id for ref in self.subexprs(ColumnRef)} | {ref.tbl.id for ref in self.subexprs(RowidRef)}
|
|
330
325
|
|
|
331
326
|
@classmethod
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
for e in expr_list:
|
|
335
|
-
ids.update(e.tbl_ids())
|
|
336
|
-
return ids
|
|
327
|
+
def all_tbl_ids(cls, exprs_: Iterable[Expr]) -> set[UUID]:
|
|
328
|
+
return set(tbl_id for e in exprs_ for tbl_id in e.tbl_ids())
|
|
337
329
|
|
|
338
330
|
@classmethod
|
|
339
331
|
def get_refd_columns(cls, expr_dict: dict[str, Any]) -> list[catalog.Column]:
|
|
@@ -383,7 +375,7 @@ class Expr(abc.ABC):
|
|
|
383
375
|
pass
|
|
384
376
|
|
|
385
377
|
@abc.abstractmethod
|
|
386
|
-
def eval(self, data_row: DataRow, row_builder: '
|
|
378
|
+
def eval(self, data_row: DataRow, row_builder: 'exprs.RowBuilder') -> None:
|
|
387
379
|
"""
|
|
388
380
|
Compute the expr value for data_row and store the result in data_row[slot_idx].
|
|
389
381
|
Not called if sql_expr() != None (exception: Literal).
|
|
@@ -449,18 +441,18 @@ class Expr(abc.ABC):
|
|
|
449
441
|
def _from_dict(cls, d: dict, components: list[Expr]) -> Self:
|
|
450
442
|
assert False, 'not implemented'
|
|
451
443
|
|
|
452
|
-
def isin(self, value_set: Any) -> '
|
|
444
|
+
def isin(self, value_set: Any) -> 'exprs.InPredicate':
|
|
453
445
|
from .in_predicate import InPredicate
|
|
454
446
|
if isinstance(value_set, Expr):
|
|
455
447
|
return InPredicate(self, value_set_expr=value_set)
|
|
456
448
|
else:
|
|
457
449
|
return InPredicate(self, value_set_literal=value_set)
|
|
458
450
|
|
|
459
|
-
def astype(self, new_type: Union[ts.ColumnType, type, _AnnotatedAlias]) -> '
|
|
451
|
+
def astype(self, new_type: Union[ts.ColumnType, type, _AnnotatedAlias]) -> 'exprs.TypeCast':
|
|
460
452
|
from pixeltable.exprs import TypeCast
|
|
461
453
|
return TypeCast(self, ts.ColumnType.normalize_type(new_type))
|
|
462
454
|
|
|
463
|
-
def apply(self, fn: Callable, *, col_type: Union[ts.ColumnType, type, _AnnotatedAlias, None] = None) -> '
|
|
455
|
+
def apply(self, fn: Callable, *, col_type: Union[ts.ColumnType, type, _AnnotatedAlias, None] = None) -> 'exprs.FunctionCall':
|
|
464
456
|
if col_type is not None:
|
|
465
457
|
col_type = ts.ColumnType.normalize_type(col_type)
|
|
466
458
|
function = self._make_applicator_function(fn, col_type)
|
|
@@ -475,23 +467,32 @@ class Expr(abc.ABC):
|
|
|
475
467
|
]
|
|
476
468
|
return attrs
|
|
477
469
|
|
|
470
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
471
|
+
raise NotImplementedError(f'Expression of type `{type(self)}` is not callable')
|
|
472
|
+
|
|
478
473
|
def __getitem__(self, index: object) -> Expr:
|
|
479
474
|
if self.col_type.is_json_type():
|
|
480
475
|
from .json_path import JsonPath
|
|
481
|
-
return JsonPath(self)
|
|
476
|
+
return JsonPath(self)[index]
|
|
482
477
|
if self.col_type.is_array_type():
|
|
483
478
|
from .array_slice import ArraySlice
|
|
479
|
+
if not isinstance(index, tuple):
|
|
480
|
+
index = (index,)
|
|
481
|
+
if any(not isinstance(i, (int, slice)) for i in index):
|
|
482
|
+
raise AttributeError(f'Invalid array indices: {index}')
|
|
484
483
|
return ArraySlice(self, index)
|
|
485
484
|
raise AttributeError(f'Type {self.col_type} is not subscriptable')
|
|
486
485
|
|
|
487
|
-
def __getattr__(self, name: str) ->
|
|
486
|
+
def __getattr__(self, name: str) -> 'exprs.Expr':
|
|
488
487
|
"""
|
|
489
488
|
ex.: <img col>.rotate(60)
|
|
490
489
|
"""
|
|
490
|
+
from .json_path import JsonPath
|
|
491
|
+
from .method_ref import MethodRef
|
|
491
492
|
if self.col_type.is_json_type():
|
|
492
|
-
return
|
|
493
|
+
return JsonPath(self).__getattr__(name)
|
|
493
494
|
else:
|
|
494
|
-
method_ref =
|
|
495
|
+
method_ref = MethodRef(self, name)
|
|
495
496
|
if method_ref.fn.is_property:
|
|
496
497
|
# Marked as a property, so autoinvoke the method to obtain a `FunctionCall`
|
|
497
498
|
assert method_ref.fn.arity == 1
|
|
@@ -504,32 +505,32 @@ class Expr(abc.ABC):
|
|
|
504
505
|
raise TypeError(
|
|
505
506
|
'Pixeltable expressions cannot be used in conjunction with Python boolean operators (and/or/not)')
|
|
506
507
|
|
|
507
|
-
def __lt__(self, other: object) -> '
|
|
508
|
+
def __lt__(self, other: object) -> 'exprs.Comparison':
|
|
508
509
|
return self._make_comparison(ComparisonOperator.LT, other)
|
|
509
510
|
|
|
510
|
-
def __le__(self, other: object) -> '
|
|
511
|
+
def __le__(self, other: object) -> 'exprs.Comparison':
|
|
511
512
|
return self._make_comparison(ComparisonOperator.LE, other)
|
|
512
513
|
|
|
513
|
-
def __eq__(self, other: object) -> '
|
|
514
|
+
def __eq__(self, other: object) -> 'exprs.Expr': # type: ignore[override]
|
|
514
515
|
if other is None:
|
|
515
516
|
from .is_null import IsNull
|
|
516
517
|
return IsNull(self)
|
|
517
518
|
return self._make_comparison(ComparisonOperator.EQ, other)
|
|
518
519
|
|
|
519
|
-
def __ne__(self, other: object) -> '
|
|
520
|
+
def __ne__(self, other: object) -> 'exprs.Expr': # type: ignore[override]
|
|
520
521
|
if other is None:
|
|
521
522
|
from .compound_predicate import CompoundPredicate
|
|
522
523
|
from .is_null import IsNull
|
|
523
524
|
return CompoundPredicate(LogicalOperator.NOT, [IsNull(self)])
|
|
524
525
|
return self._make_comparison(ComparisonOperator.NE, other)
|
|
525
526
|
|
|
526
|
-
def __gt__(self, other: object) -> '
|
|
527
|
+
def __gt__(self, other: object) -> 'exprs.Comparison':
|
|
527
528
|
return self._make_comparison(ComparisonOperator.GT, other)
|
|
528
529
|
|
|
529
|
-
def __ge__(self, other: object) -> '
|
|
530
|
+
def __ge__(self, other: object) -> 'exprs.Comparison':
|
|
530
531
|
return self._make_comparison(ComparisonOperator.GE, other)
|
|
531
532
|
|
|
532
|
-
def _make_comparison(self, op: ComparisonOperator, other: object) -> '
|
|
533
|
+
def _make_comparison(self, op: ComparisonOperator, other: object) -> 'exprs.Comparison':
|
|
533
534
|
"""
|
|
534
535
|
other: Union[Expr, LiteralPythonTypes]
|
|
535
536
|
"""
|
|
@@ -539,49 +540,49 @@ class Expr(abc.ABC):
|
|
|
539
540
|
if isinstance(other, Expr):
|
|
540
541
|
return Comparison(op, self, other)
|
|
541
542
|
if isinstance(other, typing.get_args(LiteralPythonTypes)):
|
|
542
|
-
return Comparison(op, self, Literal(other))
|
|
543
|
+
return Comparison(op, self, Literal(other))
|
|
543
544
|
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
544
545
|
|
|
545
|
-
def __neg__(self) -> '
|
|
546
|
+
def __neg__(self) -> 'exprs.ArithmeticExpr':
|
|
546
547
|
return self._make_arithmetic_expr(ArithmeticOperator.MUL, -1)
|
|
547
548
|
|
|
548
|
-
def __add__(self, other: object) -> '
|
|
549
|
+
def __add__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
549
550
|
return self._make_arithmetic_expr(ArithmeticOperator.ADD, other)
|
|
550
551
|
|
|
551
|
-
def __sub__(self, other: object) -> '
|
|
552
|
+
def __sub__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
552
553
|
return self._make_arithmetic_expr(ArithmeticOperator.SUB, other)
|
|
553
554
|
|
|
554
|
-
def __mul__(self, other: object) -> '
|
|
555
|
+
def __mul__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
555
556
|
return self._make_arithmetic_expr(ArithmeticOperator.MUL, other)
|
|
556
557
|
|
|
557
|
-
def __truediv__(self, other: object) -> '
|
|
558
|
+
def __truediv__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
558
559
|
return self._make_arithmetic_expr(ArithmeticOperator.DIV, other)
|
|
559
560
|
|
|
560
|
-
def __mod__(self, other: object) -> '
|
|
561
|
+
def __mod__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
561
562
|
return self._make_arithmetic_expr(ArithmeticOperator.MOD, other)
|
|
562
563
|
|
|
563
|
-
def __floordiv__(self, other: object) -> '
|
|
564
|
+
def __floordiv__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
564
565
|
return self._make_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
|
|
565
566
|
|
|
566
|
-
def __radd__(self, other: object) -> '
|
|
567
|
+
def __radd__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
567
568
|
return self._rmake_arithmetic_expr(ArithmeticOperator.ADD, other)
|
|
568
569
|
|
|
569
|
-
def __rsub__(self, other: object) -> '
|
|
570
|
+
def __rsub__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
570
571
|
return self._rmake_arithmetic_expr(ArithmeticOperator.SUB, other)
|
|
571
572
|
|
|
572
|
-
def __rmul__(self, other: object) -> '
|
|
573
|
+
def __rmul__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
573
574
|
return self._rmake_arithmetic_expr(ArithmeticOperator.MUL, other)
|
|
574
575
|
|
|
575
|
-
def __rtruediv__(self, other: object) -> '
|
|
576
|
+
def __rtruediv__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
576
577
|
return self._rmake_arithmetic_expr(ArithmeticOperator.DIV, other)
|
|
577
578
|
|
|
578
|
-
def __rmod__(self, other: object) -> '
|
|
579
|
+
def __rmod__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
579
580
|
return self._rmake_arithmetic_expr(ArithmeticOperator.MOD, other)
|
|
580
581
|
|
|
581
|
-
def __rfloordiv__(self, other: object) -> '
|
|
582
|
+
def __rfloordiv__(self, other: object) -> 'exprs.ArithmeticExpr':
|
|
582
583
|
return self._rmake_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
|
|
583
584
|
|
|
584
|
-
def _make_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> '
|
|
585
|
+
def _make_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'exprs.ArithmeticExpr':
|
|
585
586
|
"""
|
|
586
587
|
other: Union[Expr, LiteralPythonTypes]
|
|
587
588
|
"""
|
|
@@ -591,10 +592,10 @@ class Expr(abc.ABC):
|
|
|
591
592
|
if isinstance(other, Expr):
|
|
592
593
|
return ArithmeticExpr(op, self, other)
|
|
593
594
|
if isinstance(other, typing.get_args(LiteralPythonTypes)):
|
|
594
|
-
return ArithmeticExpr(op, self, Literal(other))
|
|
595
|
+
return ArithmeticExpr(op, self, Literal(other))
|
|
595
596
|
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
596
597
|
|
|
597
|
-
def _rmake_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> '
|
|
598
|
+
def _rmake_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'exprs.ArithmeticExpr':
|
|
598
599
|
"""
|
|
599
600
|
Right-handed version of _make_arithmetic_expr. other must be a literal; if it were an Expr,
|
|
600
601
|
the operation would have already been evaluated in its left-handed form.
|
|
@@ -604,7 +605,7 @@ class Expr(abc.ABC):
|
|
|
604
605
|
from .literal import Literal
|
|
605
606
|
assert not isinstance(other, Expr) # Else the left-handed form would have evaluated first
|
|
606
607
|
if isinstance(other, typing.get_args(LiteralPythonTypes)):
|
|
607
|
-
return ArithmeticExpr(op, Literal(other), self)
|
|
608
|
+
return ArithmeticExpr(op, Literal(other), self)
|
|
608
609
|
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
609
610
|
|
|
610
611
|
def __and__(self, other: object) -> Expr:
|
|
@@ -639,7 +640,7 @@ class Expr(abc.ABC):
|
|
|
639
640
|
else:
|
|
640
641
|
return [], self
|
|
641
642
|
|
|
642
|
-
def _make_applicator_function(self, fn: Callable, col_type: Optional[ts.ColumnType]) -> '
|
|
643
|
+
def _make_applicator_function(self, fn: Callable, col_type: Optional[ts.ColumnType]) -> 'func.Function':
|
|
643
644
|
"""
|
|
644
645
|
Creates a unary pixeltable `Function` that encapsulates a python `Callable`. The result type of
|
|
645
646
|
the new `Function` is given by `col_type`, and its parameter type will be `self.col_type`.
|
pixeltable/exprs/expr_set.py
CHANGED
|
@@ -50,14 +50,29 @@ class FunctionCall(Expr):
|
|
|
50
50
|
if group_by_clause is None:
|
|
51
51
|
group_by_clause = []
|
|
52
52
|
signature = fn.signature
|
|
53
|
-
|
|
53
|
+
return_type = fn.call_return_type(bound_args)
|
|
54
54
|
self.fn = fn
|
|
55
55
|
self.is_method_call = is_method_call
|
|
56
56
|
self.normalize_args(fn.name, signature, bound_args)
|
|
57
57
|
|
|
58
|
+
# If `return_type` is non-nullable, but the function call has a nullable input to any of its non-nullable
|
|
59
|
+
# parameters, then we need to make it nullable. This is because Pixeltable defaults a function output to
|
|
60
|
+
# `None` when any of its non-nullable inputs are `None`.
|
|
61
|
+
for arg_name, arg in bound_args.items():
|
|
62
|
+
param = signature.parameters[arg_name]
|
|
63
|
+
if (
|
|
64
|
+
param.col_type is not None and not param.col_type.nullable
|
|
65
|
+
and isinstance(arg, Expr) and arg.col_type.nullable
|
|
66
|
+
):
|
|
67
|
+
return_type = return_type.copy(nullable=True)
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
super().__init__(return_type)
|
|
71
|
+
|
|
58
72
|
self.agg_init_args = {}
|
|
59
73
|
if self.is_agg_fn_call:
|
|
60
74
|
# we separate out the init args for the aggregator
|
|
75
|
+
assert isinstance(fn, func.AggregateFunction)
|
|
61
76
|
self.agg_init_args = {
|
|
62
77
|
arg_name: arg for arg_name, arg in bound_args.items() if arg_name in fn.init_param_names
|
|
63
78
|
}
|
|
@@ -71,17 +86,17 @@ class FunctionCall(Expr):
|
|
|
71
86
|
self.arg_types = []
|
|
72
87
|
self.kwarg_types = {}
|
|
73
88
|
# the prefix of parameters that are bound can be passed by position
|
|
74
|
-
for
|
|
75
|
-
if
|
|
89
|
+
for py_param in fn.signature.py_signature.parameters.values():
|
|
90
|
+
if py_param.name not in bound_args or py_param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
76
91
|
break
|
|
77
|
-
arg = bound_args[
|
|
92
|
+
arg = bound_args[py_param.name]
|
|
78
93
|
if isinstance(arg, Expr):
|
|
79
94
|
self.args.append((len(self.components), None))
|
|
80
95
|
self.components.append(arg.copy())
|
|
81
96
|
else:
|
|
82
97
|
self.args.append((None, arg))
|
|
83
|
-
if
|
|
84
|
-
self.arg_types.append(signature.parameters[
|
|
98
|
+
if py_param.kind != inspect.Parameter.VAR_POSITIONAL and py_param.kind != inspect.Parameter.VAR_KEYWORD:
|
|
99
|
+
self.arg_types.append(signature.parameters[py_param.name].col_type)
|
|
85
100
|
|
|
86
101
|
# the remaining args are passed as keywords
|
|
87
102
|
kw_param_names = set(bound_args.keys()) - set(list(fn.signature.py_signature.parameters.keys())[:len(self.args)])
|
|
@@ -138,13 +153,11 @@ class FunctionCall(Expr):
|
|
|
138
153
|
return [RowidRef(target, i) for i in range(target.num_rowid_columns())]
|
|
139
154
|
|
|
140
155
|
def default_column_name(self) -> Optional[str]:
|
|
141
|
-
|
|
142
|
-
return self.fn.name
|
|
143
|
-
return super().default_column_name()
|
|
156
|
+
return self.fn.name
|
|
144
157
|
|
|
145
158
|
@classmethod
|
|
146
159
|
def normalize_args(cls, fn_name: str, signature: func.Signature, bound_args: dict[str, Any]) -> None:
|
|
147
|
-
"""Converts
|
|
160
|
+
"""Converts args to Exprs where appropriate and checks that they are compatible with signature.
|
|
148
161
|
|
|
149
162
|
Updates bound_args in place, where necessary.
|
|
150
163
|
"""
|
|
@@ -263,6 +276,7 @@ class FunctionCall(Expr):
|
|
|
263
276
|
for param_name, (idx, arg) in self.kwargs.items()
|
|
264
277
|
])
|
|
265
278
|
if len(self.order_by) > 0:
|
|
279
|
+
assert isinstance(self.fn, func.AggregateFunction)
|
|
266
280
|
if self.fn.requires_order_by:
|
|
267
281
|
arg_strs.insert(0, Expr.print_list(self.order_by))
|
|
268
282
|
else:
|
|
@@ -273,7 +287,7 @@ class FunctionCall(Expr):
|
|
|
273
287
|
separator = ', ' if inline else ',\n '
|
|
274
288
|
return separator.join(arg_strs)
|
|
275
289
|
|
|
276
|
-
def has_group_by(self) ->
|
|
290
|
+
def has_group_by(self) -> bool:
|
|
277
291
|
return self.group_by_stop_idx != 0
|
|
278
292
|
|
|
279
293
|
@property
|
|
@@ -286,10 +300,11 @@ class FunctionCall(Expr):
|
|
|
286
300
|
|
|
287
301
|
@property
|
|
288
302
|
def is_window_fn_call(self) -> bool:
|
|
289
|
-
return isinstance(self.fn, func.AggregateFunction) and self.fn.allows_window and
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
return isinstance(self.fn, func.AggregateFunction) and self.fn.allows_window and (
|
|
304
|
+
not self.fn.allows_std_agg
|
|
305
|
+
or self.has_group_by()
|
|
306
|
+
or (len(self.order_by) > 0 and not self.fn.requires_order_by)
|
|
307
|
+
)
|
|
293
308
|
|
|
294
309
|
def get_window_sort_exprs(self) -> tuple[list[Expr], list[Expr]]:
|
|
295
310
|
return self.group_by, self.order_by
|
|
@@ -413,6 +428,7 @@ class FunctionCall(Expr):
|
|
|
413
428
|
# optimization: avoid additional level of indirection we'd get from calling Function.exec()
|
|
414
429
|
data_row[self.slot_idx] = self.fn.py_fn(*args, **kwargs)
|
|
415
430
|
elif self.is_window_fn_call:
|
|
431
|
+
assert isinstance(self.fn, func.AggregateFunction)
|
|
416
432
|
if self.has_group_by():
|
|
417
433
|
if self.current_partition_vals is None:
|
|
418
434
|
self.current_partition_vals = [None] * len(self.group_by)
|
|
@@ -438,7 +454,7 @@ class FunctionCall(Expr):
|
|
|
438
454
|
return result
|
|
439
455
|
|
|
440
456
|
@classmethod
|
|
441
|
-
def _from_dict(cls, d: dict, components: list[Expr]) ->
|
|
457
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> FunctionCall:
|
|
442
458
|
assert 'fn' in d
|
|
443
459
|
assert 'args' in d
|
|
444
460
|
assert 'kwargs' in d
|
pixeltable/exprs/globals.py
CHANGED
|
@@ -5,7 +5,7 @@ import enum
|
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
7
|
# Python types corresponding to our literal types
|
|
8
|
-
LiteralPythonTypes = Union[str, int, float, bool, datetime.datetime
|
|
8
|
+
LiteralPythonTypes = Union[str, int, float, bool, datetime.datetime]
|
|
9
9
|
|
|
10
10
|
def print_slice(s: slice) -> str:
|
|
11
11
|
start_str = f'{str(s.start) if s.start is not None else ""}'
|
|
@@ -35,6 +35,7 @@ class ComparisonOperator(enum.Enum):
|
|
|
35
35
|
return '>'
|
|
36
36
|
if self == self.GE:
|
|
37
37
|
return '>='
|
|
38
|
+
assert False
|
|
38
39
|
|
|
39
40
|
def reverse(self) -> ComparisonOperator:
|
|
40
41
|
if self == self.LT:
|
|
@@ -60,6 +61,7 @@ class LogicalOperator(enum.Enum):
|
|
|
60
61
|
return '|'
|
|
61
62
|
if self == self.NOT:
|
|
62
63
|
return '~'
|
|
64
|
+
assert False
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
class ArithmeticOperator(enum.Enum):
|
|
@@ -83,3 +85,4 @@ class ArithmeticOperator(enum.Enum):
|
|
|
83
85
|
return '%'
|
|
84
86
|
if self == self.FLOORDIV:
|
|
85
87
|
return '//'
|
|
88
|
+
assert False
|
pixeltable/exprs/in_predicate.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Iterable, Optional
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sql
|
|
6
6
|
|
|
7
7
|
import pixeltable.exceptions as excs
|
|
8
|
-
from .sql_element_cache import SqlElementCache
|
|
9
8
|
import pixeltable.type_system as ts
|
|
9
|
+
|
|
10
10
|
from .data_row import DataRow
|
|
11
11
|
from .expr import Expr
|
|
12
12
|
from .row_builder import RowBuilder
|
|
13
|
+
from .sql_element_cache import SqlElementCache
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class InPredicate(Expr):
|
|
@@ -43,7 +44,7 @@ class InPredicate(Expr):
|
|
|
43
44
|
assert len(self.components) == 2
|
|
44
45
|
return self.components[1]
|
|
45
46
|
|
|
46
|
-
def _normalize_value_set(self, value_set:
|
|
47
|
+
def _normalize_value_set(self, value_set: Iterable, filter_type_mismatches: bool = True) -> list:
|
|
47
48
|
if not isinstance(value_set, Iterable):
|
|
48
49
|
raise excs.Error(f'isin(): argument must be an Iterable (eg, list, dict, ...), not {value_set!r}')
|
|
49
50
|
value_list = list(value_set)
|
|
@@ -68,10 +69,10 @@ class InPredicate(Expr):
|
|
|
68
69
|
def _equals(self, other: InPredicate) -> bool:
|
|
69
70
|
return self.value_list == other.value_list
|
|
70
71
|
|
|
71
|
-
def _id_attrs(self) ->
|
|
72
|
+
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
72
73
|
return super()._id_attrs() + [('value_list', self.value_list)]
|
|
73
74
|
|
|
74
|
-
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.
|
|
75
|
+
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
75
76
|
lhs_sql_exprs = sql_elements.get(self.components[0])
|
|
76
77
|
if lhs_sql_exprs is None or self.value_list is None:
|
|
77
78
|
return None
|
|
@@ -86,11 +87,11 @@ class InPredicate(Expr):
|
|
|
86
87
|
value_list = self._normalize_value_set(value_set, filter_type_mismatches=False)
|
|
87
88
|
data_row[self.slot_idx] = lhs_val in value_list
|
|
88
89
|
|
|
89
|
-
def _as_dict(self) ->
|
|
90
|
+
def _as_dict(self) -> dict:
|
|
90
91
|
return {'value_list': self.value_list, **super()._as_dict()}
|
|
91
92
|
|
|
92
93
|
@classmethod
|
|
93
|
-
def _from_dict(cls, d:
|
|
94
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> InPredicate:
|
|
94
95
|
assert 'value_list' in d
|
|
95
96
|
assert len(components) <= 2
|
|
96
97
|
return cls(components[0], d['value_list'], components[1] if len(components) == 2 else None)
|
pixeltable/exprs/inline_expr.py
CHANGED
|
@@ -73,7 +73,7 @@ class InlineArray(Expr):
|
|
|
73
73
|
return super()._as_dict()
|
|
74
74
|
|
|
75
75
|
@classmethod
|
|
76
|
-
def _from_dict(cls, _: dict, components: list[Expr]) ->
|
|
76
|
+
def _from_dict(cls, _: dict, components: list[Expr]) -> InlineArray:
|
|
77
77
|
try:
|
|
78
78
|
return cls(components)
|
|
79
79
|
except excs.Error:
|
|
@@ -81,7 +81,7 @@ class InlineArray(Expr):
|
|
|
81
81
|
# This is because in schema versions <= 19, `InlineArray` was serialized incorrectly, and
|
|
82
82
|
# there is no way to determine the correct expression type until the subexpressions are
|
|
83
83
|
# loaded and their types are known.
|
|
84
|
-
return InlineList(components)
|
|
84
|
+
return InlineList(components) # type: ignore[return-value]
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class InlineList(Expr):
|
|
@@ -122,7 +122,7 @@ class InlineList(Expr):
|
|
|
122
122
|
return super()._as_dict()
|
|
123
123
|
|
|
124
124
|
@classmethod
|
|
125
|
-
def _from_dict(cls, _: dict, components: list[Expr]) ->
|
|
125
|
+
def _from_dict(cls, _: dict, components: list[Expr]) -> InlineList:
|
|
126
126
|
return cls(components)
|
|
127
127
|
|
|
128
128
|
|
|
@@ -193,7 +193,7 @@ class InlineDict(Expr):
|
|
|
193
193
|
return {'keys': self.keys, **super()._as_dict()}
|
|
194
194
|
|
|
195
195
|
@classmethod
|
|
196
|
-
def _from_dict(cls, d: dict, components: list[Expr]) ->
|
|
196
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> InlineDict:
|
|
197
197
|
assert 'keys' in d
|
|
198
198
|
assert len(d['keys']) == len(components)
|
|
199
199
|
arg = dict(zip(d['keys'], components))
|
pixeltable/exprs/is_null.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sql
|
|
6
6
|
|
|
7
7
|
import pixeltable.type_system as ts
|
|
8
|
+
|
|
8
9
|
from .data_row import DataRow
|
|
9
10
|
from .expr import Expr
|
|
10
11
|
from .row_builder import RowBuilder
|
|
@@ -23,7 +24,7 @@ class IsNull(Expr):
|
|
|
23
24
|
def _equals(self, other: IsNull) -> bool:
|
|
24
25
|
return True
|
|
25
26
|
|
|
26
|
-
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.
|
|
27
|
+
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
27
28
|
e = sql_elements.get(self.components[0])
|
|
28
29
|
if e is None:
|
|
29
30
|
return None
|
|
@@ -33,7 +34,6 @@ class IsNull(Expr):
|
|
|
33
34
|
data_row[self.slot_idx] = data_row[self.components[0].slot_idx] is None
|
|
34
35
|
|
|
35
36
|
@classmethod
|
|
36
|
-
def _from_dict(cls, d:
|
|
37
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> IsNull:
|
|
37
38
|
assert len(components) == 1
|
|
38
39
|
return cls(components[0])
|
|
39
|
-
|
pixeltable/exprs/json_mapper.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sql
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ class JsonMapper(Expr):
|
|
|
18
18
|
is populated by JsonMapper.eval(). The JsonMapper effectively creates a new scope for its target expr.
|
|
19
19
|
"""
|
|
20
20
|
def __init__(self, src_expr: Expr, target_expr: Expr):
|
|
21
|
-
# TODO: type spec should be
|
|
21
|
+
# TODO: type spec should be list[target_expr.col_type]
|
|
22
22
|
super().__init__(ts.JsonType())
|
|
23
23
|
|
|
24
24
|
# we're creating a new scope, but we don't know yet whether this is nested within another JsonMapper;
|
|
@@ -32,7 +32,7 @@ class JsonMapper(Expr):
|
|
|
32
32
|
self.target_expr_eval_ctx: Optional[RowBuilder.EvalCtx] = None
|
|
33
33
|
self.id = self._create_id()
|
|
34
34
|
|
|
35
|
-
def bind_rel_paths(self, mapper: Optional[JsonMapper]) -> None:
|
|
35
|
+
def bind_rel_paths(self, mapper: Optional[JsonMapper] = None) -> None:
|
|
36
36
|
self._src_expr.bind_rel_paths(mapper)
|
|
37
37
|
self._target_expr.bind_rel_paths(self)
|
|
38
38
|
self.parent_mapper = mapper
|
|
@@ -43,12 +43,12 @@ class JsonMapper(Expr):
|
|
|
43
43
|
# need to ignore target_expr
|
|
44
44
|
return self._src_expr.scope()
|
|
45
45
|
|
|
46
|
-
def dependencies(self) ->
|
|
46
|
+
def dependencies(self) -> list[Expr]:
|
|
47
47
|
result = [self._src_expr]
|
|
48
48
|
result.extend(self._target_dependencies(self._target_expr))
|
|
49
49
|
return result
|
|
50
50
|
|
|
51
|
-
def _target_dependencies(self, e: Expr) ->
|
|
51
|
+
def _target_dependencies(self, e: Expr) -> list[Expr]:
|
|
52
52
|
"""
|
|
53
53
|
Return all subexprs of e of which the scope isn't contained in target_expr_scope.
|
|
54
54
|
Those need to be evaluated before us.
|
|
@@ -56,7 +56,7 @@ class JsonMapper(Expr):
|
|
|
56
56
|
expr_scope = e.scope()
|
|
57
57
|
if not expr_scope.is_contained_in(self.target_expr_scope):
|
|
58
58
|
return [e]
|
|
59
|
-
result:
|
|
59
|
+
result: list[Expr] = []
|
|
60
60
|
for c in e.components:
|
|
61
61
|
result.extend(self._target_dependencies(c))
|
|
62
62
|
return result
|
|
@@ -84,10 +84,10 @@ class JsonMapper(Expr):
|
|
|
84
84
|
def scope_anchor(self) -> Expr:
|
|
85
85
|
return self.components[2]
|
|
86
86
|
|
|
87
|
-
def _equals(self,
|
|
87
|
+
def _equals(self, _: JsonMapper) -> bool:
|
|
88
88
|
return True
|
|
89
89
|
|
|
90
|
-
def sql_expr(self, _: SqlElementCache) -> Optional[sql.
|
|
90
|
+
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
91
91
|
return None
|
|
92
92
|
|
|
93
93
|
def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
|
|
@@ -104,19 +104,18 @@ class JsonMapper(Expr):
|
|
|
104
104
|
for i, val in enumerate(src):
|
|
105
105
|
data_row[self.scope_anchor.slot_idx] = val
|
|
106
106
|
# stored target_expr
|
|
107
|
-
|
|
108
|
-
assert exc_tb is None
|
|
107
|
+
row_builder.eval(data_row, self.target_expr_eval_ctx)
|
|
109
108
|
result[i] = data_row[self._target_expr.slot_idx]
|
|
110
109
|
data_row[self.slot_idx] = result
|
|
111
110
|
|
|
112
|
-
def _as_dict(self) ->
|
|
111
|
+
def _as_dict(self) -> dict:
|
|
113
112
|
"""
|
|
114
113
|
We need to avoid serializing component[2], which is an ObjectRef.
|
|
115
114
|
"""
|
|
116
115
|
return {'components': [c.as_dict() for c in self.components[0:2]]}
|
|
117
116
|
|
|
118
117
|
@classmethod
|
|
119
|
-
def _from_dict(cls, d:
|
|
118
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> JsonMapper:
|
|
120
119
|
assert len(components) == 2
|
|
121
120
|
return cls(components[0], components[1])
|
|
122
121
|
|