pixeltable 0.4.18__py3-none-any.whl → 0.4.19__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 (152) hide show
  1. pixeltable/__init__.py +1 -1
  2. pixeltable/_version.py +1 -0
  3. pixeltable/catalog/catalog.py +119 -100
  4. pixeltable/catalog/column.py +104 -115
  5. pixeltable/catalog/globals.py +1 -2
  6. pixeltable/catalog/insertable_table.py +44 -49
  7. pixeltable/catalog/path.py +3 -4
  8. pixeltable/catalog/schema_object.py +4 -4
  9. pixeltable/catalog/table.py +118 -122
  10. pixeltable/catalog/table_metadata.py +6 -6
  11. pixeltable/catalog/table_version.py +322 -257
  12. pixeltable/catalog/table_version_handle.py +4 -4
  13. pixeltable/catalog/table_version_path.py +9 -10
  14. pixeltable/catalog/tbl_ops.py +9 -3
  15. pixeltable/catalog/view.py +34 -28
  16. pixeltable/config.py +14 -10
  17. pixeltable/dataframe.py +68 -77
  18. pixeltable/env.py +74 -64
  19. pixeltable/exec/aggregation_node.py +6 -6
  20. pixeltable/exec/cache_prefetch_node.py +10 -10
  21. pixeltable/exec/data_row_batch.py +3 -3
  22. pixeltable/exec/exec_context.py +4 -5
  23. pixeltable/exec/exec_node.py +5 -5
  24. pixeltable/exec/expr_eval/evaluators.py +6 -6
  25. pixeltable/exec/expr_eval/expr_eval_node.py +8 -7
  26. pixeltable/exec/expr_eval/globals.py +6 -6
  27. pixeltable/exec/expr_eval/row_buffer.py +1 -2
  28. pixeltable/exec/expr_eval/schedulers.py +11 -11
  29. pixeltable/exec/in_memory_data_node.py +2 -2
  30. pixeltable/exec/object_store_save_node.py +14 -17
  31. pixeltable/exec/sql_node.py +25 -25
  32. pixeltable/exprs/arithmetic_expr.py +4 -4
  33. pixeltable/exprs/array_slice.py +2 -2
  34. pixeltable/exprs/column_property_ref.py +3 -3
  35. pixeltable/exprs/column_ref.py +61 -74
  36. pixeltable/exprs/comparison.py +5 -5
  37. pixeltable/exprs/compound_predicate.py +3 -3
  38. pixeltable/exprs/data_row.py +12 -12
  39. pixeltable/exprs/expr.py +41 -31
  40. pixeltable/exprs/expr_dict.py +3 -3
  41. pixeltable/exprs/expr_set.py +3 -3
  42. pixeltable/exprs/function_call.py +14 -14
  43. pixeltable/exprs/in_predicate.py +4 -4
  44. pixeltable/exprs/inline_expr.py +8 -8
  45. pixeltable/exprs/is_null.py +1 -3
  46. pixeltable/exprs/json_mapper.py +8 -8
  47. pixeltable/exprs/json_path.py +6 -6
  48. pixeltable/exprs/literal.py +5 -5
  49. pixeltable/exprs/method_ref.py +2 -2
  50. pixeltable/exprs/object_ref.py +2 -2
  51. pixeltable/exprs/row_builder.py +14 -14
  52. pixeltable/exprs/rowid_ref.py +8 -8
  53. pixeltable/exprs/similarity_expr.py +50 -25
  54. pixeltable/exprs/sql_element_cache.py +4 -4
  55. pixeltable/exprs/string_op.py +2 -2
  56. pixeltable/exprs/type_cast.py +3 -5
  57. pixeltable/func/aggregate_function.py +8 -8
  58. pixeltable/func/callable_function.py +9 -9
  59. pixeltable/func/expr_template_function.py +3 -3
  60. pixeltable/func/function.py +15 -17
  61. pixeltable/func/function_registry.py +6 -7
  62. pixeltable/func/globals.py +2 -3
  63. pixeltable/func/mcp.py +2 -2
  64. pixeltable/func/query_template_function.py +16 -16
  65. pixeltable/func/signature.py +14 -14
  66. pixeltable/func/tools.py +11 -11
  67. pixeltable/func/udf.py +16 -18
  68. pixeltable/functions/__init__.py +1 -0
  69. pixeltable/functions/anthropic.py +7 -7
  70. pixeltable/functions/audio.py +76 -0
  71. pixeltable/functions/bedrock.py +6 -6
  72. pixeltable/functions/deepseek.py +4 -4
  73. pixeltable/functions/fireworks.py +2 -2
  74. pixeltable/functions/gemini.py +6 -6
  75. pixeltable/functions/globals.py +12 -12
  76. pixeltable/functions/groq.py +4 -4
  77. pixeltable/functions/huggingface.py +18 -20
  78. pixeltable/functions/image.py +7 -10
  79. pixeltable/functions/llama_cpp.py +7 -7
  80. pixeltable/functions/math.py +2 -3
  81. pixeltable/functions/mistralai.py +3 -3
  82. pixeltable/functions/ollama.py +9 -9
  83. pixeltable/functions/openai.py +21 -21
  84. pixeltable/functions/openrouter.py +7 -7
  85. pixeltable/functions/string.py +21 -28
  86. pixeltable/functions/timestamp.py +7 -8
  87. pixeltable/functions/together.py +4 -6
  88. pixeltable/functions/twelvelabs.py +92 -0
  89. pixeltable/functions/video.py +2 -24
  90. pixeltable/functions/vision.py +6 -6
  91. pixeltable/functions/whisper.py +7 -7
  92. pixeltable/functions/whisperx.py +16 -16
  93. pixeltable/globals.py +52 -36
  94. pixeltable/index/base.py +12 -8
  95. pixeltable/index/btree.py +19 -22
  96. pixeltable/index/embedding_index.py +30 -39
  97. pixeltable/io/datarows.py +3 -3
  98. pixeltable/io/external_store.py +13 -16
  99. pixeltable/io/fiftyone.py +5 -5
  100. pixeltable/io/globals.py +5 -5
  101. pixeltable/io/hf_datasets.py +4 -4
  102. pixeltable/io/label_studio.py +12 -12
  103. pixeltable/io/pandas.py +6 -6
  104. pixeltable/io/parquet.py +2 -2
  105. pixeltable/io/table_data_conduit.py +12 -12
  106. pixeltable/io/utils.py +2 -2
  107. pixeltable/iterators/audio.py +2 -2
  108. pixeltable/iterators/video.py +8 -13
  109. pixeltable/metadata/converters/convert_18.py +2 -2
  110. pixeltable/metadata/converters/convert_19.py +2 -2
  111. pixeltable/metadata/converters/convert_20.py +2 -2
  112. pixeltable/metadata/converters/convert_21.py +2 -2
  113. pixeltable/metadata/converters/convert_22.py +2 -2
  114. pixeltable/metadata/converters/convert_24.py +2 -2
  115. pixeltable/metadata/converters/convert_25.py +2 -2
  116. pixeltable/metadata/converters/convert_26.py +2 -2
  117. pixeltable/metadata/converters/convert_29.py +4 -4
  118. pixeltable/metadata/converters/convert_34.py +2 -2
  119. pixeltable/metadata/converters/convert_36.py +2 -2
  120. pixeltable/metadata/converters/convert_38.py +2 -2
  121. pixeltable/metadata/converters/convert_39.py +1 -2
  122. pixeltable/metadata/converters/util.py +11 -13
  123. pixeltable/metadata/schema.py +22 -21
  124. pixeltable/metadata/utils.py +2 -6
  125. pixeltable/mypy/mypy_plugin.py +5 -5
  126. pixeltable/plan.py +30 -28
  127. pixeltable/share/packager.py +7 -7
  128. pixeltable/share/publish.py +3 -3
  129. pixeltable/store.py +125 -61
  130. pixeltable/type_system.py +43 -46
  131. pixeltable/utils/__init__.py +1 -2
  132. pixeltable/utils/arrow.py +4 -4
  133. pixeltable/utils/av.py +8 -0
  134. pixeltable/utils/azure_store.py +305 -0
  135. pixeltable/utils/code.py +1 -2
  136. pixeltable/utils/dbms.py +15 -19
  137. pixeltable/utils/description_helper.py +2 -3
  138. pixeltable/utils/documents.py +5 -6
  139. pixeltable/utils/exception_handler.py +2 -2
  140. pixeltable/utils/filecache.py +5 -5
  141. pixeltable/utils/formatter.py +4 -6
  142. pixeltable/utils/gcs_store.py +9 -9
  143. pixeltable/utils/local_store.py +17 -17
  144. pixeltable/utils/object_stores.py +59 -43
  145. pixeltable/utils/s3_store.py +35 -30
  146. {pixeltable-0.4.18.dist-info → pixeltable-0.4.19.dist-info}/METADATA +1 -1
  147. pixeltable-0.4.19.dist-info/RECORD +213 -0
  148. pixeltable/__version__.py +0 -3
  149. pixeltable-0.4.18.dist-info/RECORD +0 -211
  150. {pixeltable-0.4.18.dist-info → pixeltable-0.4.19.dist-info}/WHEEL +0 -0
  151. {pixeltable-0.4.18.dist-info → pixeltable-0.4.19.dist-info}/entry_points.txt +0 -0
  152. {pixeltable-0.4.18.dist-info → pixeltable-0.4.19.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@ import dataclasses
4
4
  import inspect
5
5
  import logging
6
6
  import typing
7
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional
7
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar
8
8
 
9
9
  import pixeltable.exceptions as excs
10
10
  import pixeltable.type_system as ts
@@ -18,11 +18,11 @@ _logger = logging.getLogger('pixeltable')
18
18
  @dataclasses.dataclass
19
19
  class Parameter:
20
20
  name: str
21
- col_type: Optional[ts.ColumnType] # None for variable parameters
21
+ col_type: ts.ColumnType | None # None for variable parameters
22
22
  kind: inspect._ParameterKind
23
23
  # for some reason, this needs to precede is_batched in the dataclass definition,
24
24
  # otherwise Python complains that an argument with a default is followed by an argument without a default
25
- default: Optional['exprs.Literal'] = None # default value for the parameter
25
+ default: 'exprs.Literal' | None = None # default value for the parameter
26
26
  is_batched: bool = False # True if the parameter is a batched parameter (eg, Batch[dict])
27
27
 
28
28
  def __post_init__(self) -> None:
@@ -104,7 +104,7 @@ class Signature:
104
104
  return_type: ts.ColumnType,
105
105
  parameters: list[Parameter],
106
106
  is_batched: bool = False,
107
- system_parameters: Optional[list[str]] = None,
107
+ system_parameters: list[str] | None = None,
108
108
  ):
109
109
  assert isinstance(return_type, ts.ColumnType)
110
110
  self.return_type = return_type
@@ -172,7 +172,7 @@ class Signature:
172
172
 
173
173
  return True
174
174
 
175
- def validate_args(self, bound_args: dict[str, Optional['exprs.Expr']], context: str = '') -> None:
175
+ def validate_args(self, bound_args: dict[str, 'exprs.Expr' | None], context: str = '') -> None:
176
176
  if context:
177
177
  context = f' ({context})'
178
178
 
@@ -231,11 +231,11 @@ class Signature:
231
231
  return f'({", ".join(param_strs)}) -> {self.get_return_type()}'
232
232
 
233
233
  @classmethod
234
- def _infer_type(cls, annotation: Optional[type]) -> tuple[Optional[ts.ColumnType], Optional[bool]]:
234
+ def _infer_type(cls, annotation: type | None) -> tuple[ts.ColumnType | None, bool | None]:
235
235
  """Returns: (column type, is_batched) or (None, ...) if the type cannot be inferred"""
236
236
  if annotation is None:
237
237
  return (None, None)
238
- py_type: Optional[type] = None
238
+ py_type: type | None = None
239
239
  is_batched = False
240
240
  if typing.get_origin(annotation) == typing.Annotated:
241
241
  type_args = typing.get_args(annotation)
@@ -252,10 +252,10 @@ class Signature:
252
252
  @classmethod
253
253
  def create_parameters(
254
254
  cls,
255
- py_fn: Optional[Callable] = None,
256
- py_params: Optional[list[inspect.Parameter]] = None,
257
- param_types: Optional[list[ts.ColumnType]] = None,
258
- type_substitutions: Optional[dict] = None,
255
+ py_fn: Callable | None = None,
256
+ py_params: list[inspect.Parameter] | None = None,
257
+ param_types: list[ts.ColumnType] | None = None,
258
+ type_substitutions: dict | None = None,
259
259
  is_cls_method: bool = False,
260
260
  ) -> list[Parameter]:
261
261
  """Ignores parameters starting with '_'."""
@@ -310,9 +310,9 @@ class Signature:
310
310
  def create(
311
311
  cls,
312
312
  py_fn: Callable,
313
- param_types: Optional[list[ts.ColumnType]] = None,
314
- return_type: Optional[ts.ColumnType] = None,
315
- type_substitutions: Optional[dict] = None,
313
+ param_types: list[ts.ColumnType] | None = None,
314
+ return_type: ts.ColumnType | None = None,
315
+ type_substitutions: dict | None = None,
316
316
  is_cls_method: bool = False,
317
317
  ) -> Signature:
318
318
  """Create a signature for the given Callable.
pixeltable/func/tools.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
2
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
3
3
 
4
4
  import pydantic
5
5
 
@@ -29,8 +29,8 @@ class Tool(pydantic.BaseModel):
29
29
  model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
30
30
 
31
31
  fn: Function
32
- name: Optional[str] = None
33
- description: Optional[str] = None
32
+ name: str | None = None
33
+ description: str | None = None
34
34
 
35
35
  @property
36
36
  def parameters(self) -> dict[str, Parameter]:
@@ -78,7 +78,7 @@ class Tool(pydantic.BaseModel):
78
78
  class ToolChoice(pydantic.BaseModel):
79
79
  auto: bool
80
80
  required: bool
81
- tool: Optional[str]
81
+ tool: str | None
82
82
  parallel_tool_calls: bool
83
83
 
84
84
 
@@ -105,7 +105,7 @@ class Tools(pydantic.BaseModel):
105
105
  ) -> ToolChoice:
106
106
  if sum([auto, required, tool is not None]) != 1:
107
107
  raise excs.Error('Exactly one of `auto`, `required`, or `tool` must be specified.')
108
- tool_name: Optional[str] = None
108
+ tool_name: str | None = None
109
109
  if tool is not None:
110
110
  try:
111
111
  tool_obj = next(
@@ -121,27 +121,27 @@ class Tools(pydantic.BaseModel):
121
121
 
122
122
 
123
123
  @udf
124
- def _extract_str_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[str]:
124
+ def _extract_str_tool_arg(kwargs: dict[str, Any], param_name: str) -> str | None:
125
125
  return _extract_arg(str, kwargs, param_name)
126
126
 
127
127
 
128
128
  @udf
129
- def _extract_int_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[int]:
129
+ def _extract_int_tool_arg(kwargs: dict[str, Any], param_name: str) -> int | None:
130
130
  return _extract_arg(int, kwargs, param_name)
131
131
 
132
132
 
133
133
  @udf
134
- def _extract_float_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[float]:
134
+ def _extract_float_tool_arg(kwargs: dict[str, Any], param_name: str) -> float | None:
135
135
  return _extract_arg(float, kwargs, param_name)
136
136
 
137
137
 
138
138
  @udf
139
- def _extract_bool_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[bool]:
139
+ def _extract_bool_tool_arg(kwargs: dict[str, Any], param_name: str) -> bool | None:
140
140
  return _extract_arg(bool, kwargs, param_name)
141
141
 
142
142
 
143
143
  @udf
144
- def _extract_json_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[ts.Json]:
144
+ def _extract_json_tool_arg(kwargs: dict[str, Any], param_name: str) -> ts.Json | None:
145
145
  if param_name in kwargs:
146
146
  return json.loads(kwargs[param_name])
147
147
  return None
@@ -150,7 +150,7 @@ def _extract_json_tool_arg(kwargs: dict[str, Any], param_name: str) -> Optional[
150
150
  T = TypeVar('T')
151
151
 
152
152
 
153
- def _extract_arg(eval_fn: Callable[[Any], T], kwargs: dict[str, Any], param_name: str) -> Optional[T]:
153
+ def _extract_arg(eval_fn: Callable[[Any], T], kwargs: dict[str, Any], param_name: str) -> T | None:
154
154
  if param_name in kwargs:
155
155
  return eval_fn(kwargs[param_name])
156
156
  return None
pixeltable/func/udf.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
- from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence, overload
4
+ from typing import TYPE_CHECKING, Any, Callable, Sequence, overload
5
5
 
6
6
  import pixeltable.exceptions as excs
7
7
  import pixeltable.type_system as ts
@@ -26,12 +26,12 @@ def udf(decorated_fn: Callable) -> CallableFunction: ...
26
26
  @overload
27
27
  def udf(
28
28
  *,
29
- batch_size: Optional[int] = None,
30
- substitute_fn: Optional[Callable] = None,
29
+ batch_size: int | None = None,
30
+ substitute_fn: Callable | None = None,
31
31
  is_method: bool = False,
32
32
  is_property: bool = False,
33
- resource_pool: Optional[str] = None,
34
- type_substitutions: Optional[Sequence[dict]] = None,
33
+ resource_pool: str | None = None,
34
+ type_substitutions: Sequence[dict] | None = None,
35
35
  _force_stored: bool = False,
36
36
  ) -> Callable[[Callable], CallableFunction]: ...
37
37
 
@@ -39,7 +39,7 @@ def udf(
39
39
  # pxt.udf() called explicitly on a Table:
40
40
  @overload
41
41
  def udf(
42
- table: catalog.Table, /, *, return_value: Any = None, description: Optional[str] = None
42
+ table: catalog.Table, /, *, return_value: Any = None, description: str | None = None
43
43
  ) -> ExprTemplateFunction: ...
44
44
 
45
45
 
@@ -96,15 +96,15 @@ def udf(*args, **kwargs): # type: ignore[no-untyped-def]
96
96
 
97
97
  def make_function(
98
98
  decorated_fn: Callable,
99
- return_type: Optional[ts.ColumnType] = None,
100
- param_types: Optional[list[ts.ColumnType]] = None,
101
- batch_size: Optional[int] = None,
102
- substitute_fn: Optional[Callable] = None,
99
+ return_type: ts.ColumnType | None = None,
100
+ param_types: list[ts.ColumnType] | None = None,
101
+ batch_size: int | None = None,
102
+ substitute_fn: Callable | None = None,
103
103
  is_method: bool = False,
104
104
  is_property: bool = False,
105
- resource_pool: Optional[str] = None,
106
- type_substitutions: Optional[Sequence[dict]] = None,
107
- function_name: Optional[str] = None,
105
+ resource_pool: str | None = None,
106
+ type_substitutions: Sequence[dict] | None = None,
107
+ function_name: str | None = None,
108
108
  force_stored: bool = False,
109
109
  ) -> CallableFunction:
110
110
  """
@@ -201,11 +201,11 @@ def expr_udf(py_fn: Callable) -> ExprTemplateFunction: ...
201
201
 
202
202
 
203
203
  @overload
204
- def expr_udf(*, param_types: Optional[list[ts.ColumnType]] = None) -> Callable[[Callable], ExprTemplateFunction]: ...
204
+ def expr_udf(*, param_types: list[ts.ColumnType] | None = None) -> Callable[[Callable], ExprTemplateFunction]: ...
205
205
 
206
206
 
207
207
  def expr_udf(*args: Any, **kwargs: Any) -> Any:
208
- def make_expr_template(py_fn: Callable, param_types: Optional[list[ts.ColumnType]]) -> ExprTemplateFunction:
208
+ def make_expr_template(py_fn: Callable, param_types: list[ts.ColumnType] | None) -> ExprTemplateFunction:
209
209
  from pixeltable import exprs
210
210
 
211
211
  if py_fn.__module__ != '__main__' and py_fn.__name__.isidentifier():
@@ -237,9 +237,7 @@ def expr_udf(*args: Any, **kwargs: Any) -> Any:
237
237
  return lambda py_fn: make_expr_template(py_fn, kwargs['param_types'])
238
238
 
239
239
 
240
- def from_table(
241
- tbl: catalog.Table, return_value: Optional['exprs.Expr'], description: Optional[str]
242
- ) -> ExprTemplateFunction:
240
+ def from_table(tbl: catalog.Table, return_value: 'exprs.Expr' | None, description: str | None) -> ExprTemplateFunction:
243
241
  """
244
242
  Constructs an `ExprTemplateFunction` from a `Table`.
245
243
 
@@ -24,6 +24,7 @@ from . import (
24
24
  string,
25
25
  timestamp,
26
26
  together,
27
+ twelvelabs,
27
28
  video,
28
29
  vision,
29
30
  whisper,
@@ -8,7 +8,7 @@ the [Working with Anthropic](https://pixeltable.readme.io/docs/working-with-anth
8
8
  import datetime
9
9
  import json
10
10
  import logging
11
- from typing import TYPE_CHECKING, Any, Iterable, Optional, cast
11
+ from typing import TYPE_CHECKING, Any, Iterable, cast
12
12
 
13
13
  import httpx
14
14
 
@@ -130,7 +130,7 @@ class AnthropicRateLimitsInfo(env.RateLimitsInfo):
130
130
  if retry_after_str is not None:
131
131
  _logger.debug(f'retry-after: {retry_after_str}')
132
132
 
133
- def get_retry_delay(self, exc: Exception) -> Optional[float]:
133
+ def get_retry_delay(self, exc: Exception) -> float | None:
134
134
  import anthropic
135
135
 
136
136
  # deal with timeouts separately, they don't come with headers
@@ -152,10 +152,10 @@ async def messages(
152
152
  *,
153
153
  model: str,
154
154
  max_tokens: int,
155
- model_kwargs: Optional[dict[str, Any]] = None,
156
- tools: Optional[list[dict[str, Any]]] = None,
157
- tool_choice: Optional[dict[str, Any]] = None,
158
- _runtime_ctx: Optional[env.RuntimeCtx] = None,
155
+ model_kwargs: dict[str, Any] | None = None,
156
+ tools: list[dict[str, Any]] | None = None,
157
+ tool_choice: dict[str, Any] | None = None,
158
+ _runtime_ctx: env.RuntimeCtx | None = None,
159
159
  ) -> dict:
160
160
  """
161
161
  Create a Message.
@@ -254,7 +254,7 @@ def invoke_tools(tools: Tools, response: exprs.Expr) -> exprs.InlineDict:
254
254
 
255
255
 
256
256
  @pxt.udf
257
- def _anthropic_response_to_pxt_tool_calls(response: dict) -> Optional[dict]:
257
+ def _anthropic_response_to_pxt_tool_calls(response: dict) -> dict | None:
258
258
  anthropic_tool_calls = [r for r in response['content'] if r['type'] == 'tool_use']
259
259
  if len(anthropic_tool_calls) == 0:
260
260
  return None
@@ -2,9 +2,13 @@
2
2
  Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs) for `AudioType`.
3
3
  """
4
4
 
5
+ import av
6
+ import numpy as np
7
+
5
8
  import pixeltable as pxt
6
9
  import pixeltable.utils.av as av_utils
7
10
  from pixeltable.utils.code import local_public_names
11
+ from pixeltable.utils.local_store import TempStore
8
12
 
9
13
 
10
14
  @pxt.udf(is_method=True)
@@ -51,6 +55,78 @@ def get_metadata(audio: pxt.Audio) -> dict:
51
55
  return av_utils.get_metadata(audio)
52
56
 
53
57
 
58
+ @pxt.udf()
59
+ def encode_audio(
60
+ audio_data: pxt.Array[pxt.Float], *, input_sample_rate: int, format: str, output_sample_rate: int | None = None
61
+ ) -> pxt.Audio:
62
+ """
63
+ Encodes an audio clip represented as an array into a specified audio format.
64
+
65
+ Parameters:
66
+ audio_data: An array of sampled amplitudes. The accepted array shapes are `(N,)` or `(1, N)` for mono audio
67
+ or `(2, N)` for stereo.
68
+ input_sample_rate: The sample rate of the input audio data.
69
+ format: The desired output audio format. The supported formats are 'wav', 'mp3', 'flac', and 'mp4'.
70
+ output_sample_rate: The desired sample rate for the output audio. Defaults to the input sample rate if
71
+ unspecified.
72
+
73
+ Examples:
74
+ Add a computed column with encoded FLAC audio files to a table with audio data (as arrays of floats) and sample
75
+ rates:
76
+
77
+ ```
78
+ t.add_computed_column(
79
+ audio_file=encode_audio(
80
+ t.audio_data, input_sample_rate=t.sample_rate, format='flac'
81
+ )
82
+ )
83
+ ```
84
+ """
85
+ if format not in av_utils.AUDIO_FORMATS:
86
+ raise pxt.Error(f'Only the following formats are supported: {av_utils.AUDIO_FORMATS.keys()}')
87
+ if output_sample_rate is None:
88
+ output_sample_rate = input_sample_rate
89
+
90
+ codec, ext = av_utils.AUDIO_FORMATS[format]
91
+ output_path = str(TempStore.create_path(extension=f'.{ext}'))
92
+
93
+ match audio_data.shape:
94
+ case (_,):
95
+ # Mono audio as 1D array, reshape for pyav
96
+ layout = 'mono'
97
+ audio_data_transformed = audio_data[None, :]
98
+ case (1, _):
99
+ # Mono audio as 2D array, simply reshape and transpose the input for pyav
100
+ layout = 'mono'
101
+ audio_data_transformed = audio_data.reshape(-1, 1).transpose()
102
+ case (2, _):
103
+ # Stereo audio. Input layout: [[L0, L1, L2, ...],[R0, R1, R2, ...]],
104
+ # pyav expects: [L0, R0, L1, R1, L2, R2, ...]
105
+ layout = 'stereo'
106
+ audio_data_transformed = np.empty(audio_data.shape[1] * 2, dtype=audio_data.dtype)
107
+ audio_data_transformed[0::2] = audio_data[0]
108
+ audio_data_transformed[1::2] = audio_data[1]
109
+ audio_data_transformed = audio_data_transformed.reshape(1, -1)
110
+ case _:
111
+ raise pxt.Error(
112
+ f'Supported input array shapes are (N,), (1, N) for mono and (2, N) for stereo, got {audio_data.shape}'
113
+ )
114
+
115
+ with av.open(output_path, mode='w') as output_container:
116
+ stream = output_container.add_stream(codec, rate=output_sample_rate)
117
+ assert isinstance(stream, av.AudioStream)
118
+
119
+ frame = av.AudioFrame.from_ndarray(audio_data_transformed, format='flt', layout=layout)
120
+ frame.sample_rate = input_sample_rate
121
+
122
+ for packet in stream.encode(frame):
123
+ output_container.mux(packet)
124
+ for packet in stream.encode():
125
+ output_container.mux(packet)
126
+
127
+ return output_path
128
+
129
+
54
130
  __all__ = local_public_names(__name__)
55
131
 
56
132
 
@@ -6,7 +6,7 @@ including Anthropic Claude, Amazon Titan, and other providers.
6
6
  """
7
7
 
8
8
  import logging
9
- from typing import TYPE_CHECKING, Any, Optional
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  import pixeltable as pxt
12
12
  from pixeltable import env, exprs
@@ -36,10 +36,10 @@ def converse(
36
36
  messages: list[dict[str, Any]],
37
37
  *,
38
38
  model_id: str,
39
- system: Optional[list[dict[str, Any]]] = None,
40
- inference_config: Optional[dict] = None,
41
- additional_model_request_fields: Optional[dict] = None,
42
- tool_config: Optional[list[dict]] = None,
39
+ system: list[dict[str, Any]] | None = None,
40
+ inference_config: dict | None = None,
41
+ additional_model_request_fields: dict | None = None,
42
+ tool_config: list[dict] | None = None,
43
43
  ) -> dict:
44
44
  """
45
45
  Generate a conversation response.
@@ -111,7 +111,7 @@ def invoke_tools(tools: Tools, response: exprs.Expr) -> exprs.InlineDict:
111
111
 
112
112
 
113
113
  @pxt.udf
114
- def _bedrock_response_to_pxt_tool_calls(response: dict) -> Optional[dict]:
114
+ def _bedrock_response_to_pxt_tool_calls(response: dict) -> dict | None:
115
115
  if response.get('stopReason') != 'tool_use':
116
116
  return None
117
117
 
@@ -6,7 +6,7 @@ and other AI capabilities.
6
6
  """
7
7
 
8
8
  import json
9
- from typing import TYPE_CHECKING, Any, Optional
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  import httpx
12
12
 
@@ -38,9 +38,9 @@ async def chat_completions(
38
38
  messages: list,
39
39
  *,
40
40
  model: str,
41
- model_kwargs: Optional[dict[str, Any]] = None,
42
- tools: Optional[list[dict[str, Any]]] = None,
43
- tool_choice: Optional[dict[str, Any]] = None,
41
+ model_kwargs: dict[str, Any] | None = None,
42
+ tools: list[dict[str, Any]] | None = None,
43
+ tool_choice: dict[str, Any] | None = None,
44
44
  ) -> dict:
45
45
  """
46
46
  Creates a model response for the given chat conversation.
@@ -5,7 +5,7 @@ first `pip install fireworks-ai` and configure your Fireworks AI credentials, as
5
5
  the [Working with Fireworks](https://pixeltable.readme.io/docs/working-with-fireworks) tutorial.
6
6
  """
7
7
 
8
- from typing import TYPE_CHECKING, Any, Optional
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  import pixeltable as pxt
11
11
  from pixeltable import env
@@ -29,7 +29,7 @@ def _fireworks_client() -> 'fireworks.client.Fireworks':
29
29
 
30
30
  @pxt.udf(resource_pool='request-rate:fireworks')
31
31
  async def chat_completions(
32
- messages: list[dict[str, str]], *, model: str, model_kwargs: Optional[dict[str, Any]] = None
32
+ messages: list[dict[str, str]], *, model: str, model_kwargs: dict[str, Any] | None = None
33
33
  ) -> dict:
34
34
  """
35
35
  Creates a model response for the given chat conversation.
@@ -8,7 +8,7 @@ the [Working with Gemini](https://pixeltable.readme.io/docs/working-with-gemini)
8
8
  import asyncio
9
9
  import io
10
10
  from pathlib import Path
11
- from typing import TYPE_CHECKING, Optional
11
+ from typing import TYPE_CHECKING
12
12
 
13
13
  import PIL.Image
14
14
 
@@ -34,7 +34,7 @@ def _genai_client() -> 'genai.client.Client':
34
34
 
35
35
  @pxt.udf(resource_pool='request-rate:gemini')
36
36
  async def generate_content(
37
- contents: str, *, model: str, config: Optional[dict] = None, tools: Optional[list[dict]] = None
37
+ contents: str, *, model: str, config: dict | None = None, tools: list[dict] | None = None
38
38
  ) -> dict:
39
39
  """
40
40
  Generate content from the specified model. For additional details, see:
@@ -103,7 +103,7 @@ def invoke_tools(tools: pxt.func.Tools, response: exprs.Expr) -> exprs.InlineDic
103
103
 
104
104
 
105
105
  @pxt.udf
106
- def _gemini_response_to_pxt_tool_calls(response: dict) -> Optional[dict]:
106
+ def _gemini_response_to_pxt_tool_calls(response: dict) -> dict | None:
107
107
  pxt_tool_calls: dict[str, list[dict]] = {}
108
108
  for part in response['candidates'][0]['content']['parts']:
109
109
  tool_call = part.get('function_call')
@@ -123,7 +123,7 @@ def _(model: str) -> str:
123
123
 
124
124
 
125
125
  @pxt.udf(resource_pool='request-rate:imagen')
126
- async def generate_images(prompt: str, *, model: str, config: Optional[dict] = None) -> PIL.Image.Image:
126
+ async def generate_images(prompt: str, *, model: str, config: dict | None = None) -> PIL.Image.Image:
127
127
  """
128
128
  Generates images based on a text description and configuration. For additional details, see:
129
129
  <https://ai.google.dev/gemini-api/docs/image-generation>
@@ -167,7 +167,7 @@ def _(model: str) -> str:
167
167
 
168
168
  @pxt.udf(resource_pool='request-rate:veo')
169
169
  async def generate_videos(
170
- prompt: Optional[str] = None, image: Optional[PIL.Image.Image] = None, *, model: str, config: Optional[dict] = None
170
+ prompt: str | None = None, image: PIL.Image.Image | None = None, *, model: str, config: dict | None = None
171
171
  ) -> pxt.Video:
172
172
  """
173
173
  Generates videos based on a text description and configuration. For additional details, see:
@@ -205,7 +205,7 @@ async def generate_videos(
205
205
  if prompt is None and image is None:
206
206
  raise excs.Error('At least one of `prompt` or `image` must be provided.')
207
207
 
208
- image_: Optional[types.Image] = None
208
+ image_: types.Image | None = None
209
209
  if image is not None:
210
210
  with io.BytesIO() as buffer:
211
211
  image.save(buffer, format='jpeg')
@@ -1,6 +1,6 @@
1
1
  import builtins
2
2
  import typing
3
- from typing import Any, Callable, Optional
3
+ from typing import Any, Callable
4
4
 
5
5
  import sqlalchemy as sql
6
6
 
@@ -19,7 +19,7 @@ def cast(expr: exprs.Expr, target_type: ts.ColumnType | type | _GenericAlias) ->
19
19
  T = typing.TypeVar('T')
20
20
 
21
21
 
22
- @func.uda(allows_window=True, type_substitutions=({T: Optional[int]}, {T: Optional[float]})) # type: ignore[misc]
22
+ @func.uda(allows_window=True, type_substitutions=({T: int | None}, {T: float | None})) # type: ignore[misc]
23
23
  class sum(func.Aggregator, typing.Generic[T]):
24
24
  """Sums the selected integers or floats."""
25
25
 
@@ -39,7 +39,7 @@ class sum(func.Aggregator, typing.Generic[T]):
39
39
 
40
40
 
41
41
  @sum.to_sql
42
- def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
42
+ def _(val: sql.ColumnElement) -> sql.ColumnElement | None:
43
43
  # This can produce a Decimal. We are deliberately avoiding an explicit cast to a Bigint here, because that can
44
44
  # cause overflows in Postgres. We're instead doing the conversion to the target type in SqlNode.__iter__().
45
45
  return sql.sql.func.sum(val)
@@ -49,7 +49,7 @@ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
49
49
  allows_window=True,
50
50
  # Allow counting non-null values of any type
51
51
  # TODO: should we have an "Any" type that can be used here?
52
- type_substitutions=tuple({T: Optional[t]} for t in ts.ALL_PIXELTABLE_TYPES), # type: ignore[misc]
52
+ type_substitutions=tuple({T: t | None} for t in ts.ALL_PIXELTABLE_TYPES), # type: ignore[misc]
53
53
  )
54
54
  class count(func.Aggregator, typing.Generic[T]):
55
55
  def __init__(self) -> None:
@@ -64,13 +64,13 @@ class count(func.Aggregator, typing.Generic[T]):
64
64
 
65
65
 
66
66
  @count.to_sql
67
- def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
67
+ def _(val: sql.ColumnElement) -> sql.ColumnElement | None:
68
68
  return sql.sql.func.count(val)
69
69
 
70
70
 
71
71
  @func.uda(
72
72
  allows_window=True,
73
- type_substitutions=tuple({T: Optional[t]} for t in (str, int, float, bool, ts.Timestamp)), # type: ignore[misc]
73
+ type_substitutions=tuple({T: t | None} for t in (str, int, float, bool, ts.Timestamp)), # type: ignore[misc]
74
74
  )
75
75
  class min(func.Aggregator, typing.Generic[T]):
76
76
  def __init__(self) -> None:
@@ -89,7 +89,7 @@ class min(func.Aggregator, typing.Generic[T]):
89
89
 
90
90
 
91
91
  @min.to_sql
92
- def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
92
+ def _(val: sql.ColumnElement) -> sql.ColumnElement | None:
93
93
  if val.type.python_type is bool:
94
94
  # TODO: min/max aggregation of booleans is not supported in Postgres (but it is in Python).
95
95
  # Right now we simply force the computation to be done in Python; we might consider implementing an alternate
@@ -100,7 +100,7 @@ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
100
100
 
101
101
  @func.uda(
102
102
  allows_window=True,
103
- type_substitutions=tuple({T: Optional[t]} for t in (str, int, float, bool, ts.Timestamp)), # type: ignore[misc]
103
+ type_substitutions=tuple({T: t | None} for t in (str, int, float, bool, ts.Timestamp)), # type: ignore[misc]
104
104
  )
105
105
  class max(func.Aggregator, typing.Generic[T]):
106
106
  def __init__(self) -> None:
@@ -119,14 +119,14 @@ class max(func.Aggregator, typing.Generic[T]):
119
119
 
120
120
 
121
121
  @max.to_sql
122
- def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
122
+ def _(val: sql.ColumnElement) -> sql.ColumnElement | None:
123
123
  if val.type.python_type is bool:
124
124
  # TODO: see comment in @min.to_sql.
125
125
  return None
126
126
  return sql.sql.func.max(val)
127
127
 
128
128
 
129
- @func.uda(type_substitutions=({T: Optional[int]}, {T: Optional[float]})) # type: ignore[misc]
129
+ @func.uda(type_substitutions=({T: int | None}, {T: float | None})) # type: ignore[misc]
130
130
  class mean(func.Aggregator, typing.Generic[T]):
131
131
  def __init__(self) -> None:
132
132
  self.sum: T = None
@@ -141,14 +141,14 @@ class mean(func.Aggregator, typing.Generic[T]):
141
141
  self.sum += val # type: ignore[operator]
142
142
  self.count += 1
143
143
 
144
- def value(self) -> Optional[float]: # Always a float
144
+ def value(self) -> float | None: # Always a float
145
145
  if self.count == 0:
146
146
  return None
147
147
  return self.sum / self.count # type: ignore[operator]
148
148
 
149
149
 
150
150
  @mean.to_sql
151
- def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
151
+ def _(val: sql.ColumnElement) -> sql.ColumnElement | None:
152
152
  return sql.sql.func.avg(val)
153
153
 
154
154
 
@@ -5,7 +5,7 @@ first `pip install groq` and configure your Groq credentials, as described in
5
5
  the [Working with Groq](https://pixeltable.readme.io/docs/working-with-groq) tutorial.
6
6
  """
7
7
 
8
- from typing import TYPE_CHECKING, Any, Optional
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  import pixeltable as pxt
11
11
  from pixeltable import exprs
@@ -34,9 +34,9 @@ async def chat_completions(
34
34
  messages: list[dict[str, str]],
35
35
  *,
36
36
  model: str,
37
- model_kwargs: Optional[dict[str, Any]] = None,
38
- tools: Optional[list[dict[str, Any]]] = None,
39
- tool_choice: Optional[dict[str, Any]] = None,
37
+ model_kwargs: dict[str, Any] | None = None,
38
+ tools: list[dict[str, Any]] | None = None,
39
+ tool_choice: dict[str, Any] | None = None,
40
40
  ) -> dict:
41
41
  """
42
42
  Chat Completion API.