pixeltable 0.2.21__py3-none-any.whl → 0.2.22__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.
Potentially problematic release.
This version of pixeltable might be problematic. Click here for more details.
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/__init__.py +1 -1
- pixeltable/catalog/column.py +37 -11
- pixeltable/catalog/globals.py +18 -0
- pixeltable/catalog/insertable_table.py +6 -4
- pixeltable/catalog/table.py +19 -3
- pixeltable/catalog/table_version.py +34 -14
- pixeltable/catalog/view.py +16 -17
- pixeltable/dataframe.py +7 -8
- pixeltable/env.py +5 -0
- pixeltable/exec/__init__.py +0 -1
- pixeltable/exec/aggregation_node.py +6 -3
- pixeltable/exec/cache_prefetch_node.py +1 -1
- pixeltable/exec/data_row_batch.py +2 -19
- pixeltable/exec/exec_node.py +2 -1
- pixeltable/exec/expr_eval_node.py +17 -10
- pixeltable/exec/in_memory_data_node.py +6 -3
- pixeltable/exec/sql_node.py +24 -25
- pixeltable/exprs/arithmetic_expr.py +3 -1
- pixeltable/exprs/array_slice.py +7 -7
- pixeltable/exprs/column_property_ref.py +37 -10
- pixeltable/exprs/column_ref.py +93 -14
- pixeltable/exprs/comparison.py +5 -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 +5 -10
- 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 +1 -1
- pixeltable/func/function.py +11 -10
- pixeltable/functions/__init__.py +2 -2
- pixeltable/functions/globals.py +5 -7
- pixeltable/functions/huggingface.py +19 -20
- pixeltable/functions/llama_cpp.py +106 -0
- pixeltable/functions/ollama.py +147 -0
- pixeltable/functions/replicate.py +72 -0
- pixeltable/functions/string.py +9 -0
- pixeltable/globals.py +12 -20
- 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 +16 -14
- pixeltable/py.typed +0 -0
- pixeltable/store.py +7 -2
- 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 +17 -1
- pixeltable/utils/documents.py +15 -1
- pixeltable/utils/formatter.py +9 -10
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.22.dist-info}/METADATA +46 -10
- pixeltable-0.2.22.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.22.dist-info}/LICENSE +0 -0
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.22.dist-info}/WHEEL +0 -0
- {pixeltable-0.2.21.dist-info → pixeltable-0.2.22.dist-info}/entry_points.txt +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import operator
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
5
|
|
|
6
6
|
import sqlalchemy as sql
|
|
7
7
|
|
|
8
|
+
import pixeltable.type_system as ts
|
|
9
|
+
|
|
8
10
|
from .data_row import DataRow
|
|
9
11
|
from .expr import Expr
|
|
10
12
|
from .globals import LogicalOperator
|
|
11
13
|
from .row_builder import RowBuilder
|
|
12
14
|
from .sql_element_cache import SqlElementCache
|
|
13
|
-
import pixeltable.type_system as ts
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class CompoundPredicate(Expr):
|
|
17
|
-
def __init__(self, operator: LogicalOperator, operands:
|
|
18
|
+
def __init__(self, operator: LogicalOperator, operands: list[Expr]):
|
|
18
19
|
super().__init__(ts.BoolType())
|
|
19
20
|
self.operator = operator
|
|
20
21
|
# operands are stored in self.components
|
|
@@ -23,7 +24,7 @@ class CompoundPredicate(Expr):
|
|
|
23
24
|
self.components = operands
|
|
24
25
|
else:
|
|
25
26
|
assert len(operands) > 1
|
|
26
|
-
self.operands:
|
|
27
|
+
self.operands: list[Expr] = []
|
|
27
28
|
for operand in operands:
|
|
28
29
|
self._merge_operand(operand)
|
|
29
30
|
|
|
@@ -35,7 +36,7 @@ class CompoundPredicate(Expr):
|
|
|
35
36
|
return f' {self.operator} '.join([f'({e})' for e in self.components])
|
|
36
37
|
|
|
37
38
|
@classmethod
|
|
38
|
-
def make_conjunction(cls, operands:
|
|
39
|
+
def make_conjunction(cls, operands: list[Expr]) -> Optional[Expr]:
|
|
39
40
|
if len(operands) == 0:
|
|
40
41
|
return None
|
|
41
42
|
if len(operands) == 1:
|
|
@@ -89,11 +90,11 @@ class CompoundPredicate(Expr):
|
|
|
89
90
|
val = op_function(val, data_row[op.slot_idx])
|
|
90
91
|
data_row[self.slot_idx] = val
|
|
91
92
|
|
|
92
|
-
def _as_dict(self) ->
|
|
93
|
+
def _as_dict(self) -> dict:
|
|
93
94
|
return {'operator': self.operator.value, **super()._as_dict()}
|
|
94
95
|
|
|
95
96
|
@classmethod
|
|
96
|
-
def _from_dict(cls, d:
|
|
97
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> CompoundPredicate:
|
|
97
98
|
assert 'operator' in d
|
|
98
99
|
return cls(LogicalOperator(d['operator']), components)
|
|
99
100
|
|
pixeltable/exprs/data_row.py
CHANGED
|
@@ -4,13 +4,13 @@ import datetime
|
|
|
4
4
|
import io
|
|
5
5
|
import urllib.parse
|
|
6
6
|
import urllib.request
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
import pgvector.sqlalchemy
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pgvector.sqlalchemy # type: ignore[import-untyped]
|
|
11
11
|
import PIL
|
|
12
12
|
import PIL.Image
|
|
13
|
-
import
|
|
13
|
+
import sqlalchemy as sql
|
|
14
14
|
|
|
15
15
|
from pixeltable import env
|
|
16
16
|
|
|
@@ -57,7 +57,7 @@ class DataRow:
|
|
|
57
57
|
# - None if vals[i] is not a media type or if there is no local file yet for file_urls[i]
|
|
58
58
|
file_paths: list[Optional[str]]
|
|
59
59
|
|
|
60
|
-
def __init__(self, size: int, img_slot_idxs:
|
|
60
|
+
def __init__(self, size: int, img_slot_idxs: list[int], media_slot_idxs: list[int], array_slot_idxs: list[int]):
|
|
61
61
|
self.vals = [None] * size
|
|
62
62
|
self.has_val = [False] * size
|
|
63
63
|
self.excs = [None] * size
|
|
@@ -89,27 +89,35 @@ class DataRow:
|
|
|
89
89
|
target.file_urls = self.file_urls.copy()
|
|
90
90
|
target.file_paths = self.file_paths.copy()
|
|
91
91
|
|
|
92
|
-
def set_pk(self, pk:
|
|
92
|
+
def set_pk(self, pk: tuple[int, ...]) -> None:
|
|
93
93
|
self.pk = pk
|
|
94
94
|
|
|
95
|
-
def has_exc(self, slot_idx: int) -> bool:
|
|
96
|
-
|
|
95
|
+
def has_exc(self, slot_idx: Optional[int] = None) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Returns True if an exception has been set for the given slot index, or for any slot index if slot_idx is None
|
|
98
|
+
"""
|
|
99
|
+
if slot_idx is not None:
|
|
100
|
+
return self.excs[slot_idx] is not None
|
|
101
|
+
return any(exc is not None for exc in self.excs)
|
|
97
102
|
|
|
98
|
-
def get_exc(self, slot_idx: int) -> Exception:
|
|
99
|
-
assert self.has_val[slot_idx] is False
|
|
100
|
-
assert self.excs[slot_idx] is not None
|
|
103
|
+
def get_exc(self, slot_idx: int) -> Optional[Exception]:
|
|
101
104
|
return self.excs[slot_idx]
|
|
102
105
|
|
|
106
|
+
def get_first_exc(self) -> Optional[Exception]:
|
|
107
|
+
for exc in self.excs:
|
|
108
|
+
if exc is not None:
|
|
109
|
+
return exc
|
|
110
|
+
return None
|
|
111
|
+
|
|
103
112
|
def set_exc(self, slot_idx: int, exc: Exception) -> None:
|
|
104
113
|
assert self.excs[slot_idx] is None
|
|
105
114
|
self.excs[slot_idx] = exc
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
self.file_urls[slot_idx] = None
|
|
116
|
+
# an exception means the value is None
|
|
117
|
+
self.has_val[slot_idx] = True
|
|
118
|
+
self.vals[slot_idx] = None
|
|
119
|
+
self.file_paths[slot_idx] = None
|
|
120
|
+
self.file_urls[slot_idx] = None
|
|
113
121
|
|
|
114
122
|
def __len__(self) -> int:
|
|
115
123
|
return len(self.vals)
|
|
@@ -124,6 +132,7 @@ class DataRow:
|
|
|
124
132
|
|
|
125
133
|
if self.file_urls[index] is not None and index in self.img_slot_idxs:
|
|
126
134
|
# if we need to load this from a file, it should have been materialized locally
|
|
135
|
+
# TODO this fails if the url was instantiated dynamically using astype()
|
|
127
136
|
assert self.file_paths[index] is not None
|
|
128
137
|
if self.vals[index] is None:
|
|
129
138
|
self.vals[index] = PIL.Image.open(self.file_paths[index])
|
|
@@ -231,7 +240,7 @@ class DataRow:
|
|
|
231
240
|
self.vals[index] = None
|
|
232
241
|
|
|
233
242
|
@property
|
|
234
|
-
def rowid(self) ->
|
|
243
|
+
def rowid(self) -> tuple[int, ...]:
|
|
235
244
|
return self.pk[:-1]
|
|
236
245
|
|
|
237
246
|
@property
|
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)
|