pixeltable 0.4.2__py3-none-any.whl → 0.4.4__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 (60) hide show
  1. pixeltable/__init__.py +1 -0
  2. pixeltable/__version__.py +2 -2
  3. pixeltable/catalog/__init__.py +3 -11
  4. pixeltable/catalog/catalog.py +575 -220
  5. pixeltable/catalog/column.py +22 -23
  6. pixeltable/catalog/dir.py +1 -2
  7. pixeltable/catalog/globals.py +2 -148
  8. pixeltable/catalog/insertable_table.py +15 -13
  9. pixeltable/catalog/path.py +6 -0
  10. pixeltable/catalog/schema_object.py +9 -4
  11. pixeltable/catalog/table.py +96 -85
  12. pixeltable/catalog/table_version.py +257 -174
  13. pixeltable/catalog/table_version_path.py +1 -1
  14. pixeltable/catalog/tbl_ops.py +44 -0
  15. pixeltable/catalog/update_status.py +179 -0
  16. pixeltable/catalog/view.py +50 -56
  17. pixeltable/config.py +76 -12
  18. pixeltable/dataframe.py +19 -6
  19. pixeltable/env.py +50 -4
  20. pixeltable/exec/data_row_batch.py +3 -1
  21. pixeltable/exec/exec_node.py +7 -24
  22. pixeltable/exec/expr_eval/schedulers.py +134 -7
  23. pixeltable/exec/in_memory_data_node.py +6 -7
  24. pixeltable/exprs/column_property_ref.py +21 -9
  25. pixeltable/exprs/column_ref.py +7 -2
  26. pixeltable/exprs/function_call.py +2 -2
  27. pixeltable/exprs/row_builder.py +10 -9
  28. pixeltable/exprs/rowid_ref.py +0 -4
  29. pixeltable/func/function.py +3 -3
  30. pixeltable/functions/audio.py +36 -9
  31. pixeltable/functions/gemini.py +4 -4
  32. pixeltable/functions/openai.py +1 -2
  33. pixeltable/functions/video.py +59 -16
  34. pixeltable/globals.py +109 -24
  35. pixeltable/io/__init__.py +1 -1
  36. pixeltable/io/datarows.py +2 -1
  37. pixeltable/io/external_store.py +3 -55
  38. pixeltable/io/globals.py +4 -4
  39. pixeltable/io/hf_datasets.py +10 -2
  40. pixeltable/io/label_studio.py +16 -16
  41. pixeltable/io/pandas.py +1 -0
  42. pixeltable/io/table_data_conduit.py +12 -13
  43. pixeltable/iterators/audio.py +17 -8
  44. pixeltable/iterators/image.py +5 -2
  45. pixeltable/metadata/__init__.py +1 -1
  46. pixeltable/metadata/converters/convert_39.py +125 -0
  47. pixeltable/metadata/converters/util.py +3 -0
  48. pixeltable/metadata/notes.py +1 -0
  49. pixeltable/metadata/schema.py +50 -1
  50. pixeltable/plan.py +4 -0
  51. pixeltable/share/packager.py +20 -38
  52. pixeltable/store.py +40 -51
  53. pixeltable/type_system.py +2 -2
  54. pixeltable/utils/coroutine.py +6 -23
  55. pixeltable/utils/media_store.py +50 -0
  56. {pixeltable-0.4.2.dist-info → pixeltable-0.4.4.dist-info}/METADATA +1 -1
  57. {pixeltable-0.4.2.dist-info → pixeltable-0.4.4.dist-info}/RECORD +60 -57
  58. {pixeltable-0.4.2.dist-info → pixeltable-0.4.4.dist-info}/LICENSE +0 -0
  59. {pixeltable-0.4.2.dist-info → pixeltable-0.4.4.dist-info}/WHEEL +0 -0
  60. {pixeltable-0.4.2.dist-info → pixeltable-0.4.4.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@ import json
6
6
  import logging
7
7
  from keyword import iskeyword as is_python_keyword
8
8
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Iterable, Literal, Optional, Union, overload
9
+ from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Literal, Optional, Union, overload
10
10
 
11
11
  from typing import _GenericAlias # type: ignore[attr-defined] # isort: skip
12
12
  import datetime
@@ -29,13 +29,13 @@ from .globals import (
29
29
  IfExistsParam,
30
30
  IfNotExistsParam,
31
31
  MediaValidation,
32
- UpdateStatus,
33
32
  is_system_column_name,
34
33
  is_valid_identifier,
35
34
  )
36
35
  from .schema_object import SchemaObject
37
36
  from .table_version_handle import TableVersionHandle
38
37
  from .table_version_path import TableVersionPath
38
+ from .update_status import UpdateStatus
39
39
 
40
40
  if TYPE_CHECKING:
41
41
  import torch.utils.data
@@ -109,8 +109,6 @@ class Table(SchemaObject):
109
109
 
110
110
  def _get_metadata(self) -> dict[str, Any]:
111
111
  md = super()._get_metadata()
112
- base = self._get_base_table()
113
- md['base'] = base._path() if base is not None else None
114
112
  md['schema'] = self._get_schema()
115
113
  md['is_replica'] = self._tbl_version_path.is_replica()
116
114
  md['version'] = self._get_version()
@@ -149,11 +147,15 @@ class Table(SchemaObject):
149
147
  Returns:
150
148
  A list of view paths.
151
149
  """
152
- from pixeltable.catalog import Catalog
150
+ from pixeltable.catalog import retry_loop
153
151
 
154
- with Catalog.get().begin_xact(for_write=False):
152
+ # we need retry_loop() here, because we end up loading Tables for the views
153
+ @retry_loop(tbl=self._tbl_version_path, for_write=False)
154
+ def op() -> list[str]:
155
155
  return [t._path() for t in self._get_views(recursive=recursive)]
156
156
 
157
+ return op()
158
+
157
159
  def _get_views(self, *, recursive: bool = True, include_snapshots: bool = True) -> list['Table']:
158
160
  cat = catalog.Catalog.get()
159
161
  view_ids = cat.get_view_ids(self._id)
@@ -180,7 +182,7 @@ class Table(SchemaObject):
180
182
  """
181
183
  from pixeltable.catalog import Catalog
182
184
 
183
- with Catalog.get().begin_xact(for_write=False):
185
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
184
186
  return self._df().select(*items, **named_items)
185
187
 
186
188
  def where(self, pred: 'exprs.Expr') -> 'pxt.DataFrame':
@@ -190,7 +192,7 @@ class Table(SchemaObject):
190
192
  """
191
193
  from pixeltable.catalog import Catalog
192
194
 
193
- with Catalog.get().begin_xact(for_write=False):
195
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
194
196
  return self._df().where(pred)
195
197
 
196
198
  def join(
@@ -203,7 +205,7 @@ class Table(SchemaObject):
203
205
  """Join this table with another table."""
204
206
  from pixeltable.catalog import Catalog
205
207
 
206
- with Catalog.get().begin_xact(for_write=False):
208
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
207
209
  return self._df().join(other, on=on, how=how)
208
210
 
209
211
  def order_by(self, *items: 'exprs.Expr', asc: bool = True) -> 'pxt.DataFrame':
@@ -213,7 +215,7 @@ class Table(SchemaObject):
213
215
  """
214
216
  from pixeltable.catalog import Catalog
215
217
 
216
- with Catalog.get().begin_xact(for_write=False):
218
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
217
219
  return self._df().order_by(*items, asc=asc)
218
220
 
219
221
  def group_by(self, *items: 'exprs.Expr') -> 'pxt.DataFrame':
@@ -223,7 +225,7 @@ class Table(SchemaObject):
223
225
  """
224
226
  from pixeltable.catalog import Catalog
225
227
 
226
- with Catalog.get().begin_xact(for_write=False):
228
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
227
229
  return self._df().group_by(*items)
228
230
 
229
231
  def distinct(self) -> 'pxt.DataFrame':
@@ -279,10 +281,7 @@ class Table(SchemaObject):
279
281
  return {c.name: c.col_type for c in self._tbl_version_path.columns()}
280
282
 
281
283
  def get_base_table(self) -> Optional['Table']:
282
- from pixeltable.catalog import Catalog
283
-
284
- with Catalog.get().begin_xact(for_write=False):
285
- return self._get_base_table()
284
+ return self._get_base_table()
286
285
 
287
286
  @abc.abstractmethod
288
287
  def _get_base_table(self) -> Optional['Table']:
@@ -323,7 +322,7 @@ class Table(SchemaObject):
323
322
  """
324
323
  from pixeltable.catalog import Catalog
325
324
 
326
- with Catalog.get().begin_xact(for_write=False):
325
+ with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=False):
327
326
  helper = DescriptionHelper()
328
327
  helper.append(self._table_descriptor())
329
328
  helper.append(self._col_descriptor())
@@ -494,8 +493,7 @@ class Table(SchemaObject):
494
493
 
495
494
  # lock_mutable_tree=True: we might end up having to drop existing columns, which requires locking the tree
496
495
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
497
- if self._tbl_version_path.is_snapshot():
498
- raise excs.Error('Cannot add column to a snapshot.')
496
+ self.__check_mutable('add columns to')
499
497
  col_schema = {
500
498
  col_name: {'type': ts.ColumnType.normalize_type(spec, nullable_default=True, allow_builtin_types=False)}
501
499
  for col_name, spec in schema.items()
@@ -510,15 +508,16 @@ class Table(SchemaObject):
510
508
  for cname in cols_to_ignore:
511
509
  assert cname in col_schema
512
510
  del col_schema[cname]
511
+ result = UpdateStatus()
513
512
  if len(col_schema) == 0:
514
- return UpdateStatus()
513
+ return result
515
514
  new_cols = self._create_columns(col_schema)
516
515
  for new_col in new_cols:
517
516
  self._verify_column(new_col)
518
517
  assert self._tbl_version is not None
519
- status = self._tbl_version.get().add_columns(new_cols, print_stats=False, on_error='abort')
518
+ result += self._tbl_version.get().add_columns(new_cols, print_stats=False, on_error='abort')
520
519
  FileCache.get().emit_eviction_warnings()
521
- return status
520
+ return result
522
521
 
523
522
  def add_column(
524
523
  self,
@@ -554,24 +553,18 @@ class Table(SchemaObject):
554
553
 
555
554
  >>> tbl.add_columns({'new_col': pxt.Int})
556
555
  """
557
- from pixeltable.catalog import Catalog
558
-
559
- with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
560
- # verify kwargs
561
- if self._tbl_version_path.is_snapshot():
562
- raise excs.Error('Cannot add column to a snapshot.')
563
- # verify kwargs and construct column schema dict
564
- if len(kwargs) != 1:
565
- raise excs.Error(
566
- f'add_column() requires exactly one keyword argument of the form "col_name=col_type"; '
567
- f'got {len(kwargs)} instead ({", ".join(kwargs.keys())})'
568
- )
569
- col_type = next(iter(kwargs.values()))
570
- if not isinstance(col_type, (ts.ColumnType, type, _GenericAlias)):
571
- raise excs.Error(
572
- 'The argument to add_column() must be a type; did you intend to use add_computed_column() instead?'
573
- )
574
- return self.add_columns(kwargs, if_exists=if_exists)
556
+ # verify kwargs and construct column schema dict
557
+ if len(kwargs) != 1:
558
+ raise excs.Error(
559
+ f'add_column() requires exactly one keyword argument of the form "col_name=col_type"; '
560
+ f'got {len(kwargs)} instead ({", ".join(kwargs.keys())})'
561
+ )
562
+ col_type = next(iter(kwargs.values()))
563
+ if not isinstance(col_type, (ts.ColumnType, type, _GenericAlias)):
564
+ raise excs.Error(
565
+ 'The argument to add_column() must be a type; did you intend to use add_computed_column() instead?'
566
+ )
567
+ return self.add_columns(kwargs, if_exists=if_exists)
575
568
 
576
569
  def add_computed_column(
577
570
  self,
@@ -595,7 +588,7 @@ class Table(SchemaObject):
595
588
  - `'abort'`: an exception will be raised and the column will not be added.
596
589
  - `'ignore'`: execution will continue and the column will be added. Any rows
597
590
  with errors will have a `None` value for the column, with information about the error stored in the
598
- corresponding `tbl.col_name.errortype` and `tbl.col_name.errormsg` fields.
591
+ corresponding `tbl.col_name.errormsg` tbl.col_name.errortype` fields.
599
592
  if_exists: Determines the behavior if the column already exists. Must be one of the following:
600
593
 
601
594
  - `'error'`: an exception will be raised.
@@ -623,8 +616,7 @@ class Table(SchemaObject):
623
616
  from pixeltable.catalog import Catalog
624
617
 
625
618
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
626
- if self._tbl_version_path.is_snapshot():
627
- raise excs.Error('Cannot add column to a snapshot.')
619
+ self.__check_mutable('add columns to')
628
620
  if len(kwargs) != 1:
629
621
  raise excs.Error(
630
622
  f'add_computed_column() requires exactly one keyword argument of the form '
@@ -642,10 +634,10 @@ class Table(SchemaObject):
642
634
  # Raise an error if the column expression refers to a column error property
643
635
  if isinstance(spec, exprs.Expr):
644
636
  for e in spec.subexprs(expr_class=exprs.ColumnPropertyRef, traverse_matches=False):
645
- if e.is_error_prop():
637
+ if e.is_cellmd_prop():
646
638
  raise excs.Error(
647
- 'Use of a reference to an error property of another column is not allowed in a computed '
648
- f'column. The specified computation for this column contains this reference: `{e!r}`'
639
+ f'Use of a reference to the {e.prop.name.lower()!r} property of another column '
640
+ f'is not allowed in a computed column.'
649
641
  )
650
642
 
651
643
  # handle existing columns based on if_exists parameter
@@ -654,16 +646,17 @@ class Table(SchemaObject):
654
646
  )
655
647
  # if the column to add already exists and user asked to ignore
656
648
  # exiting column, there's nothing to do.
649
+ result = UpdateStatus()
657
650
  if len(cols_to_ignore) != 0:
658
651
  assert cols_to_ignore[0] == col_name
659
- return UpdateStatus()
652
+ return result
660
653
 
661
654
  new_col = self._create_columns({col_name: col_schema})[0]
662
655
  self._verify_column(new_col)
663
656
  assert self._tbl_version is not None
664
- status = self._tbl_version.get().add_columns([new_col], print_stats=print_stats, on_error=on_error)
657
+ result += self._tbl_version.get().add_columns([new_col], print_stats=print_stats, on_error=on_error)
665
658
  FileCache.get().emit_eviction_warnings()
666
- return status
659
+ return result
667
660
 
668
661
  @classmethod
669
662
  def _validate_column_spec(cls, name: str, spec: dict[str, Any]) -> None:
@@ -808,10 +801,10 @@ class Table(SchemaObject):
808
801
  from pixeltable.catalog import Catalog
809
802
 
810
803
  cat = Catalog.get()
804
+
811
805
  # lock_mutable_tree=True: we need to be able to see whether any transitive view has column dependents
812
806
  with cat.begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
813
- if self._tbl_version_path.is_snapshot():
814
- raise excs.Error('Cannot drop column from a snapshot.')
807
+ self.__check_mutable('drop columns from')
815
808
  col: Column = None
816
809
  if_not_exists_ = IfNotExistsParam.validated(if_not_exists, 'if_not_exists')
817
810
 
@@ -835,7 +828,7 @@ class Table(SchemaObject):
835
828
  dependent_user_cols = [c for c in cat.get_column_dependents(col.tbl.id, col.id) if c.name is not None]
836
829
  if len(dependent_user_cols) > 0:
837
830
  raise excs.Error(
838
- f'Cannot drop column `{col.name}` because the following columns depend on it:\n'
831
+ f'Cannot drop column {col.name!r} because the following columns depend on it:\n'
839
832
  f'{", ".join(c.name for c in dependent_user_cols)}'
840
833
  )
841
834
 
@@ -989,8 +982,7 @@ class Table(SchemaObject):
989
982
  from pixeltable.catalog import Catalog
990
983
 
991
984
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
992
- if self._tbl_version_path.is_snapshot():
993
- raise excs.Error('Cannot add an index to a snapshot')
985
+ self.__check_mutable('add an index to')
994
986
  col = self._resolve_column_parameter(column)
995
987
 
996
988
  if idx_name is not None and idx_name in self._tbl_version.get().idxs_by_name:
@@ -1174,8 +1166,7 @@ class Table(SchemaObject):
1174
1166
  ) -> None:
1175
1167
  from pixeltable.catalog import Catalog
1176
1168
 
1177
- if self._tbl_version_path.is_snapshot():
1178
- raise excs.Error('Cannot drop an index from a snapshot')
1169
+ self.__check_mutable('drop an index from')
1179
1170
  assert (col is None) != (idx_name is None)
1180
1171
 
1181
1172
  if idx_name is not None:
@@ -1347,11 +1338,10 @@ class Table(SchemaObject):
1347
1338
  from pixeltable.catalog import Catalog
1348
1339
 
1349
1340
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
1350
- if self._tbl_version_path.is_snapshot():
1351
- raise excs.Error('Cannot update a snapshot')
1352
- status = self._tbl_version.get().update(value_spec, where, cascade)
1341
+ self.__check_mutable('update')
1342
+ result = self._tbl_version.get().update(value_spec, where, cascade)
1353
1343
  FileCache.get().emit_eviction_warnings()
1354
- return status
1344
+ return result
1355
1345
 
1356
1346
  def batch_update(
1357
1347
  self,
@@ -1387,8 +1377,7 @@ class Table(SchemaObject):
1387
1377
  from pixeltable.catalog import Catalog
1388
1378
 
1389
1379
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
1390
- if self._tbl_version_path.is_snapshot():
1391
- raise excs.Error('Cannot update a snapshot')
1380
+ self.__check_mutable('update')
1392
1381
  rows = list(rows)
1393
1382
 
1394
1383
  row_updates: list[dict[Column, exprs.Expr]] = []
@@ -1415,7 +1404,7 @@ class Table(SchemaObject):
1415
1404
  raise excs.Error(f'Primary key columns ({", ".join(missing_cols)}) missing in {row_spec}')
1416
1405
  row_updates.append(col_vals)
1417
1406
 
1418
- status = self._tbl_version.get().batch_update(
1407
+ result = self._tbl_version.get().batch_update(
1419
1408
  row_updates,
1420
1409
  rowids,
1421
1410
  error_if_not_exists=if_not_exists == 'error',
@@ -1423,7 +1412,7 @@ class Table(SchemaObject):
1423
1412
  cascade=cascade,
1424
1413
  )
1425
1414
  FileCache.get().emit_eviction_warnings()
1426
- return status
1415
+ return result
1427
1416
 
1428
1417
  def recompute_columns(
1429
1418
  self, *columns: Union[str, ColumnRef], errors_only: bool = False, cascade: bool = True
@@ -1433,7 +1422,7 @@ class Table(SchemaObject):
1433
1422
  Args:
1434
1423
  columns: The names or references of the computed columns to recompute.
1435
1424
  errors_only: If True, only run the recomputation for rows that have errors in the column (ie, the column's
1436
- `errortype` property is non-None). Only allowed for recomputing a single column.
1425
+ `errortype` property indicates that an error occurred). Only allowed for recomputing a single column.
1437
1426
  cascade: if True, also update all computed columns that transitively depend on the recomputed columns.
1438
1427
 
1439
1428
  Examples:
@@ -1456,8 +1445,7 @@ class Table(SchemaObject):
1456
1445
  cat = Catalog.get()
1457
1446
  # lock_mutable_tree=True: we need to be able to see whether any transitive view has column dependents
1458
1447
  with cat.begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
1459
- if self._tbl_version_path.is_snapshot():
1460
- raise excs.Error('Cannot recompute columns of a snapshot.')
1448
+ self.__check_mutable('recompute columns of')
1461
1449
  if len(columns) == 0:
1462
1450
  raise excs.Error('At least one column must be specified to recompute')
1463
1451
  if errors_only and len(columns) > 1:
@@ -1484,9 +1472,9 @@ class Table(SchemaObject):
1484
1472
  raise excs.Error(f'Cannot recompute column of a base: {col_name!r}')
1485
1473
  col_names.append(col_name)
1486
1474
 
1487
- status = self._tbl_version.get().recompute_columns(col_names, errors_only=errors_only, cascade=cascade)
1475
+ result = self._tbl_version.get().recompute_columns(col_names, errors_only=errors_only, cascade=cascade)
1488
1476
  FileCache.get().emit_eviction_warnings()
1489
- return status
1477
+ return result
1490
1478
 
1491
1479
  def delete(self, where: Optional['exprs.Expr'] = None) -> UpdateStatus:
1492
1480
  """Delete rows in this table.
@@ -1514,8 +1502,7 @@ class Table(SchemaObject):
1514
1502
  from pixeltable.catalog import Catalog
1515
1503
 
1516
1504
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=True):
1517
- if self._tbl_version_path.is_snapshot():
1518
- raise excs.Error('Cannot revert a snapshot')
1505
+ self.__check_mutable('revert')
1519
1506
  self._tbl_version.get().revert()
1520
1507
  # remove cached md in order to force a reload on the next operation
1521
1508
  self._tbl_version_path.clear_cached_md()
@@ -1530,8 +1517,7 @@ class Table(SchemaObject):
1530
1517
  from pixeltable.catalog import Catalog
1531
1518
 
1532
1519
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=False):
1533
- if self._tbl_version_path.is_snapshot():
1534
- raise excs.Error(f'Table `{self._name}` is a snapshot, so it cannot be linked to an external store.')
1520
+ self.__check_mutable('link an external store to')
1535
1521
  if store.name in self.external_stores():
1536
1522
  raise excs.Error(f'Table `{self._name}` already has an external store with that name: {store.name}')
1537
1523
  _logger.info(f'Linking external store `{store.name}` to table `{self._name}`')
@@ -1560,7 +1546,7 @@ class Table(SchemaObject):
1560
1546
  """
1561
1547
  from pixeltable.catalog import Catalog
1562
1548
 
1563
- if self._tbl_version_path.is_snapshot():
1549
+ if not self._tbl_version_path.is_mutable():
1564
1550
  return
1565
1551
  with Catalog.get().begin_xact(tbl=self._tbl_version_path, for_write=True, lock_mutable_tree=False):
1566
1552
  all_stores = self.external_stores()
@@ -1588,7 +1574,7 @@ class Table(SchemaObject):
1588
1574
 
1589
1575
  def sync(
1590
1576
  self, stores: Optional[str | list[str]] = None, *, export_data: bool = True, import_data: bool = True
1591
- ) -> 'pxt.io.SyncStatus':
1577
+ ) -> UpdateStatus:
1592
1578
  """
1593
1579
  Synchronizes this table with its linked external stores.
1594
1580
 
@@ -1600,8 +1586,8 @@ class Table(SchemaObject):
1600
1586
  """
1601
1587
  from pixeltable.catalog import Catalog
1602
1588
 
1603
- if self._tbl_version_path.is_snapshot():
1604
- return pxt.io.SyncStatus()
1589
+ if not self._tbl_version_path.is_mutable():
1590
+ return UpdateStatus()
1605
1591
  # we lock the entire tree starting at the root base table in order to ensure that all synced columns can
1606
1592
  # have their updates propagated down the tree
1607
1593
  base_tv = self._tbl_version_path.get_tbl_versions()[-1]
@@ -1617,7 +1603,7 @@ class Table(SchemaObject):
1617
1603
  if store not in all_stores:
1618
1604
  raise excs.Error(f'Table `{self._name}` has no external store with that name: {store}')
1619
1605
 
1620
- sync_status = pxt.io.SyncStatus()
1606
+ sync_status = UpdateStatus()
1621
1607
  for store in stores:
1622
1608
  store_obj = self._tbl_version.get().external_stores[store]
1623
1609
  store_sync_status = store_obj.sync(self, export_data=export_data, import_data=import_data)
@@ -1631,6 +1617,19 @@ class Table(SchemaObject):
1631
1617
  def _ipython_key_completions_(self) -> list[str]:
1632
1618
  return list(self._get_schema().keys())
1633
1619
 
1620
+ _REPORT_SCHEMA: ClassVar[dict[str, ts.ColumnType]] = {
1621
+ 'version': ts.IntType(),
1622
+ 'created_at': ts.TimestampType(),
1623
+ 'user': ts.StringType(nullable=True),
1624
+ 'note': ts.StringType(),
1625
+ 'inserts': ts.IntType(nullable=True),
1626
+ 'updates': ts.IntType(nullable=True),
1627
+ 'deletes': ts.IntType(nullable=True),
1628
+ 'errors': ts.IntType(nullable=True),
1629
+ 'computed': ts.IntType(),
1630
+ 'schema_change': ts.StringType(),
1631
+ }
1632
+
1634
1633
  def history(self, n: Optional[int] = None) -> pixeltable.dataframe.DataFrameResultSet:
1635
1634
  """Returns rows of information about the versions of this table, most recent first.
1636
1635
 
@@ -1676,19 +1675,31 @@ class Table(SchemaObject):
1676
1675
  for vers_md in vers_list[0 : len(vers_list) - over_count]:
1677
1676
  version = vers_md.version_md.version
1678
1677
  schema_change = md_dict.get(version, '')
1679
- change_type = 'schema' if schema_change != '' else 'data'
1678
+ update_status = vers_md.version_md.update_status
1679
+ if update_status is None:
1680
+ update_status = UpdateStatus()
1681
+ change_type = 'schema' if schema_change != '' else ''
1682
+ if change_type == '':
1683
+ change_type = 'data'
1684
+ rcs = update_status.row_count_stats + update_status.cascade_row_count_stats
1680
1685
  report_line = [
1681
1686
  version,
1682
1687
  datetime.datetime.fromtimestamp(vers_md.version_md.created_at),
1688
+ vers_md.version_md.user,
1683
1689
  change_type,
1690
+ rcs.ins_rows,
1691
+ rcs.upd_rows,
1692
+ rcs.del_rows,
1693
+ rcs.num_excs,
1694
+ rcs.computed_values,
1684
1695
  schema_change,
1685
1696
  ]
1686
1697
  report_lines.append(report_line)
1687
1698
 
1688
- report_schema = {
1689
- 'version': ts.IntType(),
1690
- 'created_at': ts.TimestampType(),
1691
- 'change': ts.StringType(),
1692
- 'schema_change': ts.StringType(),
1693
- }
1694
- return pxt.dataframe.DataFrameResultSet(report_lines, report_schema)
1699
+ return pxt.dataframe.DataFrameResultSet(report_lines, self._REPORT_SCHEMA)
1700
+
1701
+ def __check_mutable(self, op_descr: str) -> None:
1702
+ if self._tbl_version_path.is_snapshot():
1703
+ raise excs.Error(f'{self._display_str()}: Cannot {op_descr} a snapshot.')
1704
+ if self._tbl_version_path.is_replica():
1705
+ raise excs.Error(f'{self._display_str()}: Cannot {op_descr} a {self._display_name()}.')