pixeltable 0.4.0rc3__py3-none-any.whl → 0.4.20__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 (202) hide show
  1. pixeltable/__init__.py +23 -5
  2. pixeltable/_version.py +1 -0
  3. pixeltable/catalog/__init__.py +5 -3
  4. pixeltable/catalog/catalog.py +1318 -404
  5. pixeltable/catalog/column.py +186 -115
  6. pixeltable/catalog/dir.py +1 -2
  7. pixeltable/catalog/globals.py +11 -43
  8. pixeltable/catalog/insertable_table.py +167 -79
  9. pixeltable/catalog/path.py +61 -23
  10. pixeltable/catalog/schema_object.py +9 -10
  11. pixeltable/catalog/table.py +626 -308
  12. pixeltable/catalog/table_metadata.py +101 -0
  13. pixeltable/catalog/table_version.py +713 -569
  14. pixeltable/catalog/table_version_handle.py +37 -6
  15. pixeltable/catalog/table_version_path.py +42 -29
  16. pixeltable/catalog/tbl_ops.py +50 -0
  17. pixeltable/catalog/update_status.py +191 -0
  18. pixeltable/catalog/view.py +108 -94
  19. pixeltable/config.py +128 -22
  20. pixeltable/dataframe.py +188 -100
  21. pixeltable/env.py +407 -136
  22. pixeltable/exceptions.py +6 -0
  23. pixeltable/exec/__init__.py +3 -0
  24. pixeltable/exec/aggregation_node.py +7 -8
  25. pixeltable/exec/cache_prefetch_node.py +83 -110
  26. pixeltable/exec/cell_materialization_node.py +231 -0
  27. pixeltable/exec/cell_reconstruction_node.py +135 -0
  28. pixeltable/exec/component_iteration_node.py +4 -3
  29. pixeltable/exec/data_row_batch.py +8 -65
  30. pixeltable/exec/exec_context.py +16 -4
  31. pixeltable/exec/exec_node.py +13 -36
  32. pixeltable/exec/expr_eval/evaluators.py +7 -6
  33. pixeltable/exec/expr_eval/expr_eval_node.py +27 -12
  34. pixeltable/exec/expr_eval/globals.py +8 -5
  35. pixeltable/exec/expr_eval/row_buffer.py +1 -2
  36. pixeltable/exec/expr_eval/schedulers.py +190 -30
  37. pixeltable/exec/globals.py +32 -0
  38. pixeltable/exec/in_memory_data_node.py +18 -18
  39. pixeltable/exec/object_store_save_node.py +293 -0
  40. pixeltable/exec/row_update_node.py +16 -9
  41. pixeltable/exec/sql_node.py +206 -101
  42. pixeltable/exprs/__init__.py +1 -1
  43. pixeltable/exprs/arithmetic_expr.py +27 -22
  44. pixeltable/exprs/array_slice.py +3 -3
  45. pixeltable/exprs/column_property_ref.py +34 -30
  46. pixeltable/exprs/column_ref.py +92 -96
  47. pixeltable/exprs/comparison.py +5 -5
  48. pixeltable/exprs/compound_predicate.py +5 -4
  49. pixeltable/exprs/data_row.py +152 -55
  50. pixeltable/exprs/expr.py +62 -43
  51. pixeltable/exprs/expr_dict.py +3 -3
  52. pixeltable/exprs/expr_set.py +17 -10
  53. pixeltable/exprs/function_call.py +75 -37
  54. pixeltable/exprs/globals.py +1 -2
  55. pixeltable/exprs/in_predicate.py +4 -4
  56. pixeltable/exprs/inline_expr.py +10 -27
  57. pixeltable/exprs/is_null.py +1 -3
  58. pixeltable/exprs/json_mapper.py +8 -8
  59. pixeltable/exprs/json_path.py +56 -22
  60. pixeltable/exprs/literal.py +5 -5
  61. pixeltable/exprs/method_ref.py +2 -2
  62. pixeltable/exprs/object_ref.py +2 -2
  63. pixeltable/exprs/row_builder.py +127 -53
  64. pixeltable/exprs/rowid_ref.py +8 -12
  65. pixeltable/exprs/similarity_expr.py +50 -25
  66. pixeltable/exprs/sql_element_cache.py +4 -4
  67. pixeltable/exprs/string_op.py +5 -5
  68. pixeltable/exprs/type_cast.py +3 -5
  69. pixeltable/func/__init__.py +1 -0
  70. pixeltable/func/aggregate_function.py +8 -8
  71. pixeltable/func/callable_function.py +9 -9
  72. pixeltable/func/expr_template_function.py +10 -10
  73. pixeltable/func/function.py +18 -20
  74. pixeltable/func/function_registry.py +6 -7
  75. pixeltable/func/globals.py +2 -3
  76. pixeltable/func/mcp.py +74 -0
  77. pixeltable/func/query_template_function.py +20 -18
  78. pixeltable/func/signature.py +43 -16
  79. pixeltable/func/tools.py +23 -13
  80. pixeltable/func/udf.py +18 -20
  81. pixeltable/functions/__init__.py +6 -0
  82. pixeltable/functions/anthropic.py +93 -33
  83. pixeltable/functions/audio.py +114 -10
  84. pixeltable/functions/bedrock.py +13 -6
  85. pixeltable/functions/date.py +1 -1
  86. pixeltable/functions/deepseek.py +20 -9
  87. pixeltable/functions/fireworks.py +2 -2
  88. pixeltable/functions/gemini.py +28 -11
  89. pixeltable/functions/globals.py +13 -13
  90. pixeltable/functions/groq.py +108 -0
  91. pixeltable/functions/huggingface.py +1046 -23
  92. pixeltable/functions/image.py +9 -18
  93. pixeltable/functions/llama_cpp.py +23 -8
  94. pixeltable/functions/math.py +3 -4
  95. pixeltable/functions/mistralai.py +4 -15
  96. pixeltable/functions/ollama.py +16 -9
  97. pixeltable/functions/openai.py +104 -82
  98. pixeltable/functions/openrouter.py +143 -0
  99. pixeltable/functions/replicate.py +2 -2
  100. pixeltable/functions/reve.py +250 -0
  101. pixeltable/functions/string.py +21 -28
  102. pixeltable/functions/timestamp.py +13 -14
  103. pixeltable/functions/together.py +4 -6
  104. pixeltable/functions/twelvelabs.py +92 -0
  105. pixeltable/functions/util.py +6 -1
  106. pixeltable/functions/video.py +1388 -106
  107. pixeltable/functions/vision.py +7 -7
  108. pixeltable/functions/whisper.py +15 -7
  109. pixeltable/functions/whisperx.py +179 -0
  110. pixeltable/{ext/functions → functions}/yolox.py +2 -4
  111. pixeltable/globals.py +332 -105
  112. pixeltable/index/base.py +13 -22
  113. pixeltable/index/btree.py +23 -22
  114. pixeltable/index/embedding_index.py +32 -44
  115. pixeltable/io/__init__.py +4 -2
  116. pixeltable/io/datarows.py +7 -6
  117. pixeltable/io/external_store.py +49 -77
  118. pixeltable/io/fiftyone.py +11 -11
  119. pixeltable/io/globals.py +29 -28
  120. pixeltable/io/hf_datasets.py +17 -9
  121. pixeltable/io/label_studio.py +70 -66
  122. pixeltable/io/lancedb.py +3 -0
  123. pixeltable/io/pandas.py +12 -11
  124. pixeltable/io/parquet.py +13 -93
  125. pixeltable/io/table_data_conduit.py +71 -47
  126. pixeltable/io/utils.py +3 -3
  127. pixeltable/iterators/__init__.py +2 -1
  128. pixeltable/iterators/audio.py +21 -11
  129. pixeltable/iterators/document.py +116 -55
  130. pixeltable/iterators/image.py +5 -2
  131. pixeltable/iterators/video.py +293 -13
  132. pixeltable/metadata/__init__.py +4 -2
  133. pixeltable/metadata/converters/convert_18.py +2 -2
  134. pixeltable/metadata/converters/convert_19.py +2 -2
  135. pixeltable/metadata/converters/convert_20.py +2 -2
  136. pixeltable/metadata/converters/convert_21.py +2 -2
  137. pixeltable/metadata/converters/convert_22.py +2 -2
  138. pixeltable/metadata/converters/convert_24.py +2 -2
  139. pixeltable/metadata/converters/convert_25.py +2 -2
  140. pixeltable/metadata/converters/convert_26.py +2 -2
  141. pixeltable/metadata/converters/convert_29.py +4 -4
  142. pixeltable/metadata/converters/convert_34.py +2 -2
  143. pixeltable/metadata/converters/convert_36.py +2 -2
  144. pixeltable/metadata/converters/convert_37.py +15 -0
  145. pixeltable/metadata/converters/convert_38.py +39 -0
  146. pixeltable/metadata/converters/convert_39.py +124 -0
  147. pixeltable/metadata/converters/convert_40.py +73 -0
  148. pixeltable/metadata/converters/util.py +13 -12
  149. pixeltable/metadata/notes.py +4 -0
  150. pixeltable/metadata/schema.py +79 -42
  151. pixeltable/metadata/utils.py +74 -0
  152. pixeltable/mypy/__init__.py +3 -0
  153. pixeltable/mypy/mypy_plugin.py +123 -0
  154. pixeltable/plan.py +274 -223
  155. pixeltable/share/__init__.py +1 -1
  156. pixeltable/share/packager.py +259 -129
  157. pixeltable/share/protocol/__init__.py +34 -0
  158. pixeltable/share/protocol/common.py +170 -0
  159. pixeltable/share/protocol/operation_types.py +33 -0
  160. pixeltable/share/protocol/replica.py +109 -0
  161. pixeltable/share/publish.py +213 -57
  162. pixeltable/store.py +238 -175
  163. pixeltable/type_system.py +104 -63
  164. pixeltable/utils/__init__.py +2 -3
  165. pixeltable/utils/arrow.py +108 -13
  166. pixeltable/utils/av.py +298 -0
  167. pixeltable/utils/azure_store.py +305 -0
  168. pixeltable/utils/code.py +3 -3
  169. pixeltable/utils/console_output.py +4 -1
  170. pixeltable/utils/coroutine.py +6 -23
  171. pixeltable/utils/dbms.py +31 -5
  172. pixeltable/utils/description_helper.py +4 -5
  173. pixeltable/utils/documents.py +5 -6
  174. pixeltable/utils/exception_handler.py +7 -30
  175. pixeltable/utils/filecache.py +6 -6
  176. pixeltable/utils/formatter.py +4 -6
  177. pixeltable/utils/gcs_store.py +283 -0
  178. pixeltable/utils/http_server.py +2 -3
  179. pixeltable/utils/iceberg.py +1 -2
  180. pixeltable/utils/image.py +17 -0
  181. pixeltable/utils/lancedb.py +88 -0
  182. pixeltable/utils/local_store.py +316 -0
  183. pixeltable/utils/misc.py +5 -0
  184. pixeltable/utils/object_stores.py +528 -0
  185. pixeltable/utils/pydantic.py +60 -0
  186. pixeltable/utils/pytorch.py +5 -6
  187. pixeltable/utils/s3_store.py +392 -0
  188. pixeltable-0.4.20.dist-info/METADATA +587 -0
  189. pixeltable-0.4.20.dist-info/RECORD +218 -0
  190. {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.dist-info}/WHEEL +1 -1
  191. pixeltable-0.4.20.dist-info/entry_points.txt +2 -0
  192. pixeltable/__version__.py +0 -3
  193. pixeltable/ext/__init__.py +0 -17
  194. pixeltable/ext/functions/__init__.py +0 -11
  195. pixeltable/ext/functions/whisperx.py +0 -77
  196. pixeltable/utils/media_store.py +0 -77
  197. pixeltable/utils/s3.py +0 -17
  198. pixeltable/utils/sample.py +0 -25
  199. pixeltable-0.4.0rc3.dist-info/METADATA +0 -435
  200. pixeltable-0.4.0rc3.dist-info/RECORD +0 -189
  201. pixeltable-0.4.0rc3.dist-info/entry_points.txt +0 -3
  202. {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.dist-info/licenses}/LICENSE +0 -0
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING, Optional
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
5
6
  from uuid import UUID
6
7
 
8
+ from pixeltable import exceptions as excs
9
+
7
10
  from .table_version import TableVersion
8
11
 
9
12
  if TYPE_CHECKING:
10
- pass
13
+ from pixeltable.catalog import Column
11
14
 
12
15
  _logger = logging.getLogger('pixeltable')
13
16
 
@@ -18,10 +21,10 @@ class TableVersionHandle:
18
21
  """
19
22
 
20
23
  id: UUID
21
- effective_version: Optional[int]
22
- _tbl_version: Optional[TableVersion]
24
+ effective_version: int | None
25
+ _tbl_version: TableVersion | None
23
26
 
24
- def __init__(self, tbl_id: UUID, effective_version: Optional[int], tbl_version: Optional[TableVersion] = None):
27
+ def __init__(self, tbl_id: UUID, effective_version: int | None, tbl_version: TableVersion | None = None):
25
28
  self.id = tbl_id
26
29
  self.effective_version = effective_version
27
30
  self._tbl_version = tbl_version
@@ -34,6 +37,13 @@ class TableVersionHandle:
34
37
  def __hash__(self) -> int:
35
38
  return hash((self.id, self.effective_version))
36
39
 
40
+ def __repr__(self) -> str:
41
+ return f'TableVersionHandle(id={self.id!r}, effective_version={self.effective_version})'
42
+
43
+ @property
44
+ def is_snapshot(self) -> bool:
45
+ return self.effective_version is not None
46
+
37
47
  @classmethod
38
48
  def create(cls, tbl_version: TableVersion) -> TableVersionHandle:
39
49
  return cls(tbl_version.id, tbl_version.effective_version, tbl_version)
@@ -53,7 +63,6 @@ class TableVersionHandle:
53
63
  else:
54
64
  self._tbl_version = Catalog.get().get_tbl_version(self.id, self.effective_version)
55
65
  if self.effective_version is None:
56
- # make sure we don't see a discarded instance of a live TableVersion
57
66
  tvs = list(Catalog.get()._tbl_versions.values())
58
67
  assert self._tbl_version in tvs
59
68
  return self._tbl_version
@@ -64,3 +73,25 @@ class TableVersionHandle:
64
73
  @classmethod
65
74
  def from_dict(cls, d: dict) -> TableVersionHandle:
66
75
  return cls(UUID(d['id']), d['effective_version'])
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class ColumnHandle:
80
+ tbl_version: TableVersionHandle
81
+ col_id: int
82
+
83
+ def get(self) -> 'Column':
84
+ if self.col_id not in self.tbl_version.get().cols_by_id:
85
+ schema_version_drop = self.tbl_version.get()._tbl_md.column_md[self.col_id].schema_version_drop
86
+ raise excs.Error(
87
+ f'Column was dropped (no record for column ID {self.col_id} in table '
88
+ f'{self.tbl_version.get().versioned_name!r}; it was dropped in table version {schema_version_drop})'
89
+ )
90
+ return self.tbl_version.get().cols_by_id[self.col_id]
91
+
92
+ def as_dict(self) -> dict:
93
+ return {'tbl_version': self.tbl_version.as_dict(), 'col_id': self.col_id}
94
+
95
+ @classmethod
96
+ def from_dict(cls, d: dict) -> ColumnHandle:
97
+ return cls(tbl_version=TableVersionHandle.from_dict(d['tbl_version']), col_id=d['col_id'])
@@ -1,13 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import Optional
5
4
  from uuid import UUID
6
5
 
7
6
  from pixeltable.env import Env
8
7
  from pixeltable.metadata import schema
9
8
 
10
9
  from .column import Column
10
+ from .globals import MediaValidation
11
11
  from .table_version import TableVersion
12
12
  from .table_version_handle import TableVersionHandle
13
13
 
@@ -38,10 +38,10 @@ class TableVersionPath:
38
38
  """
39
39
 
40
40
  tbl_version: TableVersionHandle
41
- base: Optional[TableVersionPath]
42
- _cached_tbl_version: Optional[TableVersion]
41
+ base: TableVersionPath | None
42
+ _cached_tbl_version: TableVersion | None
43
43
 
44
- def __init__(self, tbl_version: TableVersionHandle, base: Optional[TableVersionPath] = None):
44
+ def __init__(self, tbl_version: TableVersionHandle, base: TableVersionPath | None = None):
45
45
  assert tbl_version is not None
46
46
  self.tbl_version = tbl_version
47
47
  self.base = base
@@ -50,7 +50,7 @@ class TableVersionPath:
50
50
  @classmethod
51
51
  def from_md(cls, path: schema.TableVersionPath) -> TableVersionPath:
52
52
  assert len(path) > 0
53
- result: Optional[TableVersionPath] = None
53
+ result: TableVersionPath | None = None
54
54
  for tbl_id_str, effective_version in path[::-1]:
55
55
  tbl_id = UUID(tbl_id_str)
56
56
  result = TableVersionPath(TableVersionHandle(tbl_id, effective_version), base=result)
@@ -75,7 +75,7 @@ class TableVersionPath:
75
75
  elif self._cached_tbl_version is not None:
76
76
  return
77
77
 
78
- with Catalog.get().begin_xact(for_write=False):
78
+ with Catalog.get().begin_xact(tbl_id=self.tbl_version.id, for_write=False):
79
79
  self._cached_tbl_version = self.tbl_version.get()
80
80
 
81
81
  def clear_cached_md(self) -> None:
@@ -83,6 +83,7 @@ class TableVersionPath:
83
83
  if self.base is not None:
84
84
  self.base.clear_cached_md()
85
85
 
86
+ @property
86
87
  def tbl_id(self) -> UUID:
87
88
  """Return the id of the table/view that this path represents"""
88
89
  return self.tbl_version.id
@@ -92,6 +93,11 @@ class TableVersionPath:
92
93
  self.refresh_cached_md()
93
94
  return self._cached_tbl_version.version
94
95
 
96
+ def schema_version(self) -> int:
97
+ """Return the version of the table/view that this path represents"""
98
+ self.refresh_cached_md()
99
+ return self._cached_tbl_version.schema_version
100
+
95
101
  def tbl_name(self) -> str:
96
102
  """Return the name of the table/view that this path represents"""
97
103
  self.refresh_cached_md()
@@ -103,10 +109,7 @@ class TableVersionPath:
103
109
 
104
110
  def is_snapshot(self) -> bool:
105
111
  """Return True if this is a path of snapshot versions"""
106
- self.refresh_cached_md()
107
- if not self._cached_tbl_version.is_snapshot:
108
- return False
109
- return self.base.is_snapshot() if self.base is not None else True
112
+ return self.tbl_version.is_snapshot
110
113
 
111
114
  def is_view(self) -> bool:
112
115
  self.refresh_cached_md()
@@ -116,10 +119,30 @@ class TableVersionPath:
116
119
  self.refresh_cached_md()
117
120
  return self._cached_tbl_version.is_component_view
118
121
 
122
+ def is_replica(self) -> bool:
123
+ self.refresh_cached_md()
124
+ return self._cached_tbl_version.is_replica
125
+
126
+ def is_mutable(self) -> bool:
127
+ self.refresh_cached_md()
128
+ return self._cached_tbl_version.is_mutable
129
+
119
130
  def is_insertable(self) -> bool:
120
131
  self.refresh_cached_md()
121
132
  return self._cached_tbl_version.is_insertable
122
133
 
134
+ def comment(self) -> str:
135
+ self.refresh_cached_md()
136
+ return self._cached_tbl_version.comment
137
+
138
+ def num_retained_versions(self) -> int:
139
+ self.refresh_cached_md()
140
+ return self._cached_tbl_version.num_retained_versions
141
+
142
+ def media_validation(self) -> MediaValidation:
143
+ self.refresh_cached_md()
144
+ return self._cached_tbl_version.media_validation
145
+
123
146
  def get_tbl_versions(self) -> list[TableVersionHandle]:
124
147
  """Return all tbl versions"""
125
148
  if self.base is None:
@@ -132,7 +155,7 @@ class TableVersionPath:
132
155
  return []
133
156
  return self.base.get_tbl_versions()
134
157
 
135
- def find_tbl_version(self, id: UUID) -> Optional[TableVersionHandle]:
158
+ def find_tbl_version(self, id: UUID) -> TableVersionHandle | None:
136
159
  """Return the matching TableVersion in the chain of TableVersions, starting with this one"""
137
160
  if self.tbl_version.id == id:
138
161
  return self.tbl_version
@@ -160,40 +183,30 @@ class TableVersionPath:
160
183
  cols = self.columns()
161
184
  return {col.id: col for col in cols}
162
185
 
163
- def get_column(self, name: str, include_bases: Optional[bool] = None) -> Optional[Column]:
186
+ def get_column(self, name: str) -> Column | None:
164
187
  """Return the column with the given name, or None if not found"""
165
188
  self.refresh_cached_md()
166
189
  col = self._cached_tbl_version.cols_by_name.get(name)
167
190
  if col is not None:
168
191
  return col
169
- elif self.base is not None and (include_bases or self._cached_tbl_version.include_base_columns):
192
+ elif self.base is not None and self._cached_tbl_version.include_base_columns:
170
193
  return self.base.get_column(name)
171
194
  else:
172
195
  return None
173
196
 
174
- def get_column_by_id(self, tbl_id: UUID, col_id: int) -> Optional[Column]:
175
- """Return the column for the given tbl/col id"""
176
- self.refresh_cached_md()
177
- if self.tbl_version.id == tbl_id:
178
- assert col_id in self._cached_tbl_version.cols_by_id
179
- return self._cached_tbl_version.cols_by_id[col_id]
180
- elif self.base is not None:
181
- return self.base.get_column_by_id(tbl_id, col_id)
182
- else:
183
- return None
184
-
185
- def has_column(self, col: Column, include_bases: bool = True) -> bool:
197
+ def has_column(self, col: Column) -> bool:
186
198
  """Return True if this table has the given column."""
199
+ assert col.get_tbl() is not None
187
200
  self.refresh_cached_md()
188
- assert col.tbl is not None
201
+
189
202
  if (
190
- col.tbl.id == self.tbl_version.id
191
- and col.tbl.effective_version == self.tbl_version.effective_version
203
+ col.get_tbl().id == self.tbl_version.id
204
+ and col.get_tbl().effective_version == self.tbl_version.effective_version
192
205
  and col.id in self._cached_tbl_version.cols_by_id
193
206
  ):
194
207
  # the column is visible in this table version
195
208
  return True
196
- elif self.base is not None and include_bases:
209
+ elif self.base is not None:
197
210
  return self.base.has_column(col)
198
211
  else:
199
212
  return False
@@ -0,0 +1,50 @@
1
+ # This file contains all dataclasses related to schema.PendingTableOp:
2
+ # - TableOp: the container for each log entry
3
+ # - <>Op: the actual operation, which is performed by TableVersion.exec_op(); each <>Op class contains
4
+ # enough information for exec_op() to perform the operation without having to reference data outside of
5
+ # TableVersion
6
+
7
+ import dataclasses
8
+ from typing import Any
9
+
10
+
11
+ @dataclasses.dataclass
12
+ class CreateStoreTableOp:
13
+ pass
14
+
15
+
16
+ @dataclasses.dataclass
17
+ class CreateIndexOp:
18
+ idx_id: int
19
+
20
+
21
+ @dataclasses.dataclass
22
+ class LoadViewOp:
23
+ view_path: dict[str, Any] # needed to create the view load plan
24
+
25
+
26
+ @dataclasses.dataclass
27
+ class DeleteTableMdOp:
28
+ pass
29
+
30
+
31
+ @dataclasses.dataclass
32
+ class DeleteTableMediaFilesOp:
33
+ pass
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class DropStoreTableOp:
38
+ pass
39
+
40
+
41
+ @dataclasses.dataclass
42
+ class TableOp:
43
+ tbl_id: str # uuid.UUID
44
+ op_sn: int # sequence number within the update operation; [0, num_ops)
45
+ num_ops: int # total number of ops forming the update operation
46
+ needs_xact: bool # if True, op must be run as part of a transaction
47
+
48
+ create_store_table_op: CreateStoreTableOp | None = None
49
+ create_index_op: CreateIndexOp | None = None
50
+ load_view_op: LoadViewOp | None = None
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from IPython.lib.pretty import RepresentationPrinter
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class RowCountStats:
12
+ """
13
+ Statistics about the counts of rows affected by a table operation.
14
+ """
15
+
16
+ ins_rows: int = 0 # rows inserted
17
+ del_rows: int = 0 # rows deleted
18
+ upd_rows: int = 0 # rows updated
19
+ num_excs: int = 0 # total number of exceptions
20
+ # TODO: disambiguate what this means: # of slots computed or # of columns computed?
21
+ computed_values: int = 0 # number of computed values (e.g., computed columns) affected by the operation
22
+
23
+ @property
24
+ def num_rows(self) -> int:
25
+ return self.ins_rows + self.del_rows + self.upd_rows
26
+
27
+ def insert_to_update(self) -> 'RowCountStats':
28
+ """
29
+ Convert insert row count stats to update row count stats.
30
+ This is used when an insert operation is treated as an update.
31
+ """
32
+ return RowCountStats(
33
+ ins_rows=0,
34
+ del_rows=self.del_rows,
35
+ upd_rows=self.upd_rows + self.ins_rows,
36
+ num_excs=self.num_excs,
37
+ computed_values=self.computed_values,
38
+ )
39
+
40
+ def __add__(self, other: 'RowCountStats') -> 'RowCountStats':
41
+ """
42
+ Add the stats from two RowCountStats objects together.
43
+ """
44
+ return RowCountStats(
45
+ ins_rows=self.ins_rows + other.ins_rows,
46
+ del_rows=self.del_rows + other.del_rows,
47
+ upd_rows=self.upd_rows + other.upd_rows,
48
+ num_excs=self.num_excs + other.num_excs,
49
+ computed_values=self.computed_values + other.computed_values,
50
+ )
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class UpdateStatus:
55
+ """
56
+ Information about changes to table data or table schema
57
+ """
58
+
59
+ updated_cols: list[str] = field(default_factory=list)
60
+ """Columns that were updated."""
61
+ cols_with_excs: list[str] = field(default_factory=list)
62
+ """Columns that encountered exceptions."""
63
+
64
+ # stats for the rows affected by the operation
65
+ row_count_stats: RowCountStats = field(default_factory=RowCountStats)
66
+ """Row count statistics for rows affected by this operation."""
67
+
68
+ # stats for changes cascaded to other tables
69
+ cascade_row_count_stats: RowCountStats = field(default_factory=RowCountStats)
70
+ """Row count statistics for changes cascaded to other tables."""
71
+
72
+ # stats for the rows affected by the operation in an external store
73
+ ext_row_count_stats: RowCountStats = field(default_factory=RowCountStats)
74
+ """Row count statistics for rows affected in an external store."""
75
+
76
+ @property
77
+ def num_rows(self) -> int:
78
+ """Total number of rows affected (including cascaded changes)."""
79
+ return self.row_count_stats.num_rows + self.cascade_row_count_stats.num_rows
80
+
81
+ @property
82
+ def num_excs(self) -> int:
83
+ """Total number of exceptions encountered (including cascaded changes)."""
84
+ return self.row_count_stats.num_excs + self.cascade_row_count_stats.num_excs
85
+
86
+ @property
87
+ def num_computed_values(self) -> int:
88
+ """Total number of computed values affected (including cascaded changes)."""
89
+ return self.row_count_stats.computed_values + self.cascade_row_count_stats.computed_values
90
+
91
+ def insert_to_update(self) -> 'UpdateStatus':
92
+ """
93
+ Convert the update status from an insert operation to an update operation.
94
+ This is used when an insert operation is treated as an update.
95
+ """
96
+ return UpdateStatus(
97
+ updated_cols=self.updated_cols,
98
+ cols_with_excs=self.cols_with_excs,
99
+ row_count_stats=self.row_count_stats.insert_to_update(),
100
+ cascade_row_count_stats=self.cascade_row_count_stats.insert_to_update(),
101
+ ext_row_count_stats=self.ext_row_count_stats,
102
+ )
103
+
104
+ def to_cascade(self) -> 'UpdateStatus':
105
+ """
106
+ Convert the update status to a cascade update status.
107
+ This is used when an operation cascades changes to other tables.
108
+ """
109
+ return UpdateStatus(
110
+ updated_cols=self.updated_cols,
111
+ cols_with_excs=self.cols_with_excs,
112
+ row_count_stats=RowCountStats(),
113
+ cascade_row_count_stats=self.cascade_row_count_stats + self.row_count_stats,
114
+ ext_row_count_stats=self.ext_row_count_stats,
115
+ )
116
+
117
+ def __add__(self, other: 'UpdateStatus') -> UpdateStatus:
118
+ """
119
+ Add the update status from two UpdateStatus objects together.
120
+ """
121
+ return UpdateStatus(
122
+ updated_cols=list(dict.fromkeys(self.updated_cols + other.updated_cols)),
123
+ cols_with_excs=list(dict.fromkeys(self.cols_with_excs + other.cols_with_excs)),
124
+ row_count_stats=self.row_count_stats + other.row_count_stats,
125
+ cascade_row_count_stats=self.cascade_row_count_stats + other.cascade_row_count_stats,
126
+ ext_row_count_stats=self.ext_row_count_stats + other.ext_row_count_stats,
127
+ )
128
+
129
+ @property
130
+ def insert_msg(self) -> str:
131
+ """Return a message describing the results of an insert operation."""
132
+ if self.num_excs == 0:
133
+ cols_with_excs_str = ''
134
+ else:
135
+ cols_with_excs_str = (
136
+ f' across {len(self.cols_with_excs)} column{"" if len(self.cols_with_excs) == 1 else "s"}'
137
+ )
138
+ cols_with_excs_str += f' ({", ".join(self.cols_with_excs)})'
139
+ msg = (
140
+ f'Inserted {self.num_rows} row{"" if self.num_rows == 1 else "s"} '
141
+ f'with {self.num_excs} error{"" if self.num_excs == 1 else "s"}{cols_with_excs_str}.'
142
+ )
143
+ return msg
144
+
145
+ @classmethod
146
+ def __cnt_str(cls, cnt: int, item: str) -> str:
147
+ assert cnt > 0
148
+ return f'{cnt} {item}{"" if cnt == 1 else "s"}'
149
+
150
+ def _repr_pretty_(self, p: 'RepresentationPrinter', cycle: bool) -> None:
151
+ messages = []
152
+ # Combine row count stats and cascade row count stats
153
+ stats = self.row_count_stats + self.cascade_row_count_stats
154
+ if stats.ins_rows > 0:
155
+ messages.append(f'{self.__cnt_str(stats.ins_rows, "row")} inserted')
156
+ if stats.del_rows > 0:
157
+ messages.append(f'{self.__cnt_str(stats.del_rows, "row")} deleted')
158
+ if stats.upd_rows > 0:
159
+ messages.append(f'{self.__cnt_str(stats.upd_rows, "row")} updated')
160
+ if stats.computed_values > 0:
161
+ messages.append(f'{self.__cnt_str(stats.computed_values, "value")} computed')
162
+ if stats.num_excs > 0:
163
+ messages.append(self.__cnt_str(stats.num_excs, 'exception'))
164
+ p.text(', '.join(messages) + '.' if len(messages) > 0 else 'No rows affected.')
165
+
166
+ @property
167
+ def pxt_rows_updated(self) -> int:
168
+ """
169
+ Returns the number of Pixeltable rows that were updated as a result of the operation.
170
+ """
171
+ return (self.row_count_stats + self.cascade_row_count_stats).upd_rows
172
+
173
+ @property
174
+ def external_rows_updated(self) -> int:
175
+ """Number of rows updated in an external store."""
176
+ return self.ext_row_count_stats.upd_rows
177
+
178
+ @property
179
+ def external_rows_created(self) -> int:
180
+ """Number of rows created in an external store."""
181
+ return self.ext_row_count_stats.ins_rows
182
+
183
+ @property
184
+ def external_rows_deleted(self) -> int:
185
+ """Number of rows deleted from an external store."""
186
+ return self.ext_row_count_stats.del_rows
187
+
188
+ @property
189
+ def ext_num_rows(self) -> int:
190
+ """Total number of rows affected in an external store."""
191
+ return self.ext_row_count_stats.num_rows