pixeltable 0.2.13__py3-none-any.whl → 0.2.15__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.

Files changed (58) hide show
  1. pixeltable/__init__.py +1 -1
  2. pixeltable/__version__.py +2 -2
  3. pixeltable/catalog/column.py +8 -3
  4. pixeltable/catalog/globals.py +8 -0
  5. pixeltable/catalog/table.py +25 -9
  6. pixeltable/catalog/table_version.py +30 -55
  7. pixeltable/catalog/view.py +1 -1
  8. pixeltable/env.py +4 -4
  9. pixeltable/exec/__init__.py +2 -1
  10. pixeltable/exec/row_update_node.py +61 -0
  11. pixeltable/exec/{sql_scan_node.py → sql_node.py} +120 -56
  12. pixeltable/exprs/__init__.py +1 -1
  13. pixeltable/exprs/arithmetic_expr.py +41 -16
  14. pixeltable/exprs/expr.py +72 -22
  15. pixeltable/exprs/function_call.py +64 -29
  16. pixeltable/exprs/globals.py +5 -1
  17. pixeltable/exprs/inline_array.py +18 -11
  18. pixeltable/exprs/method_ref.py +63 -0
  19. pixeltable/ext/__init__.py +9 -0
  20. pixeltable/ext/functions/__init__.py +8 -0
  21. pixeltable/ext/functions/whisperx.py +45 -5
  22. pixeltable/ext/functions/yolox.py +60 -14
  23. pixeltable/func/callable_function.py +12 -4
  24. pixeltable/func/expr_template_function.py +1 -1
  25. pixeltable/func/function.py +12 -2
  26. pixeltable/func/function_registry.py +24 -9
  27. pixeltable/func/udf.py +32 -4
  28. pixeltable/functions/__init__.py +1 -1
  29. pixeltable/functions/fireworks.py +33 -0
  30. pixeltable/functions/huggingface.py +96 -6
  31. pixeltable/functions/image.py +226 -41
  32. pixeltable/functions/json.py +46 -0
  33. pixeltable/functions/openai.py +214 -0
  34. pixeltable/functions/string.py +195 -218
  35. pixeltable/functions/timestamp.py +210 -0
  36. pixeltable/functions/together.py +106 -0
  37. pixeltable/functions/video.py +2 -2
  38. pixeltable/functions/{eval.py → vision.py} +170 -27
  39. pixeltable/functions/whisper.py +32 -0
  40. pixeltable/io/__init__.py +1 -1
  41. pixeltable/io/external_store.py +2 -2
  42. pixeltable/io/globals.py +133 -1
  43. pixeltable/io/pandas.py +82 -31
  44. pixeltable/iterators/video.py +55 -23
  45. pixeltable/metadata/__init__.py +1 -1
  46. pixeltable/metadata/converters/convert_18.py +39 -0
  47. pixeltable/metadata/notes.py +10 -0
  48. pixeltable/plan.py +76 -1
  49. pixeltable/store.py +65 -28
  50. pixeltable/tool/create_test_db_dump.py +8 -9
  51. pixeltable/tool/doc_plugins/griffe.py +4 -0
  52. pixeltable/type_system.py +84 -63
  53. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/METADATA +2 -2
  54. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/RECORD +57 -51
  55. pixeltable/exprs/image_member_access.py +0 -96
  56. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/LICENSE +0 -0
  57. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/WHEEL +0 -0
  58. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/entry_points.txt +0 -0
@@ -1,28 +1,28 @@
1
1
  from __future__ import annotations
2
- from typing import Optional, List, Any, Dict, Tuple
2
+
3
+ from typing import Any, Optional
3
4
 
4
5
  import sqlalchemy as sql
5
6
 
6
- from .globals import ArithmeticOperator
7
- from .expr import Expr
8
- from .data_row import DataRow
9
- from .row_builder import RowBuilder
10
7
  import pixeltable.exceptions as excs
11
- import pixeltable.catalog as catalog
12
8
  import pixeltable.type_system as ts
13
9
 
10
+ from .data_row import DataRow
11
+ from .expr import Expr
12
+ from .globals import ArithmeticOperator
13
+ from .row_builder import RowBuilder
14
+
14
15
 
15
16
  class ArithmeticExpr(Expr):
16
17
  """
17
18
  Allows arithmetic exprs on json paths
18
19
  """
19
20
  def __init__(self, operator: ArithmeticOperator, op1: Expr, op2: Expr):
20
- # TODO: determine most specific common supertype
21
- if op1.col_type.is_json_type() or op2.col_type.is_json_type():
21
+ if op1.col_type.is_json_type() or op2.col_type.is_json_type() or operator == ArithmeticOperator.DIV:
22
22
  # we assume it's a float
23
- super().__init__(ts.FloatType())
23
+ super().__init__(ts.FloatType(nullable=(op1.col_type.nullable or op2.col_type.nullable)))
24
24
  else:
25
- super().__init__(ts.ColumnType.supertype(op1.col_type, op2.col_type))
25
+ super().__init__(op1.col_type.supertype(op2.col_type))
26
26
  self.operator = operator
27
27
  self.components = [op1, op2]
28
28
 
@@ -43,7 +43,7 @@ class ArithmeticExpr(Expr):
43
43
  def _equals(self, other: ArithmeticExpr) -> bool:
44
44
  return self.operator == other.operator
45
45
 
46
- def _id_attrs(self) -> List[Tuple[str, Any]]:
46
+ def _id_attrs(self) -> list[tuple[str, Any]]:
47
47
  return super()._id_attrs() + [('operator', self.operator.value)]
48
48
 
49
49
  @property
@@ -55,6 +55,7 @@ class ArithmeticExpr(Expr):
55
55
  return self.components[1]
56
56
 
57
57
  def sql_expr(self) -> Optional[sql.ClauseElement]:
58
+ assert self.col_type.is_int_type() or self.col_type.is_float_type() or self.col_type.is_json_type()
58
59
  left = self._op1.sql_expr()
59
60
  right = self._op2.sql_expr()
60
61
  if left is None or right is None:
@@ -66,14 +67,31 @@ class ArithmeticExpr(Expr):
66
67
  if self.operator == ArithmeticOperator.MUL:
67
68
  return left * right
68
69
  if self.operator == ArithmeticOperator.DIV:
69
- return left / right
70
+ assert self.col_type.is_float_type()
71
+ # We have to cast to a `float`, or else we'll get a `Decimal`
72
+ return sql.sql.expression.cast(left / right, sql.Float)
70
73
  if self.operator == ArithmeticOperator.MOD:
71
- return left % right
74
+ if self.col_type.is_int_type():
75
+ return left % right
76
+ if self.col_type.is_float_type():
77
+ # Postgres does not support modulus for floats
78
+ return None
79
+ if self.operator == ArithmeticOperator.FLOORDIV:
80
+ # Postgres has a DIV operator, but it behaves differently from Python's // operator
81
+ # (Postgres rounds toward 0, Python rounds toward negative infinity)
82
+ # We need the behavior to be consistent, so that expressions will evaluate the same way
83
+ # whether or not their operands can be translated to SQL. These SQL clauses should
84
+ # mimic the behavior of Python's // operator.
85
+ if self.col_type.is_int_type():
86
+ return sql.sql.expression.cast(sql.func.floor(left / right), sql.Integer)
87
+ if self.col_type.is_float_type():
88
+ return sql.sql.expression.cast(sql.func.floor(left / right), sql.Float)
72
89
 
73
90
  def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
74
91
  op1_val = data_row[self._op1.slot_idx]
75
92
  op2_val = data_row[self._op2.slot_idx]
76
- # check types if we couldn't do that prior to execution
93
+
94
+ # if one or both columns is JsonTyped, we need a dynamic check that they are numeric
77
95
  if self._op1.col_type.is_json_type() and not isinstance(op1_val, int) and not isinstance(op1_val, float):
78
96
  raise excs.Error(
79
97
  f'{self.operator} requires numeric type, but {self._op1} has type {type(op1_val).__name__}')
@@ -81,6 +99,11 @@ class ArithmeticExpr(Expr):
81
99
  raise excs.Error(
82
100
  f'{self.operator} requires numeric type, but {self._op2} has type {type(op2_val).__name__}')
83
101
 
102
+ # if either operand is None, always return None
103
+ if op1_val is None or op2_val is None:
104
+ data_row[self.slot_idx] = None
105
+ return
106
+
84
107
  if self.operator == ArithmeticOperator.ADD:
85
108
  data_row[self.slot_idx] = op1_val + op2_val
86
109
  elif self.operator == ArithmeticOperator.SUB:
@@ -91,12 +114,14 @@ class ArithmeticExpr(Expr):
91
114
  data_row[self.slot_idx] = op1_val / op2_val
92
115
  elif self.operator == ArithmeticOperator.MOD:
93
116
  data_row[self.slot_idx] = op1_val % op2_val
117
+ elif self.operator == ArithmeticOperator.FLOORDIV:
118
+ data_row[self.slot_idx] = op1_val // op2_val
94
119
 
95
- def _as_dict(self) -> Dict:
120
+ def _as_dict(self) -> dict:
96
121
  return {'operator': self.operator.value, **super()._as_dict()}
97
122
 
98
123
  @classmethod
99
- def _from_dict(cls, d: Dict, components: List[Expr]) -> Expr:
124
+ def _from_dict(cls, d: dict, components: list[Expr]) -> Expr:
100
125
  assert 'operator' in d
101
126
  assert len(components) == 2
102
127
  return cls(ArithmeticOperator(d['operator']), components[0], components[1])
pixeltable/exprs/expr.py CHANGED
@@ -7,7 +7,6 @@ import inspect
7
7
  import json
8
8
  import sys
9
9
  import typing
10
- from itertools import islice
11
10
  from typing import Union, Optional, List, Callable, Any, Dict, Tuple, Set, Generator, Type
12
11
  from uuid import UUID
13
12
 
@@ -16,8 +15,8 @@ import sqlalchemy as sql
16
15
  import pixeltable
17
16
  import pixeltable.catalog as catalog
18
17
  import pixeltable.exceptions as excs
19
- import pixeltable.type_system as ts
20
18
  import pixeltable.func as func
19
+ import pixeltable.type_system as ts
21
20
  from .data_row import DataRow
22
21
  from .globals import ComparisonOperator, LogicalOperator, LiteralPythonTypes, ArithmeticOperator
23
22
 
@@ -91,8 +90,8 @@ class Expr(abc.ABC):
91
90
 
92
91
  def default_column_name(self) -> Optional[str]:
93
92
  """
94
- Returns:
95
- None if this expression lacks a default name,
93
+ Returns:
94
+ None if this expression lacks a default name,
96
95
  or a valid identifier (according to catalog.is_valid_identifer) otherwise.
97
96
  """
98
97
  return None
@@ -231,9 +230,8 @@ class Expr(abc.ABC):
231
230
  self.components[i] = self.components[i]._retarget(tbl_versions)
232
231
  return self
233
232
 
234
- @abc.abstractmethod
235
233
  def __str__(self) -> str:
236
- pass
234
+ return f'<Expression of type {type(self)}>'
237
235
 
238
236
  def display_str(self, inline: bool = True) -> str:
239
237
  """
@@ -264,7 +262,7 @@ class Expr(abc.ABC):
264
262
  if is_match:
265
263
  yield self
266
264
 
267
- def contains(self, cls: Optional[Type[Expr]] = None, filter: Optional[Callable[[Expr], bool]] = None) -> bool:
265
+ def _contains(self, cls: Optional[Type[Expr]] = None, filter: Optional[Callable[[Expr], bool]] = None) -> bool:
268
266
  """
269
267
  Returns True if any subexpr is an instance of cls.
270
268
  """
@@ -319,17 +317,20 @@ class Expr(abc.ABC):
319
317
  """
320
318
  if isinstance(o, Expr):
321
319
  return o
322
- # try to create a literal
320
+ # Try to create a literal. We need to check for InlineArray/InlineDict
321
+ # first, to prevent arrays from inappropriately being interpreted as JsonType
322
+ # literals.
323
+ # TODO: general cleanup of InlineArray/InlineDict
324
+ if isinstance(o, list):
325
+ from .inline_array import InlineArray
326
+ return InlineArray(tuple(o))
327
+ if isinstance(o, dict):
328
+ from .inline_dict import InlineDict
329
+ return InlineDict(o)
323
330
  obj_type = ts.ColumnType.infer_literal_type(o)
324
331
  if obj_type is not None:
325
332
  from .literal import Literal
326
333
  return Literal(o, col_type=obj_type)
327
- if isinstance(o, dict):
328
- from .inline_dict import InlineDict
329
- return InlineDict(o)
330
- elif isinstance(o, list):
331
- from .inline_array import InlineArray
332
- return InlineArray(tuple(o))
333
334
  return None
334
335
 
335
336
  @abc.abstractmethod
@@ -427,6 +428,14 @@ class Expr(abc.ABC):
427
428
  # Return a `FunctionCall` obtained by passing this `Expr` to the new `function`.
428
429
  return function(self)
429
430
 
431
+ def __dir__(self) -> list[str]:
432
+ attrs = ['isin', 'astype', 'apply']
433
+ attrs += [
434
+ f.name
435
+ for f in func.FunctionRegistry.get().get_type_methods(self.col_type.type_enum)
436
+ ]
437
+ return attrs
438
+
430
439
  def __getitem__(self, index: object) -> Expr:
431
440
  if self.col_type.is_json_type():
432
441
  from .json_path import JsonPath
@@ -434,19 +443,23 @@ class Expr(abc.ABC):
434
443
  if self.col_type.is_array_type():
435
444
  from .array_slice import ArraySlice
436
445
  return ArraySlice(self, index)
437
- raise excs.Error(f'Type {self.col_type} is not subscriptable')
446
+ raise AttributeError(f'Type {self.col_type} is not subscriptable')
438
447
 
439
- def __getattr__(self, name: str) -> Union['pixeltable.exprs.ImageMemberAccess', 'pixeltable.exprs.JsonPath']:
448
+ def __getattr__(self, name: str) -> Union['pixeltable.exprs.MethodRef', 'pixeltable.exprs.FunctionCall', 'pixeltable.exprs.JsonPath']:
440
449
  """
441
450
  ex.: <img col>.rotate(60)
442
451
  """
443
- if self.col_type.is_image_type():
444
- from .image_member_access import ImageMemberAccess
445
- return ImageMemberAccess(name, self)
446
452
  if self.col_type.is_json_type():
447
- from .json_path import JsonPath
448
- return JsonPath(self).__getattr__(name)
449
- raise excs.Error(f'Member access not supported on type {self.col_type}: {name}')
453
+ return pixeltable.exprs.JsonPath(self).__getattr__(name)
454
+ else:
455
+ method_ref = pixeltable.exprs.MethodRef(self, name)
456
+ if method_ref.fn.is_property:
457
+ # Marked as a property, so autoinvoke the method to obtain a `FunctionCall`
458
+ assert method_ref.fn.arity == 1
459
+ return method_ref.fn(method_ref.base_expr)
460
+ else:
461
+ # Return the `MethodRef` object itself; it requires arguments to become a `FunctionCall`
462
+ return method_ref
450
463
 
451
464
  def __bool__(self) -> bool:
452
465
  raise TypeError(
@@ -490,6 +503,9 @@ class Expr(abc.ABC):
490
503
  return Comparison(op, self, Literal(other)) # type: ignore[arg-type]
491
504
  raise TypeError(f'Other must be Expr or literal: {type(other)}')
492
505
 
506
+ def __neg__(self) -> 'pixeltable.exprs.ArithmeticExpr':
507
+ return self._make_arithmetic_expr(ArithmeticOperator.MUL, -1)
508
+
493
509
  def __add__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
494
510
  return self._make_arithmetic_expr(ArithmeticOperator.ADD, other)
495
511
 
@@ -505,6 +521,27 @@ class Expr(abc.ABC):
505
521
  def __mod__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
506
522
  return self._make_arithmetic_expr(ArithmeticOperator.MOD, other)
507
523
 
524
+ def __floordiv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
525
+ return self._make_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
526
+
527
+ def __radd__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
528
+ return self._rmake_arithmetic_expr(ArithmeticOperator.ADD, other)
529
+
530
+ def __rsub__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
531
+ return self._rmake_arithmetic_expr(ArithmeticOperator.SUB, other)
532
+
533
+ def __rmul__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
534
+ return self._rmake_arithmetic_expr(ArithmeticOperator.MUL, other)
535
+
536
+ def __rtruediv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
537
+ return self._rmake_arithmetic_expr(ArithmeticOperator.DIV, other)
538
+
539
+ def __rmod__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
540
+ return self._rmake_arithmetic_expr(ArithmeticOperator.MOD, other)
541
+
542
+ def __rfloordiv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
543
+ return self._rmake_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
544
+
508
545
  def _make_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
509
546
  """
510
547
  other: Union[Expr, LiteralPythonTypes]
@@ -518,6 +555,19 @@ class Expr(abc.ABC):
518
555
  return ArithmeticExpr(op, self, Literal(other)) # type: ignore[arg-type]
519
556
  raise TypeError(f'Other must be Expr or literal: {type(other)}')
520
557
 
558
+ def _rmake_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
559
+ """
560
+ Right-handed version of _make_arithmetic_expr. other must be a literal; if it were an Expr,
561
+ the operation would have already been evaluated in its left-handed form.
562
+ """
563
+ # TODO: check for compatibility
564
+ from .arithmetic_expr import ArithmeticExpr
565
+ from .literal import Literal
566
+ assert not isinstance(other, Expr) # Else the left-handed form would have evaluated first
567
+ if isinstance(other, typing.get_args(LiteralPythonTypes)):
568
+ return ArithmeticExpr(op, Literal(other), self) # type: ignore[arg-type]
569
+ raise TypeError(f'Other must be Expr or literal: {type(other)}')
570
+
521
571
  def __and__(self, other: object) -> Expr:
522
572
  if not isinstance(other, Expr):
523
573
  raise TypeError(f'Other needs to be an expression: {type(other)}')
@@ -20,6 +20,22 @@ from .rowid_ref import RowidRef
20
20
 
21
21
 
22
22
  class FunctionCall(Expr):
23
+
24
+ fn: func.Function
25
+ is_method_call: bool
26
+ agg_init_args: Dict[str, Any]
27
+ args: List[Tuple[Optional[int], Optional[Any]]]
28
+ kwargs: Dict[str, Tuple[Optional[int], Optional[Any]]]
29
+ arg_types: List[ts.ColumnType]
30
+ kwarg_types: Dict[str, ts.ColumnType]
31
+ group_by_start_idx: int
32
+ group_by_stop_idx: int
33
+ fn_expr_idx: int
34
+ order_by_start_idx: int
35
+ constant_args: set[str]
36
+ aggregator: Optional[Any]
37
+ current_partition_vals: Optional[List[Any]]
38
+
23
39
  def __init__(
24
40
  self, fn: func.Function, bound_args: Dict[str, Any], order_by_clause: Optional[List[Any]] = None,
25
41
  group_by_clause: Optional[List[Any]] = None, is_method_call: bool = False):
@@ -31,9 +47,9 @@ class FunctionCall(Expr):
31
47
  super().__init__(fn.call_return_type(bound_args))
32
48
  self.fn = fn
33
49
  self.is_method_call = is_method_call
34
- self.check_args(signature, bound_args)
50
+ self.normalize_args(signature, bound_args)
35
51
 
36
- self.agg_init_args: Dict[str, Any] = {}
52
+ self.agg_init_args = {}
37
53
  if self.is_agg_fn_call:
38
54
  # we separate out the init args for the aggregator
39
55
  self.agg_init_args = {
@@ -42,17 +58,16 @@ class FunctionCall(Expr):
42
58
  bound_args = {arg_name: arg for arg_name, arg in bound_args.items() if arg_name not in fn.init_param_names}
43
59
 
44
60
  # construct components, args, kwargs
45
- self.components: List[Expr] = []
46
61
 
47
62
  # Tuple[int, Any]:
48
63
  # - for Exprs: (index into components, None)
49
64
  # - otherwise: (None, val)
50
- self.args: List[Tuple[Optional[int], Optional[Any]]] = []
51
- self.kwargs: Dict[str, Tuple[Optional[int], Optional[Any]]] = {}
65
+ self.args = []
66
+ self.kwargs = {}
52
67
 
53
68
  # we record the types of non-variable parameters for runtime type checks
54
- self.arg_types: List[ts.ColumnType] = []
55
- self.kwarg_types: Dict[str, ts.ColumnType] = {}
69
+ self.arg_types = []
70
+ self.kwarg_types = {}
56
71
  # the prefix of parameters that are bound can be passed by position
57
72
  for param in fn.signature.py_signature.parameters.values():
58
73
  if param.name not in bound_args or param.kind == inspect.Parameter.KEYWORD_ONLY:
@@ -111,8 +126,8 @@ class FunctionCall(Expr):
111
126
 
112
127
  self.constant_args = {param_name for param_name, arg in bound_args.items() if not isinstance(arg, Expr)}
113
128
  # execution state for aggregate functions
114
- self.aggregator: Optional[Any] = None
115
- self.current_partition_vals: Optional[List[Any]] = None
129
+ self.aggregator = None
130
+ self.current_partition_vals = None
116
131
 
117
132
  self.id = self._create_id()
118
133
 
@@ -120,26 +135,37 @@ class FunctionCall(Expr):
120
135
  target = tbl._tbl_version_path.tbl_version
121
136
  return [RowidRef(target, i) for i in range(target.num_rowid_columns())]
122
137
 
138
+ def default_column_name(self) -> Optional[str]:
139
+ if self.fn.is_property:
140
+ return self.fn.name
141
+ return super().default_column_name()
142
+
123
143
  @classmethod
124
- def check_args(cls, signature: func.Signature, bound_args: Dict[str, Any]) -> None:
125
- """Checks that bound_args are compatible with signature.
144
+ def normalize_args(cls, signature: func.Signature, bound_args: Dict[str, Any]) -> None:
145
+ """Converts all args to Exprs and checks that they are compatible with signature.
126
146
 
127
- Convert literals to the correct type and update bound_args in place, if necessary.
147
+ Updates bound_args in place, where necessary.
128
148
  """
129
149
  for param_name, arg in bound_args.items():
130
150
  param = signature.parameters[param_name]
151
+ is_var_param = param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
152
+
131
153
  if isinstance(arg, dict):
132
154
  try:
133
155
  arg = InlineDict(arg)
134
156
  bound_args[param_name] = arg
157
+ continue
135
158
  except excs.Error:
136
159
  # this didn't work, but it might be a literal
137
160
  pass
161
+
138
162
  if isinstance(arg, list) or isinstance(arg, tuple):
139
163
  try:
140
164
  # If the column type is JsonType, force the literal to be JSON
141
- arg = InlineArray(arg, force_json=param.col_type is not None and param.col_type.is_json_type())
165
+ is_json = is_var_param or (param.col_type is not None and param.col_type.is_json_type())
166
+ arg = InlineArray(arg, force_json=is_json)
142
167
  bound_args[param_name] = arg
168
+ continue
143
169
  except excs.Error:
144
170
  # this didn't work, but it might be a literal
145
171
  pass
@@ -149,30 +175,39 @@ class FunctionCall(Expr):
149
175
  try:
150
176
  _ = json.dumps(arg)
151
177
  except TypeError:
152
- raise excs.Error(f"Argument for parameter '{param_name}' is not json-serializable: {arg}")
178
+ raise excs.Error(f'Argument for parameter {param_name!r} is not json-serializable: {arg}')
153
179
  if arg is not None:
154
180
  try:
155
181
  param_type = param.col_type
156
182
  bound_args[param_name] = param_type.create_literal(arg)
157
183
  except TypeError as e:
158
184
  msg = str(e)
159
- raise excs.Error(f"Argument for parameter '{param_name}': {msg[0].lower() + msg[1:]}")
185
+ raise excs.Error(f'Argument for parameter {param_name!r}: {msg[0].lower() + msg[1:]}')
160
186
  continue
161
187
 
162
- # variable parameters don't get type-checked, but they both need to be json-typed
163
- if param.kind == inspect.Parameter.VAR_POSITIONAL:
164
- assert isinstance(arg, InlineArray)
165
- arg.col_type = ts.JsonType()
166
- continue
167
- if param.kind == inspect.Parameter.VAR_KEYWORD:
168
- assert isinstance(arg, InlineDict)
169
- arg.col_type = ts.JsonType()
170
- continue
171
-
172
- if not param_type.is_supertype_of(arg.col_type):
173
- raise excs.Error(
174
- f'Parameter {param_name}: argument type {arg.col_type} does not match parameter type '
175
- f'{param_type}')
188
+ # these checks break the db migration test, because InlineArray isn't serialized correctly (it looses
189
+ # the type information)
190
+ # if is_var_param:
191
+ # if param.kind == inspect.Parameter.VAR_POSITIONAL:
192
+ # if not isinstance(arg, InlineArray) or not arg.col_type.is_json_type():
193
+ # pass
194
+ # assert isinstance(arg, InlineArray), type(arg)
195
+ # assert arg.col_type.is_json_type()
196
+ # if param.kind == inspect.Parameter.VAR_KEYWORD:
197
+ # if not isinstance(arg, InlineDict):
198
+ # pass
199
+ # assert isinstance(arg, InlineDict), type(arg)
200
+ if is_var_param:
201
+ pass
202
+ else:
203
+ assert param.col_type is not None
204
+ # Check that the argument is consistent with the expected parameter type, with the allowance that
205
+ # non-nullable parameters can still accept nullable arguments (since function calls with Nones
206
+ # assigned to non-nullable parameters will always return None)
207
+ if not param.col_type.is_supertype_of(arg.col_type, ignore_nullable=True):
208
+ raise excs.Error(
209
+ f'Parameter {param_name}: argument type {arg.col_type} does not match parameter type '
210
+ f'{param.col_type}')
176
211
 
177
212
  def _equals(self, other: FunctionCall) -> bool:
178
213
  if self.fn != other.fn:
@@ -29,6 +29,8 @@ class ComparisonOperator(enum.Enum):
29
29
  return '<='
30
30
  if self == self.EQ:
31
31
  return '=='
32
+ if self == self.NE:
33
+ return '!='
32
34
  if self == self.GT:
33
35
  return '>'
34
36
  if self == self.GE:
@@ -66,6 +68,7 @@ class ArithmeticOperator(enum.Enum):
66
68
  MUL = 2
67
69
  DIV = 3
68
70
  MOD = 4
71
+ FLOORDIV = 5
69
72
 
70
73
  def __str__(self) -> str:
71
74
  if self == self.ADD:
@@ -78,4 +81,5 @@ class ArithmeticOperator(enum.Enum):
78
81
  return '/'
79
82
  if self == self.MOD:
80
83
  return '%'
81
-
84
+ if self == self.FLOORDIV:
85
+ return '//'
@@ -21,6 +21,9 @@ class InlineArray(Expr):
21
21
  is `True`, it will always be cast as a `JsonType`. If `force_json` is `False`, it will be cast as an
22
22
  `ArrayType` if it is a homogenous array of scalars or arrays, or a `JsonType` otherwise.
23
23
  """
24
+
25
+ elements: List[Tuple[Optional[int], Any]]
26
+
24
27
  def __init__(self, elements: Tuple, force_json: bool = False):
25
28
  # we need to call this in order to populate self.components
26
29
  super().__init__(ts.ArrayType((len(elements),), ts.IntType()))
@@ -28,7 +31,7 @@ class InlineArray(Expr):
28
31
  # elements contains
29
32
  # - for Expr elements: (index into components, None)
30
33
  # - for non-Expr elements: (None, value)
31
- self.elements: List[Tuple[Optional[int], Any]] = []
34
+ self.elements = []
32
35
  for el in elements:
33
36
  el = copy.deepcopy(el)
34
37
  if isinstance(el, list):
@@ -43,14 +46,16 @@ class InlineArray(Expr):
43
46
  else:
44
47
  self.elements.append((None, el))
45
48
 
46
- inferred_element_type = ts.InvalidType()
47
- for idx, val in self.elements:
48
- if idx is not None:
49
- inferred_element_type = ts.ColumnType.supertype(inferred_element_type, self.components[idx].col_type)
50
- else:
51
- inferred_element_type = ts.ColumnType.supertype(inferred_element_type, ts.ColumnType.infer_literal_type(val))
52
- if inferred_element_type is None:
53
- break
49
+ inferred_element_type: Optional[ts.ColumnType] = ts.InvalidType()
50
+ if not force_json:
51
+ # try to infer the element type
52
+ for idx, val in self.elements:
53
+ if idx is not None:
54
+ inferred_element_type = inferred_element_type.supertype(self.components[idx].col_type)
55
+ else:
56
+ inferred_element_type = inferred_element_type.supertype(ts.ColumnType.infer_literal_type(val))
57
+ if inferred_element_type is None:
58
+ break
54
59
 
55
60
  if force_json or inferred_element_type is None:
56
61
  # JSON conversion is forced, or there is no common supertype
@@ -93,7 +98,7 @@ class InlineArray(Expr):
93
98
  data_row[self.slot_idx] = result
94
99
 
95
100
  def _as_dict(self) -> Dict:
96
- return {'elements': self.elements, **super()._as_dict()}
101
+ return {'elements': self.elements, 'is_json': self.col_type.is_json_type(), **super()._as_dict()}
97
102
 
98
103
  @classmethod
99
104
  def _from_dict(cls, d: Dict, components: List[Expr]) -> Expr:
@@ -106,4 +111,6 @@ class InlineArray(Expr):
106
111
  arg.append(components[idx])
107
112
  else:
108
113
  arg.append(val)
109
- return cls(tuple(arg))
114
+ # in order to avoid a schema version change, we'll interpret the absence of 'is_json' to indicate an ArrayType
115
+ is_json = d.get('is_json', False)
116
+ return cls(tuple(arg), force_json=is_json)
@@ -0,0 +1,63 @@
1
+ from typing import Any, Optional
2
+
3
+ import sqlalchemy as sql
4
+
5
+ import pixeltable.exceptions as excs
6
+ import pixeltable.type_system as ts
7
+ from pixeltable.exprs import Expr, FunctionCall
8
+ from pixeltable.func import FunctionRegistry, CallableFunction
9
+ from .data_row import DataRow
10
+ from .row_builder import RowBuilder
11
+
12
+
13
+ class MethodRef(Expr):
14
+ """
15
+ A method reference. This represents a `Function` instance with its
16
+ first parameter bound to a base expression.
17
+
18
+ When a `MethodRef` is called, it returns a `FunctionCall` with the base expression as the first argument.
19
+ The effective arity of a `MethodRef` is one less than the arity of the underlying `Function`.
20
+ """
21
+ # TODO: Should this even be an `Expr`? It can't actually be evaluated directly (it has to be first
22
+ # converted to a `FunctionCall` by binding any remaining parameters).
23
+
24
+ def __init__(self, base_expr: Expr, method_name: str):
25
+ super().__init__(ts.InvalidType()) # The `MethodRef` is untyped until it is called.
26
+ self.base_expr = base_expr
27
+ self.method_name = method_name
28
+ self.fn = FunctionRegistry.get().lookup_type_method(base_expr.col_type.type_enum, method_name)
29
+ if self.fn is None:
30
+ # This has to be an `AttributeError`, or tab-completion won't work properly in ipython.
31
+ raise AttributeError(f'Unknown method (of type {base_expr.col_type}): {method_name}')
32
+ self.components = [base_expr]
33
+ self.id = self._create_id()
34
+
35
+ def _as_dict(self) -> dict:
36
+ return {'method_name': self.method_name, **super()._as_dict()}
37
+
38
+ @classmethod
39
+ def _from_dict(cls, d: dict, components: list[Expr]) -> Expr:
40
+ assert 'method_name' in d
41
+ assert len(components) == 1
42
+ return cls(d['method_name'], components[0])
43
+
44
+ def __call__(self, *args, **kwargs) -> FunctionCall:
45
+ result = self.fn(*[self.base_expr, *args], **kwargs)
46
+ assert isinstance(result, FunctionCall)
47
+ result.is_method_call = True
48
+ return result
49
+
50
+ def _equals(self, other: 'MethodRef') -> bool:
51
+ return self.base_expr == other.base_expr and self.method_name == other.method_name
52
+
53
+ def _id_attrs(self) -> list[tuple[str, Any]]:
54
+ return super()._id_attrs() + [('method_name', self.method_name)]
55
+
56
+ def sql_expr(self) -> Optional[sql.ClauseElement]:
57
+ return None
58
+
59
+ def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
60
+ assert False, 'MethodRef cannot be evaluated directly'
61
+
62
+ def __str__(self) -> str:
63
+ return f'{self.base_expr}.{self.method_name}'
@@ -3,3 +3,12 @@ Extended integrations for Pixeltable. This package contains experimental or demo
3
3
  are not intended for production use. Long-term support cannot be guaranteed, usually because the features
4
4
  have dependencies whose future support is unclear.
5
5
  """
6
+
7
+ from pixeltable.utils.code import local_public_names
8
+ from . import functions
9
+
10
+ __all__ = local_public_names(__name__)
11
+
12
+
13
+ def __dir__():
14
+ return __all__
@@ -0,0 +1,8 @@
1
+ from pixeltable.utils.code import local_public_names
2
+ from . import whisperx, yolox
3
+
4
+ __all__ = local_public_names(__name__)
5
+
6
+
7
+ def __dir__():
8
+ return __all__