pixeltable 0.3.7__py3-none-any.whl → 0.3.8__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/column.py +1 -0
- pixeltable/catalog/table_version.py +4 -0
- pixeltable/exprs/__init__.py +2 -0
- pixeltable/exprs/arithmetic_expr.py +7 -11
- pixeltable/exprs/array_slice.py +1 -1
- pixeltable/exprs/column_property_ref.py +3 -3
- pixeltable/exprs/column_ref.py +3 -4
- pixeltable/exprs/comparison.py +2 -5
- pixeltable/exprs/compound_predicate.py +4 -4
- pixeltable/exprs/expr.py +11 -17
- pixeltable/exprs/expr_dict.py +3 -3
- pixeltable/exprs/expr_set.py +1 -1
- pixeltable/exprs/function_call.py +27 -37
- pixeltable/exprs/globals.py +3 -3
- pixeltable/exprs/in_predicate.py +1 -1
- pixeltable/exprs/inline_expr.py +3 -3
- pixeltable/exprs/is_null.py +1 -1
- pixeltable/exprs/json_mapper.py +2 -2
- pixeltable/exprs/json_path.py +17 -10
- pixeltable/exprs/literal.py +1 -1
- pixeltable/exprs/method_ref.py +2 -2
- pixeltable/exprs/row_builder.py +3 -5
- pixeltable/exprs/rowid_ref.py +4 -7
- pixeltable/exprs/similarity_expr.py +5 -5
- pixeltable/exprs/sql_element_cache.py +1 -1
- pixeltable/exprs/type_cast.py +2 -3
- pixeltable/exprs/variable.py +2 -2
- pixeltable/ext/__init__.py +2 -0
- pixeltable/ext/functions/__init__.py +2 -0
- pixeltable/ext/functions/yolox.py +3 -3
- pixeltable/func/__init__.py +2 -0
- pixeltable/func/aggregate_function.py +9 -9
- pixeltable/func/callable_function.py +3 -4
- pixeltable/func/expr_template_function.py +6 -16
- pixeltable/func/function.py +10 -8
- pixeltable/func/function_registry.py +1 -3
- pixeltable/func/query_template_function.py +2 -6
- pixeltable/func/signature.py +23 -22
- pixeltable/func/tools.py +3 -3
- pixeltable/func/udf.py +5 -3
- pixeltable/share/__init__.py +2 -0
- pixeltable/share/packager.py +3 -3
- pixeltable/share/publish.py +3 -5
- pixeltable/utils/coco.py +4 -4
- pixeltable/utils/console_output.py +1 -3
- pixeltable/utils/description_helper.py +1 -1
- pixeltable/utils/documents.py +3 -3
- pixeltable/utils/filecache.py +18 -8
- pixeltable/utils/formatter.py +2 -3
- pixeltable/utils/media_store.py +1 -1
- pixeltable/utils/pytorch.py +1 -1
- pixeltable/utils/sql.py +4 -4
- pixeltable/utils/transactional_directory.py +2 -1
- {pixeltable-0.3.7.dist-info → pixeltable-0.3.8.dist-info}/METADATA +1 -1
- {pixeltable-0.3.7.dist-info → pixeltable-0.3.8.dist-info}/RECORD +59 -59
- {pixeltable-0.3.7.dist-info → pixeltable-0.3.8.dist-info}/LICENSE +0 -0
- {pixeltable-0.3.7.dist-info → pixeltable-0.3.8.dist-info}/WHEEL +0 -0
- {pixeltable-0.3.7.dist-info → pixeltable-0.3.8.dist-info}/entry_points.txt +0 -0
pixeltable/__version__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# These version placeholders will be replaced during build.
|
|
2
|
-
__version__ = '0.3.
|
|
3
|
-
__version_tuple__ = (0, 3,
|
|
2
|
+
__version__ = '0.3.8'
|
|
3
|
+
__version_tuple__ = (0, 3, 8)
|
pixeltable/catalog/column.py
CHANGED
|
@@ -177,6 +177,10 @@ class TableVersion:
|
|
|
177
177
|
# Init external stores (this needs to happen after the schema is created)
|
|
178
178
|
self._init_external_stores(tbl_md)
|
|
179
179
|
|
|
180
|
+
# Force column metadata to load, in order to surface any invalid metadata now (as warnings)
|
|
181
|
+
for col in self.cols_by_id.values():
|
|
182
|
+
_ = col.value_expr
|
|
183
|
+
|
|
180
184
|
def __hash__(self) -> int:
|
|
181
185
|
return hash(self.id)
|
|
182
186
|
|
pixeltable/exprs/__init__.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Optional
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sql
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import pixeltable.exprs as exprs
|
|
9
|
-
import pixeltable.type_system as ts
|
|
7
|
+
from pixeltable import exceptions as excs, type_system as ts
|
|
10
8
|
|
|
11
9
|
from .data_row import DataRow
|
|
12
10
|
from .expr import Expr
|
|
@@ -50,13 +48,13 @@ class ArithmeticExpr(Expr):
|
|
|
50
48
|
# add parentheses around operands that are ArithmeticExprs to express precedence
|
|
51
49
|
op1_str = f'({self._op1})' if isinstance(self._op1, ArithmeticExpr) else str(self._op1)
|
|
52
50
|
op2_str = f'({self._op2})' if isinstance(self._op2, ArithmeticExpr) else str(self._op2)
|
|
53
|
-
return f'{op1_str} {
|
|
51
|
+
return f'{op1_str} {self.operator} {op2_str}'
|
|
54
52
|
|
|
55
53
|
def _equals(self, other: ArithmeticExpr) -> bool:
|
|
56
54
|
return self.operator == other.operator
|
|
57
55
|
|
|
58
56
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
59
|
-
return super()._id_attrs()
|
|
57
|
+
return [*super()._id_attrs(), ('operator', self.operator.value)]
|
|
60
58
|
|
|
61
59
|
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
62
60
|
assert self.col_type.is_int_type() or self.col_type.is_float_type() or self.col_type.is_json_type()
|
|
@@ -95,7 +93,7 @@ class ArithmeticExpr(Expr):
|
|
|
95
93
|
return sql.sql.expression.cast(sql.func.floor(left / nullif), self.col_type.to_sa_type())
|
|
96
94
|
if self.col_type.is_float_type():
|
|
97
95
|
return sql.sql.expression.cast(sql.func.floor(left / nullif), self.col_type.to_sa_type())
|
|
98
|
-
|
|
96
|
+
raise AssertionError()
|
|
99
97
|
|
|
100
98
|
def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
|
|
101
99
|
op1_val = data_row[self._op1.slot_idx]
|
|
@@ -113,9 +111,7 @@ class ArithmeticExpr(Expr):
|
|
|
113
111
|
|
|
114
112
|
data_row[self.slot_idx] = self.eval_nullable(op1_val, op2_val)
|
|
115
113
|
|
|
116
|
-
def eval_nullable(
|
|
117
|
-
self, op1_val: Union[int, float, None], op2_val: Union[int, float, None]
|
|
118
|
-
) -> Union[int, float, None]:
|
|
114
|
+
def eval_nullable(self, op1_val: Optional[float], op2_val: Optional[float]) -> Optional[float]:
|
|
119
115
|
"""
|
|
120
116
|
Return the result of evaluating the expression on two nullable int/float operands,
|
|
121
117
|
None is interpreted as SQL NULL
|
|
@@ -124,7 +120,7 @@ class ArithmeticExpr(Expr):
|
|
|
124
120
|
return None
|
|
125
121
|
return self.eval_non_null(op1_val, op2_val)
|
|
126
122
|
|
|
127
|
-
def eval_non_null(self, op1_val:
|
|
123
|
+
def eval_non_null(self, op1_val: float, op2_val: float) -> float:
|
|
128
124
|
"""
|
|
129
125
|
Return the result of evaluating the expression on two int/float operands
|
|
130
126
|
"""
|
pixeltable/exprs/array_slice.py
CHANGED
|
@@ -41,7 +41,7 @@ class ArraySlice(Expr):
|
|
|
41
41
|
return self.index == other.index
|
|
42
42
|
|
|
43
43
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
44
|
-
return super()._id_attrs()
|
|
44
|
+
return [*super()._id_attrs(), ('index', self.index)]
|
|
45
45
|
|
|
46
46
|
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
47
47
|
return None
|
|
@@ -40,7 +40,7 @@ class ColumnPropertyRef(Expr):
|
|
|
40
40
|
return self.prop == other.prop
|
|
41
41
|
|
|
42
42
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
43
|
-
return super()._id_attrs()
|
|
43
|
+
return [*super()._id_attrs(), ('prop', self.prop.value)]
|
|
44
44
|
|
|
45
45
|
@property
|
|
46
46
|
def _col_ref(self) -> ColumnRef:
|
|
@@ -52,7 +52,7 @@ class ColumnPropertyRef(Expr):
|
|
|
52
52
|
return f'{self._col_ref}.{self.prop.name.lower()}'
|
|
53
53
|
|
|
54
54
|
def is_error_prop(self) -> bool:
|
|
55
|
-
return self.prop
|
|
55
|
+
return self.prop in {self.Property.ERRORTYPE, self.Property.ERRORMSG}
|
|
56
56
|
|
|
57
57
|
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
58
58
|
if not self._col_ref.col.is_stored:
|
|
@@ -95,7 +95,7 @@ class ColumnPropertyRef(Expr):
|
|
|
95
95
|
else:
|
|
96
96
|
data_row[self.slot_idx] = str(exc)
|
|
97
97
|
else:
|
|
98
|
-
|
|
98
|
+
raise AssertionError()
|
|
99
99
|
|
|
100
100
|
def _as_dict(self) -> dict:
|
|
101
101
|
return {'prop': self.prop.value, **super()._as_dict()}
|
pixeltable/exprs/column_ref.py
CHANGED
|
@@ -6,9 +6,7 @@ from uuid import UUID
|
|
|
6
6
|
import sqlalchemy as sql
|
|
7
7
|
|
|
8
8
|
import pixeltable as pxt
|
|
9
|
-
import
|
|
10
|
-
import pixeltable.exceptions as excs
|
|
11
|
-
import pixeltable.iterators as iters
|
|
9
|
+
from pixeltable import catalog, exceptions as excs, iterators as iters
|
|
12
10
|
|
|
13
11
|
from ..utils.description_helper import DescriptionHelper
|
|
14
12
|
from .data_row import DataRow
|
|
@@ -84,7 +82,8 @@ class ColumnRef(Expr):
|
|
|
84
82
|
assert len(self.iter_arg_ctx.target_slot_idxs) == 1 # a single inline dict
|
|
85
83
|
|
|
86
84
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
87
|
-
return
|
|
85
|
+
return [
|
|
86
|
+
*super()._id_attrs(),
|
|
88
87
|
('tbl_id', self.col.tbl.id),
|
|
89
88
|
('col_id', self.col.id),
|
|
90
89
|
('perform_validation', self.perform_validation),
|
pixeltable/exprs/comparison.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sql
|
|
6
6
|
|
|
@@ -15,9 +15,6 @@ from .literal import Literal
|
|
|
15
15
|
from .row_builder import RowBuilder
|
|
16
16
|
from .sql_element_cache import SqlElementCache
|
|
17
17
|
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from pixeltable import index
|
|
20
|
-
|
|
21
18
|
|
|
22
19
|
class Comparison(Expr):
|
|
23
20
|
is_search_arg_comparison: bool
|
|
@@ -62,7 +59,7 @@ class Comparison(Expr):
|
|
|
62
59
|
return self.operator == other.operator
|
|
63
60
|
|
|
64
61
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
65
|
-
return super()._id_attrs()
|
|
62
|
+
return [*super()._id_attrs(), ('operator', self.operator.value)]
|
|
66
63
|
|
|
67
64
|
@property
|
|
68
65
|
def _op1(self) -> Expr:
|
|
@@ -5,7 +5,7 @@ from typing import Any, Callable, Optional
|
|
|
5
5
|
|
|
6
6
|
import sqlalchemy as sql
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
from pixeltable import type_system as ts
|
|
9
9
|
|
|
10
10
|
from .data_row import DataRow
|
|
11
11
|
from .expr import Expr
|
|
@@ -58,10 +58,10 @@ class CompoundPredicate(Expr):
|
|
|
58
58
|
return self.operator == other.operator
|
|
59
59
|
|
|
60
60
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
61
|
-
return super()._id_attrs()
|
|
61
|
+
return [*super()._id_attrs(), ('operator', self.operator.value)]
|
|
62
62
|
|
|
63
63
|
def split_conjuncts(self, condition: Callable[[Expr], bool]) -> tuple[list[Expr], Optional[Expr]]:
|
|
64
|
-
if self.operator
|
|
64
|
+
if self.operator in {LogicalOperator.OR, LogicalOperator.NOT}:
|
|
65
65
|
return super().split_conjuncts(condition)
|
|
66
66
|
matches = [op for op in self.components if condition(op)]
|
|
67
67
|
non_matches = [op for op in self.components if not condition(op)]
|
|
@@ -83,7 +83,7 @@ class CompoundPredicate(Expr):
|
|
|
83
83
|
if self.operator == LogicalOperator.NOT:
|
|
84
84
|
data_row[self.slot_idx] = not data_row[self.components[0].slot_idx]
|
|
85
85
|
else:
|
|
86
|
-
val =
|
|
86
|
+
val = self.operator == LogicalOperator.AND
|
|
87
87
|
op_function = operator.and_ if self.operator == LogicalOperator.AND else operator.or_
|
|
88
88
|
for op in self.components:
|
|
89
89
|
val = op_function(val, data_row[op.slot_idx])
|
pixeltable/exprs/expr.py
CHANGED
|
@@ -129,7 +129,7 @@ class Expr(abc.ABC):
|
|
|
129
129
|
"""
|
|
130
130
|
Subclass-specific comparison. Implemented as a function because __eq__() is needed to construct Comparisons.
|
|
131
131
|
"""
|
|
132
|
-
if type(self)
|
|
132
|
+
if type(self) is not type(other):
|
|
133
133
|
return False
|
|
134
134
|
if len(self.components) != len(other.components):
|
|
135
135
|
return False
|
|
@@ -171,10 +171,7 @@ class Expr(abc.ABC):
|
|
|
171
171
|
def list_equals(cls, a: list[Expr], b: list[Expr]) -> bool:
|
|
172
172
|
if len(a) != len(b):
|
|
173
173
|
return False
|
|
174
|
-
for i in range(len(a))
|
|
175
|
-
if not a[i].equals(b[i]):
|
|
176
|
-
return False
|
|
177
|
-
return True
|
|
174
|
+
return all(a[i].equals(b[i]) for i in range(len(a)))
|
|
178
175
|
|
|
179
176
|
def copy(self) -> Expr:
|
|
180
177
|
"""
|
|
@@ -216,9 +213,9 @@ class Expr(abc.ABC):
|
|
|
216
213
|
return new.copy()
|
|
217
214
|
for i in range(len(self.components)):
|
|
218
215
|
self.components[i] = self.components[i].substitute(spec)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return
|
|
216
|
+
result = self.maybe_literal()
|
|
217
|
+
result.id = result._create_id()
|
|
218
|
+
return result
|
|
222
219
|
|
|
223
220
|
@classmethod
|
|
224
221
|
def list_substitute(cls, expr_list: list[Expr], spec: dict[Expr, Expr]) -> None:
|
|
@@ -253,10 +250,7 @@ class Expr(abc.ABC):
|
|
|
253
250
|
from .column_ref import ColumnRef
|
|
254
251
|
|
|
255
252
|
col_refs = self.subexprs(ColumnRef)
|
|
256
|
-
for col_ref in col_refs
|
|
257
|
-
if not any(tbl.has_column(col_ref.col) for tbl in tbls):
|
|
258
|
-
return False
|
|
259
|
-
return True
|
|
253
|
+
return all(any(tbl.has_column(col_ref.col) for tbl in tbls) for col_ref in col_refs)
|
|
260
254
|
|
|
261
255
|
def retarget(self, tbl: catalog.TableVersionPath) -> Self:
|
|
262
256
|
"""Retarget ColumnRefs in this expr to the specific TableVersions in tbl."""
|
|
@@ -370,7 +364,7 @@ class Expr(abc.ABC):
|
|
|
370
364
|
|
|
371
365
|
@classmethod
|
|
372
366
|
def all_tbl_ids(cls, exprs_: Iterable[Expr]) -> set[UUID]:
|
|
373
|
-
return
|
|
367
|
+
return {tbl_id for e in exprs_ for tbl_id in e.tbl_ids()}
|
|
374
368
|
|
|
375
369
|
@classmethod
|
|
376
370
|
def get_refd_columns(cls, expr_dict: dict[str, Any]) -> list[catalog.Column]:
|
|
@@ -489,7 +483,7 @@ class Expr(abc.ABC):
|
|
|
489
483
|
return {'_classname': self.__class__.__name__, **self._as_dict()}
|
|
490
484
|
|
|
491
485
|
@classmethod
|
|
492
|
-
def as_dict_list(
|
|
486
|
+
def as_dict_list(cls, expr_list: list[Expr]) -> list[dict]:
|
|
493
487
|
return [e.as_dict() for e in expr_list]
|
|
494
488
|
|
|
495
489
|
def _as_dict(self) -> dict:
|
|
@@ -520,7 +514,7 @@ class Expr(abc.ABC):
|
|
|
520
514
|
|
|
521
515
|
@classmethod
|
|
522
516
|
def _from_dict(cls, d: dict, components: list[Expr]) -> Self:
|
|
523
|
-
|
|
517
|
+
raise AssertionError('not implemented')
|
|
524
518
|
|
|
525
519
|
def isin(self, value_set: Any) -> 'exprs.InPredicate':
|
|
526
520
|
from .in_predicate import InPredicate
|
|
@@ -792,13 +786,13 @@ class Expr(abc.ABC):
|
|
|
792
786
|
first_param = next(params_iter) if len(params) >= 1 else None
|
|
793
787
|
second_param = next(params_iter) if len(params) >= 2 else None
|
|
794
788
|
# Check that fn has at least one positional parameter
|
|
795
|
-
if len(params) == 0 or first_param.kind in
|
|
789
|
+
if len(params) == 0 or first_param.kind in {inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.VAR_KEYWORD}:
|
|
796
790
|
raise excs.Error(f'Function `{fn.__name__}` has no positional parameters.')
|
|
797
791
|
# Check that fn has at most one required parameter, i.e., its second parameter
|
|
798
792
|
# has no default and is not a varargs
|
|
799
793
|
if (
|
|
800
794
|
len(params) >= 2
|
|
801
|
-
and second_param.kind not in
|
|
795
|
+
and second_param.kind not in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD}
|
|
802
796
|
and second_param.default is inspect.Parameter.empty
|
|
803
797
|
):
|
|
804
798
|
raise excs.Error(f'Function `{fn.__name__}` has multiple required parameters.')
|
pixeltable/exprs/expr_dict.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from typing import Generic, Iterable, Iterator, Optional, TypeVar
|
|
2
2
|
|
|
3
|
-
T = TypeVar('T')
|
|
4
|
-
|
|
5
3
|
from .expr import Expr
|
|
6
4
|
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
|
|
7
7
|
|
|
8
8
|
class ExprDict(Generic[T]):
|
|
9
9
|
"""
|
|
@@ -47,7 +47,7 @@ class ExprDict(Generic[T]):
|
|
|
47
47
|
self._data.clear()
|
|
48
48
|
|
|
49
49
|
def keys(self) -> Iterator[Expr]:
|
|
50
|
-
return self
|
|
50
|
+
return iter(self)
|
|
51
51
|
|
|
52
52
|
def values(self) -> Iterator[T]:
|
|
53
53
|
return (value for _, value in self._data.values())
|
pixeltable/exprs/expr_set.py
CHANGED
|
@@ -46,7 +46,7 @@ class ExprSet(Generic[T]):
|
|
|
46
46
|
|
|
47
47
|
def __getitem__(self, index: object) -> Optional[T]:
|
|
48
48
|
"""Indexed lookup by slot_idx or Expr.id."""
|
|
49
|
-
assert isinstance(index, int
|
|
49
|
+
assert isinstance(index, (int, Expr))
|
|
50
50
|
if isinstance(index, int):
|
|
51
51
|
# return expr with matching slot_idx
|
|
52
52
|
return self.exprs_by_idx.get(index)
|
|
@@ -3,16 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
5
|
import sys
|
|
6
|
-
import warnings
|
|
7
6
|
from textwrap import dedent
|
|
8
7
|
from typing import Any, Optional, Sequence, Union
|
|
9
8
|
|
|
10
9
|
import sqlalchemy as sql
|
|
11
10
|
|
|
12
|
-
import
|
|
13
|
-
import pixeltable.exceptions as excs
|
|
14
|
-
import pixeltable.func as func
|
|
15
|
-
import pixeltable.type_system as ts
|
|
11
|
+
from pixeltable import catalog, exceptions as excs, func, type_system as ts
|
|
16
12
|
|
|
17
13
|
from .data_row import DataRow
|
|
18
14
|
from .expr import Expr
|
|
@@ -156,22 +152,18 @@ class FunctionCall(Expr):
|
|
|
156
152
|
return self.fn.name
|
|
157
153
|
|
|
158
154
|
def _equals(self, other: FunctionCall) -> bool:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if self.group_by_stop_idx != other.group_by_stop_idx:
|
|
168
|
-
return False
|
|
169
|
-
if self.order_by_start_idx != other.order_by_start_idx:
|
|
170
|
-
return False
|
|
171
|
-
return True
|
|
155
|
+
return (
|
|
156
|
+
self.fn == other.fn
|
|
157
|
+
and self.arg_idxs == other.arg_idxs
|
|
158
|
+
and self.kwarg_idxs == other.kwarg_idxs
|
|
159
|
+
and self.group_by_start_idx == other.group_by_start_idx
|
|
160
|
+
and self.group_by_stop_idx == other.group_by_stop_idx
|
|
161
|
+
and self.order_by_start_idx == other.order_by_start_idx
|
|
162
|
+
)
|
|
172
163
|
|
|
173
164
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
174
|
-
return
|
|
165
|
+
return [
|
|
166
|
+
*super()._id_attrs(),
|
|
175
167
|
('fn', id(self.fn)), # use the function pointer, not the fqn, which isn't set for lambdas
|
|
176
168
|
('args', self.arg_idxs),
|
|
177
169
|
('kwargs', self.kwarg_idxs),
|
|
@@ -192,12 +184,12 @@ class FunctionCall(Expr):
|
|
|
192
184
|
if self.is_method_call:
|
|
193
185
|
return f'{self.components[0]}.{self.fn.name}({self._print_args(1, inline)})'
|
|
194
186
|
else:
|
|
195
|
-
fn_name = self.fn.display_name
|
|
187
|
+
fn_name = self.fn.display_name or 'anonymous_fn'
|
|
196
188
|
return f'{fn_name}({self._print_args()})'
|
|
197
189
|
|
|
198
190
|
def _print_args(self, start_idx: int = 0, inline: bool = True) -> str:
|
|
199
191
|
arg_strs = [str(self.components[idx]) for idx in self.arg_idxs[start_idx:]]
|
|
200
|
-
arg_strs.extend([f'{param_name}={
|
|
192
|
+
arg_strs.extend([f'{param_name}={self.components[idx]}' for param_name, idx in self.kwarg_idxs.items()])
|
|
201
193
|
if len(self.order_by) > 0:
|
|
202
194
|
assert isinstance(self.fn, func.AggregateFunction)
|
|
203
195
|
if self.fn.requires_order_by:
|
|
@@ -297,7 +289,7 @@ class FunctionCall(Expr):
|
|
|
297
289
|
if (
|
|
298
290
|
val is None
|
|
299
291
|
and parameters_by_pos[idx].kind
|
|
300
|
-
in
|
|
292
|
+
in {inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD}
|
|
301
293
|
and not parameters_by_pos[idx].col_type.nullable
|
|
302
294
|
):
|
|
303
295
|
return None
|
|
@@ -310,7 +302,7 @@ class FunctionCall(Expr):
|
|
|
310
302
|
if (
|
|
311
303
|
val is None
|
|
312
304
|
and parameters[param_name].kind
|
|
313
|
-
in
|
|
305
|
+
in {inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD}
|
|
314
306
|
and not parameters[param_name].col_type.nullable
|
|
315
307
|
):
|
|
316
308
|
return None
|
|
@@ -466,20 +458,18 @@ class FunctionCall(Expr):
|
|
|
466
458
|
# the call_return_type that we just inferred (which matches the deserialization behavior prior to
|
|
467
459
|
# version 25).
|
|
468
460
|
return_type = call_return_type
|
|
469
|
-
|
|
470
|
-
# There is a return_type stored in metadata (schema version >= 25)
|
|
471
|
-
#
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
"""
|
|
482
|
-
).strip()
|
|
461
|
+
elif not return_type.is_supertype_of(call_return_type, ignore_nullable=True):
|
|
462
|
+
# There is a return_type stored in metadata (schema version >= 25),
|
|
463
|
+
# and the stored return_type of the UDF call doesn't match the column type of the FunctionCall.
|
|
464
|
+
validation_error = dedent(
|
|
465
|
+
f"""
|
|
466
|
+
The return type stored in the database for a UDF call to {fn.self_path!r} no longer
|
|
467
|
+
matches its return type as currently defined in the code. This probably means that the
|
|
468
|
+
code for {fn.self_path!r} has changed in a backward-incompatible way.
|
|
469
|
+
Return type of UDF call in the database: {return_type}
|
|
470
|
+
Return type of UDF as currently defined in code: {call_return_type}
|
|
471
|
+
"""
|
|
472
|
+
).strip()
|
|
483
473
|
|
|
484
474
|
fn_call = cls(
|
|
485
475
|
resolved_fn,
|
pixeltable/exprs/globals.py
CHANGED
|
@@ -36,7 +36,7 @@ class ComparisonOperator(enum.Enum):
|
|
|
36
36
|
return '>'
|
|
37
37
|
if self == self.GE:
|
|
38
38
|
return '>='
|
|
39
|
-
|
|
39
|
+
raise AssertionError()
|
|
40
40
|
|
|
41
41
|
def reverse(self) -> ComparisonOperator:
|
|
42
42
|
if self == self.LT:
|
|
@@ -62,7 +62,7 @@ class LogicalOperator(enum.Enum):
|
|
|
62
62
|
return '|'
|
|
63
63
|
if self == self.NOT:
|
|
64
64
|
return '~'
|
|
65
|
-
|
|
65
|
+
raise AssertionError()
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
class ArithmeticOperator(enum.Enum):
|
|
@@ -86,4 +86,4 @@ class ArithmeticOperator(enum.Enum):
|
|
|
86
86
|
return '%'
|
|
87
87
|
if self == self.FLOORDIV:
|
|
88
88
|
return '//'
|
|
89
|
-
|
|
89
|
+
raise AssertionError()
|
pixeltable/exprs/in_predicate.py
CHANGED
|
@@ -71,7 +71,7 @@ class InPredicate(Expr):
|
|
|
71
71
|
return self.value_list == other.value_list
|
|
72
72
|
|
|
73
73
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
74
|
-
return super()._id_attrs()
|
|
74
|
+
return [*super()._id_attrs(), ('value_list', self.value_list)]
|
|
75
75
|
|
|
76
76
|
def sql_expr(self, sql_elements: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
77
77
|
lhs_sql_exprs = sql_elements.get(self.components[0])
|
pixeltable/exprs/inline_expr.py
CHANGED
|
@@ -131,7 +131,7 @@ class InlineList(Expr):
|
|
|
131
131
|
def as_literal(self) -> Optional[Literal]:
|
|
132
132
|
if not all(isinstance(comp, Literal) for comp in self.components):
|
|
133
133
|
return None
|
|
134
|
-
return Literal(
|
|
134
|
+
return Literal([c.as_literal().val for c in self.components], self.col_type)
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
class InlineDict(Expr):
|
|
@@ -166,7 +166,7 @@ class InlineDict(Expr):
|
|
|
166
166
|
self.id = self._create_id()
|
|
167
167
|
|
|
168
168
|
def __repr__(self) -> str:
|
|
169
|
-
item_strs =
|
|
169
|
+
item_strs = [f"'{key}': {expr}" for key, expr in zip(self.keys, self.components)]
|
|
170
170
|
return '{' + ', '.join(item_strs) + '}'
|
|
171
171
|
|
|
172
172
|
def _equals(self, other: InlineDict) -> bool:
|
|
@@ -174,7 +174,7 @@ class InlineDict(Expr):
|
|
|
174
174
|
return self.keys == other.keys
|
|
175
175
|
|
|
176
176
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
177
|
-
return super()._id_attrs()
|
|
177
|
+
return [*super()._id_attrs(), ('keys', self.keys)]
|
|
178
178
|
|
|
179
179
|
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
180
180
|
return None
|
pixeltable/exprs/is_null.py
CHANGED
pixeltable/exprs/json_mapper.py
CHANGED
|
@@ -81,12 +81,12 @@ class JsonMapper(Expr):
|
|
|
81
81
|
"""
|
|
82
82
|
We override equals() because we need to avoid comparing our scope anchor.
|
|
83
83
|
"""
|
|
84
|
-
if type(self)
|
|
84
|
+
if type(self) is not type(other):
|
|
85
85
|
return False
|
|
86
86
|
return self._src_expr.equals(other._src_expr) and self._target_expr.equals(other._target_expr)
|
|
87
87
|
|
|
88
88
|
def __repr__(self) -> str:
|
|
89
|
-
return f'{
|
|
89
|
+
return f'{self._src_expr} >> {self._target_expr}'
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
92
|
def _src_expr(self) -> Expr:
|
pixeltable/exprs/json_path.py
CHANGED
|
@@ -6,14 +6,13 @@ import jmespath
|
|
|
6
6
|
import sqlalchemy as sql
|
|
7
7
|
|
|
8
8
|
import pixeltable as pxt
|
|
9
|
-
import
|
|
10
|
-
import pixeltable.exceptions as excs
|
|
11
|
-
import pixeltable.type_system as ts
|
|
9
|
+
from pixeltable import catalog, exceptions as excs, type_system as ts
|
|
12
10
|
|
|
13
11
|
from .data_row import DataRow
|
|
14
12
|
from .expr import Expr
|
|
15
13
|
from .globals import print_slice
|
|
16
14
|
from .json_mapper import JsonMapper
|
|
15
|
+
from .object_ref import ObjectRef
|
|
17
16
|
from .row_builder import RowBuilder
|
|
18
17
|
from .sql_element_cache import SqlElementCache
|
|
19
18
|
|
|
@@ -50,8 +49,16 @@ class JsonPath(Expr):
|
|
|
50
49
|
return f'{anchor_str}{"." if isinstance(self.path_elements[0], str) else ""}{self._json_path()}'
|
|
51
50
|
|
|
52
51
|
def _as_dict(self) -> dict:
|
|
52
|
+
assert len(self.components) <= 1
|
|
53
|
+
components_dict: dict[str, Any]
|
|
54
|
+
if len(self.components) == 0 or isinstance(self.components[0], ObjectRef):
|
|
55
|
+
# If the anchor is an ObjectRef, it means this JsonPath is a bound relative path. We store it as a relative
|
|
56
|
+
# path, *not* a bound path (which has no meaning in the dict).
|
|
57
|
+
components_dict = {}
|
|
58
|
+
else:
|
|
59
|
+
components_dict = super()._as_dict()
|
|
53
60
|
path_elements = [[el.start, el.stop, el.step] if isinstance(el, slice) else el for el in self.path_elements]
|
|
54
|
-
return {'path_elements': path_elements, 'scope_idx': self.scope_idx, **
|
|
61
|
+
return {'path_elements': path_elements, 'scope_idx': self.scope_idx, **components_dict}
|
|
55
62
|
|
|
56
63
|
@classmethod
|
|
57
64
|
def _from_dict(cls, d: dict, components: list[Expr]) -> JsonPath:
|
|
@@ -84,18 +91,18 @@ class JsonPath(Expr):
|
|
|
84
91
|
Construct a relative path that references an ancestor of the immediately enclosing JsonMapper.
|
|
85
92
|
"""
|
|
86
93
|
if not self.is_relative_path():
|
|
87
|
-
raise excs.Error(
|
|
94
|
+
raise excs.Error('() for an absolute path is invalid')
|
|
88
95
|
if len(args) != 1 or not isinstance(args[0], int) or args[0] >= 0:
|
|
89
|
-
raise excs.Error(
|
|
96
|
+
raise excs.Error('R() requires a negative index')
|
|
90
97
|
return JsonPath(None, [], args[0])
|
|
91
98
|
|
|
92
99
|
def __getattr__(self, name: str) -> 'JsonPath':
|
|
93
100
|
assert isinstance(name, str)
|
|
94
|
-
return JsonPath(self._anchor, self.path_elements
|
|
101
|
+
return JsonPath(self._anchor, [*self.path_elements, name])
|
|
95
102
|
|
|
96
103
|
def __getitem__(self, index: object) -> 'JsonPath':
|
|
97
104
|
if isinstance(index, (int, slice, str)):
|
|
98
|
-
return JsonPath(self._anchor, self.path_elements
|
|
105
|
+
return JsonPath(self._anchor, [*self.path_elements, index])
|
|
99
106
|
raise excs.Error(f'Invalid json list index: {index}')
|
|
100
107
|
|
|
101
108
|
def __rshift__(self, other: object) -> 'JsonMapper':
|
|
@@ -120,7 +127,7 @@ class JsonPath(Expr):
|
|
|
120
127
|
|
|
121
128
|
clean_name = ''.join(map(cleanup_char, ret_name))
|
|
122
129
|
clean_name = clean_name.lstrip('_') # remove leading underscore
|
|
123
|
-
if clean_name
|
|
130
|
+
if not clean_name: # Replace '' with None
|
|
124
131
|
clean_name = None
|
|
125
132
|
|
|
126
133
|
assert clean_name is None or catalog.is_valid_identifier(clean_name)
|
|
@@ -130,7 +137,7 @@ class JsonPath(Expr):
|
|
|
130
137
|
return self.path_elements == other.path_elements
|
|
131
138
|
|
|
132
139
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
133
|
-
return super()._id_attrs()
|
|
140
|
+
return [*super()._id_attrs(), ('path_elements', self.path_elements)]
|
|
134
141
|
|
|
135
142
|
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
136
143
|
"""
|
pixeltable/exprs/literal.py
CHANGED
|
@@ -62,7 +62,7 @@ class Literal(Expr):
|
|
|
62
62
|
return self.val == other.val
|
|
63
63
|
|
|
64
64
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
65
|
-
return super()._id_attrs()
|
|
65
|
+
return [*super()._id_attrs(), ('val', self.val)]
|
|
66
66
|
|
|
67
67
|
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
68
68
|
# Return a sql object so that constants can participate in SQL expressions
|
pixeltable/exprs/method_ref.py
CHANGED
|
@@ -53,13 +53,13 @@ class MethodRef(Expr):
|
|
|
53
53
|
return self.base_expr.id == other.base_expr.id and self.method_name == other.method_name
|
|
54
54
|
|
|
55
55
|
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
56
|
-
return super()._id_attrs()
|
|
56
|
+
return [*super()._id_attrs(), ('method_name', self.method_name)]
|
|
57
57
|
|
|
58
58
|
def sql_expr(self, _: SqlElementCache) -> Optional[sql.ColumnElement]:
|
|
59
59
|
return None
|
|
60
60
|
|
|
61
61
|
def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
|
|
62
|
-
|
|
62
|
+
raise AssertionError('MethodRef cannot be evaluated directly')
|
|
63
63
|
|
|
64
64
|
def __repr__(self) -> str:
|
|
65
65
|
return f'{self.base_expr}.{self.method_name}'
|