pixeltable 0.1.1__py3-none-any.whl → 0.2.0__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 (139) hide show
  1. pixeltable/__init__.py +34 -6
  2. pixeltable/catalog/__init__.py +13 -0
  3. pixeltable/catalog/catalog.py +159 -0
  4. pixeltable/catalog/column.py +200 -0
  5. pixeltable/catalog/dir.py +32 -0
  6. pixeltable/catalog/globals.py +33 -0
  7. pixeltable/catalog/insertable_table.py +191 -0
  8. pixeltable/catalog/named_function.py +36 -0
  9. pixeltable/catalog/path.py +58 -0
  10. pixeltable/catalog/path_dict.py +139 -0
  11. pixeltable/catalog/schema_object.py +39 -0
  12. pixeltable/catalog/table.py +581 -0
  13. pixeltable/catalog/table_version.py +749 -0
  14. pixeltable/catalog/table_version_path.py +133 -0
  15. pixeltable/catalog/view.py +203 -0
  16. pixeltable/client.py +520 -30
  17. pixeltable/dataframe.py +540 -349
  18. pixeltable/env.py +373 -45
  19. pixeltable/exceptions.py +12 -21
  20. pixeltable/exec/__init__.py +9 -0
  21. pixeltable/exec/aggregation_node.py +78 -0
  22. pixeltable/exec/cache_prefetch_node.py +113 -0
  23. pixeltable/exec/component_iteration_node.py +79 -0
  24. pixeltable/exec/data_row_batch.py +95 -0
  25. pixeltable/exec/exec_context.py +22 -0
  26. pixeltable/exec/exec_node.py +61 -0
  27. pixeltable/exec/expr_eval_node.py +217 -0
  28. pixeltable/exec/in_memory_data_node.py +69 -0
  29. pixeltable/exec/media_validation_node.py +43 -0
  30. pixeltable/exec/sql_scan_node.py +225 -0
  31. pixeltable/exprs/__init__.py +24 -0
  32. pixeltable/exprs/arithmetic_expr.py +102 -0
  33. pixeltable/exprs/array_slice.py +71 -0
  34. pixeltable/exprs/column_property_ref.py +77 -0
  35. pixeltable/exprs/column_ref.py +105 -0
  36. pixeltable/exprs/comparison.py +77 -0
  37. pixeltable/exprs/compound_predicate.py +98 -0
  38. pixeltable/exprs/data_row.py +187 -0
  39. pixeltable/exprs/expr.py +586 -0
  40. pixeltable/exprs/expr_set.py +39 -0
  41. pixeltable/exprs/function_call.py +380 -0
  42. pixeltable/exprs/globals.py +69 -0
  43. pixeltable/exprs/image_member_access.py +115 -0
  44. pixeltable/exprs/image_similarity_predicate.py +58 -0
  45. pixeltable/exprs/inline_array.py +107 -0
  46. pixeltable/exprs/inline_dict.py +101 -0
  47. pixeltable/exprs/is_null.py +38 -0
  48. pixeltable/exprs/json_mapper.py +121 -0
  49. pixeltable/exprs/json_path.py +159 -0
  50. pixeltable/exprs/literal.py +54 -0
  51. pixeltable/exprs/object_ref.py +41 -0
  52. pixeltable/exprs/predicate.py +44 -0
  53. pixeltable/exprs/row_builder.py +355 -0
  54. pixeltable/exprs/rowid_ref.py +94 -0
  55. pixeltable/exprs/type_cast.py +53 -0
  56. pixeltable/exprs/variable.py +45 -0
  57. pixeltable/func/__init__.py +9 -0
  58. pixeltable/func/aggregate_function.py +194 -0
  59. pixeltable/func/batched_function.py +53 -0
  60. pixeltable/func/callable_function.py +69 -0
  61. pixeltable/func/expr_template_function.py +82 -0
  62. pixeltable/func/function.py +110 -0
  63. pixeltable/func/function_registry.py +227 -0
  64. pixeltable/func/globals.py +36 -0
  65. pixeltable/func/nos_function.py +202 -0
  66. pixeltable/func/signature.py +166 -0
  67. pixeltable/func/udf.py +163 -0
  68. pixeltable/functions/__init__.py +52 -103
  69. pixeltable/functions/eval.py +216 -0
  70. pixeltable/functions/fireworks.py +61 -0
  71. pixeltable/functions/huggingface.py +120 -0
  72. pixeltable/functions/image.py +16 -0
  73. pixeltable/functions/openai.py +88 -0
  74. pixeltable/functions/pil/image.py +148 -7
  75. pixeltable/functions/string.py +13 -0
  76. pixeltable/functions/together.py +27 -0
  77. pixeltable/functions/util.py +41 -0
  78. pixeltable/functions/video.py +62 -0
  79. pixeltable/iterators/__init__.py +3 -0
  80. pixeltable/iterators/base.py +48 -0
  81. pixeltable/iterators/document.py +311 -0
  82. pixeltable/iterators/video.py +89 -0
  83. pixeltable/metadata/__init__.py +54 -0
  84. pixeltable/metadata/converters/convert_10.py +18 -0
  85. pixeltable/metadata/schema.py +211 -0
  86. pixeltable/plan.py +656 -0
  87. pixeltable/store.py +413 -182
  88. pixeltable/tests/conftest.py +143 -87
  89. pixeltable/tests/test_audio.py +65 -0
  90. pixeltable/tests/test_catalog.py +27 -0
  91. pixeltable/tests/test_client.py +14 -14
  92. pixeltable/tests/test_component_view.py +372 -0
  93. pixeltable/tests/test_dataframe.py +433 -0
  94. pixeltable/tests/test_dirs.py +78 -62
  95. pixeltable/tests/test_document.py +117 -0
  96. pixeltable/tests/test_exprs.py +591 -135
  97. pixeltable/tests/test_function.py +297 -67
  98. pixeltable/tests/test_functions.py +283 -1
  99. pixeltable/tests/test_migration.py +43 -0
  100. pixeltable/tests/test_nos.py +54 -0
  101. pixeltable/tests/test_snapshot.py +208 -0
  102. pixeltable/tests/test_table.py +1085 -262
  103. pixeltable/tests/test_transactional_directory.py +42 -0
  104. pixeltable/tests/test_types.py +5 -11
  105. pixeltable/tests/test_video.py +149 -34
  106. pixeltable/tests/test_view.py +530 -0
  107. pixeltable/tests/utils.py +186 -45
  108. pixeltable/tool/create_test_db_dump.py +149 -0
  109. pixeltable/type_system.py +490 -126
  110. pixeltable/utils/__init__.py +17 -46
  111. pixeltable/utils/clip.py +12 -15
  112. pixeltable/utils/coco.py +136 -0
  113. pixeltable/utils/documents.py +39 -0
  114. pixeltable/utils/filecache.py +195 -0
  115. pixeltable/utils/help.py +11 -0
  116. pixeltable/utils/media_store.py +76 -0
  117. pixeltable/utils/parquet.py +126 -0
  118. pixeltable/utils/pytorch.py +172 -0
  119. pixeltable/utils/s3.py +13 -0
  120. pixeltable/utils/sql.py +17 -0
  121. pixeltable/utils/transactional_directory.py +35 -0
  122. pixeltable-0.2.0.dist-info/LICENSE +18 -0
  123. pixeltable-0.2.0.dist-info/METADATA +117 -0
  124. pixeltable-0.2.0.dist-info/RECORD +125 -0
  125. {pixeltable-0.1.1.dist-info → pixeltable-0.2.0.dist-info}/WHEEL +1 -1
  126. pixeltable/catalog.py +0 -1421
  127. pixeltable/exprs.py +0 -1745
  128. pixeltable/function.py +0 -269
  129. pixeltable/functions/clip.py +0 -10
  130. pixeltable/functions/pil/__init__.py +0 -23
  131. pixeltable/functions/tf.py +0 -21
  132. pixeltable/index.py +0 -57
  133. pixeltable/tests/test_dict.py +0 -24
  134. pixeltable/tests/test_tf.py +0 -69
  135. pixeltable/tf.py +0 -33
  136. pixeltable/utils/tf.py +0 -33
  137. pixeltable/utils/video.py +0 -32
  138. pixeltable-0.1.1.dist-info/METADATA +0 -31
  139. pixeltable-0.1.1.dist-info/RECORD +0 -36
@@ -0,0 +1,372 @@
1
+ from typing import Dict, Any, List, Tuple
2
+
3
+ import PIL
4
+ import numpy as np
5
+ import pandas as pd
6
+ import pytest
7
+
8
+ import pixeltable as pxt
9
+ from pixeltable import exceptions as excs
10
+ from pixeltable.iterators import ComponentIterator
11
+ from pixeltable.iterators.video import FrameIterator
12
+ from pixeltable.tests.utils import assert_resultset_eq, get_video_files
13
+ from pixeltable.type_system import IntType, VideoType, JsonType
14
+
15
+
16
+ class ConstantImgIterator(ComponentIterator):
17
+ """Component iterator that generates a fixed number of all-black 1280x720 images."""
18
+ def __init__(self, video: str, num_frames: int = 10):
19
+ self.img = PIL.Image.new('RGB', (1280, 720))
20
+ self.next_frame_idx = 0
21
+ self.num_frames = num_frames
22
+ self.pos_msec = 0.0
23
+ self.pos_frame = 0.0
24
+
25
+ @classmethod
26
+ def input_schema(cls) -> Dict[str, pxt.ColumnType]:
27
+ return {
28
+ 'video': VideoType(nullable=False),
29
+ 'fps': pxt.FloatType()
30
+ }
31
+
32
+ @classmethod
33
+ def output_schema(cls, *args: Any, **kwargs: Any) -> Tuple[Dict[str, pxt.ColumnType], List[str]]:
34
+ return {
35
+ 'frame_idx': IntType(),
36
+ 'pos_msec': pxt.FloatType(),
37
+ 'pos_frame': pxt.FloatType(),
38
+ 'frame': pxt.ImageType(),
39
+ }, ['frame']
40
+
41
+ def __next__(self) -> Dict[str, Any]:
42
+ while True:
43
+ if self.next_frame_idx == self.num_frames:
44
+ raise StopIteration
45
+ result = {
46
+ 'frame_idx': self.next_frame_idx,
47
+ 'pos_msec': self.pos_msec,
48
+ 'pos_frame': self.pos_frame,
49
+ 'frame': self.img,
50
+ }
51
+ self.next_frame_idx += 1
52
+ return result
53
+
54
+ def close(self) -> None:
55
+ pass
56
+
57
+ def set_pos(self, pos: int) -> None:
58
+ if pos == self.next_frame_idx:
59
+ return
60
+ self.next_frame_idx = pos
61
+
62
+
63
+ class TestComponentView:
64
+ def test_basic(self, test_client: pxt.Client) -> None:
65
+ cl = test_client
66
+ # create video table
67
+ schema = {'video': VideoType(), 'angle': IntType(), 'other_angle': IntType()}
68
+ video_t = cl.create_table('video_tbl', schema)
69
+ video_filepaths = get_video_files()
70
+
71
+ # cannot add 'pos' column
72
+ with pytest.raises(excs.Error) as excinfo:
73
+ video_t.add_column(pos=IntType())
74
+ assert 'reserved' in str(excinfo.value)
75
+
76
+ # parameter missing
77
+ with pytest.raises(excs.Error) as excinfo:
78
+ args = {'fps': 1}
79
+ _ = cl.create_view('test_view', video_t, iterator_class=FrameIterator, iterator_args=args)
80
+ assert 'missing a required argument' in str(excinfo.value)
81
+
82
+ # bad parameter type
83
+ with pytest.raises(excs.Error) as excinfo:
84
+ args = {'video': video_t.video, 'fps': '1'}
85
+ _ = cl.create_view('test_view', video_t, iterator_class=FrameIterator, iterator_args=args)
86
+ assert 'expected float' in str(excinfo.value)
87
+
88
+ # bad parameter type
89
+ with pytest.raises(excs.Error) as excinfo:
90
+ args = {'video': 1, 'fps': 1}
91
+ _ = cl.create_view('test_view', video_t, iterator_class=FrameIterator, iterator_args=args)
92
+ assert 'expected file path' in str(excinfo.value)
93
+
94
+ # create frame view
95
+ args = {'video': video_t.video, 'fps': 1}
96
+ view_t = cl.create_view('test_view', video_t, iterator_class=FrameIterator, iterator_args=args)
97
+ # computed column that references a column from the base
98
+ view_t.add_column(angle2=view_t.angle + 1)
99
+ # computed column that references an unstored and a stored computed view column
100
+ view_t.add_column(v1=view_t.frame.rotate(view_t.angle2), stored=True)
101
+ # computed column that references a stored computed column from the view
102
+ view_t.add_column(v2=view_t.frame_idx - 1)
103
+ # computed column that references an unstored view column and a column from the base; the stored value
104
+ # cannot be materialized in SQL directly
105
+ view_t.add_column(v3=view_t.frame.rotate(video_t.other_angle), stored=True)
106
+
107
+ # and load data
108
+ rows = [{'video': p, 'angle': 30, 'other_angle': -30} for p in video_filepaths]
109
+ status = video_t.insert(rows)
110
+ assert status.num_excs == 0
111
+ # pos and frame_idx are identical
112
+ res = view_t.select(view_t.pos, view_t.frame_idx).collect().to_pandas()
113
+ assert np.all(res['pos'] == res['frame_idx'])
114
+
115
+ video_url = video_t.select(video_t.video.fileurl).show(0)[0, 0]
116
+ result = view_t.where(view_t.video == video_url).select(view_t.frame, view_t.frame_idx) \
117
+ .collect()
118
+ result = view_t.where(view_t.video == video_url).select(view_t.frame_idx).order_by(view_t.frame_idx) \
119
+ .collect().to_pandas()
120
+ assert len(result) > 0
121
+ assert np.all(result['frame_idx'] == pd.Series(range(len(result))))
122
+
123
+ def test_add_column(self, test_client: pxt.Client) -> None:
124
+ cl = test_client
125
+ # create video table
126
+ video_t = cl.create_table('video_tbl', {'video': VideoType()})
127
+ video_filepaths = get_video_files()
128
+ # create frame view
129
+ args = {'video': video_t.video, 'fps': 1}
130
+ view_t = cl.create_view('test_view', video_t, iterator_class=FrameIterator, iterator_args=args)
131
+
132
+ rows = [{'video': p} for p in video_filepaths]
133
+ video_t.insert(rows)
134
+ # adding a non-computed column backfills it with nulls
135
+ view_t.add_column(annotation=JsonType(nullable=True))
136
+ assert view_t.count() == view_t.where(view_t.annotation == None).count()
137
+ # adding more data via the base table sets the column values to null
138
+ video_t.insert(rows)
139
+ _ = view_t.where(view_t.annotation == None).count()
140
+ assert view_t.count() == view_t.where(view_t.annotation == None).count()
141
+
142
+ with pytest.raises(excs.Error) as excinfo:
143
+ view_t.add_column(annotation=JsonType(nullable=False))
144
+ assert 'must be nullable' in str(excinfo.value)
145
+
146
+ def test_update(self, test_client: pxt.Client) -> None:
147
+ cl = test_client
148
+ # create video table
149
+ video_t = cl.create_table('video_tbl', {'video': VideoType()})
150
+ # create frame view with manually updated column
151
+ args = {'video': video_t.video, 'fps': 1}
152
+ view_t = cl.create_view(
153
+ 'test_view', video_t, schema={'annotation': JsonType(nullable=True)},
154
+ iterator_class=FrameIterator, iterator_args=args)
155
+
156
+ video_filepaths = get_video_files()
157
+ rows = [{'video': p} for p in video_filepaths]
158
+ status = video_t.insert(rows)
159
+ assert status.num_excs == 0
160
+ import urllib
161
+ video_url = urllib.parse.urljoin('file:', urllib.request.pathname2url(video_filepaths[0]))
162
+ status = view_t.update({'annotation': {'a': 1}}, where=view_t.video == video_url)
163
+ c1 = view_t.where(view_t.annotation != None).count()
164
+ c2 = view_t.where(view_t.video == video_url).count()
165
+ assert c1 == c2
166
+
167
+ with pytest.raises(excs.Error) as excinfo:
168
+ _ = cl.create_view(
169
+ 'bad_view', video_t, schema={'annotation': JsonType(nullable=False)},
170
+ iterator_class=FrameIterator, iterator_args=args)
171
+ assert 'must be nullable' in str(excinfo.value)
172
+
173
+ # break up the snapshot tests for better (future) parallelization
174
+ def test_snapshot1(self, test_client: pxt.Client) -> None:
175
+ has_column = False
176
+ has_filter = False
177
+ for reload_md in [False, True]:
178
+ cl = pxt.Client(reload=True)
179
+ self.run_snapshot_test(cl, has_column=has_column, has_filter=has_filter, reload_md=reload_md)
180
+
181
+ def test_snapshot2(self, test_client: pxt.Client) -> None:
182
+ has_column = True
183
+ has_filter = False
184
+ for reload_md in [False, True]:
185
+ cl = pxt.Client(reload=True)
186
+ self.run_snapshot_test(cl, has_column=has_column, has_filter=has_filter, reload_md=reload_md)
187
+
188
+ def test_snapshot3(self, test_client: pxt.Client) -> None:
189
+ has_column = False
190
+ has_filter = True
191
+ for reload_md in [False, True]:
192
+ cl = pxt.Client(reload=True)
193
+ self.run_snapshot_test(cl, has_column=has_column, has_filter=has_filter, reload_md=reload_md)
194
+
195
+ def test_snapshot4(self, test_client: pxt.Client) -> None:
196
+ has_column = True
197
+ has_filter = True
198
+ for reload_md in [False, True]:
199
+ cl = pxt.Client(reload=True)
200
+ self.run_snapshot_test(cl, has_column=has_column, has_filter=has_filter, reload_md=reload_md)
201
+
202
+ def run_snapshot_test(self, cl: pxt.Client, has_column: bool, has_filter: bool, reload_md: bool) -> None:
203
+ base_path = 'video_tbl'
204
+ view_path = 'test_view'
205
+ snap_path = 'test_snap'
206
+
207
+ # create video table
208
+ video_t = cl.create_table(base_path, {'video': VideoType(), 'margin': IntType()})
209
+ video_filepaths = get_video_files()
210
+ rows = [{'video': path, 'margin': i * 10} for i, path in enumerate(video_filepaths)]
211
+ status = video_t.insert(rows)
212
+ assert status.num_rows == len(rows)
213
+ assert status.num_excs == 0
214
+
215
+ # create frame view with a computed column
216
+ args = {'video': video_t.video}
217
+ view_t = cl.create_view(
218
+ view_path, video_t, iterator_class=ConstantImgIterator, iterator_args=args, is_snapshot=False)
219
+ view_t.add_column(
220
+ cropped=view_t.frame.crop([view_t.margin, view_t.margin, view_t.frame.width, view_t.frame.height]),
221
+ stored=True)
222
+ snap_col_expr = [view_t.cropped.width * view_t.cropped.height] if has_column else []
223
+ view_query = \
224
+ view_t.select(
225
+ view_t.margin, view_t.frame.width, view_t.frame.height, view_t.cropped.width,
226
+ view_t.cropped.height, *snap_col_expr)\
227
+ .order_by(view_t.video, view_t.pos)
228
+ if has_filter:
229
+ view_query = view_query.where(view_t.frame_idx < 10)
230
+ orig_resultset = view_query.collect()
231
+
232
+ # create snapshot of view
233
+ filter = view_t.frame_idx < 10 if has_filter else None
234
+ schema = {'c1': view_t.cropped.width * view_t.cropped.height} if has_column else {}
235
+ snap_t = cl.create_view(snap_path, view_t, schema=schema, filter=filter, is_snapshot=True)
236
+ snap_cols = [snap_t.c1] if has_column else []
237
+ snap_query = \
238
+ snap_t.select(
239
+ snap_t.margin, snap_t.frame.width, snap_t.frame.height, snap_t.cropped.width,
240
+ snap_t.cropped.height, *snap_cols)\
241
+ .order_by(snap_t.video, snap_t.pos)
242
+ assert_resultset_eq(snap_query.collect(), orig_resultset)
243
+
244
+ if reload_md:
245
+ cl = pxt.Client(reload=True)
246
+ video_t = cl.get_table(base_path)
247
+ snap_t = cl.get_table(snap_path)
248
+ snap_cols = [snap_t.c1] if has_column else []
249
+ snap_query = \
250
+ snap_t.select(
251
+ snap_t.margin, snap_t.frame.width, snap_t.frame.height, snap_t.cropped.width,
252
+ snap_t.cropped.height, *snap_cols) \
253
+ .order_by(snap_t.video, snap_t.pos)
254
+
255
+ # snapshot is unaffected by base insert()
256
+ status = video_t.insert(rows)
257
+ assert status.num_excs == 0
258
+ assert_resultset_eq(snap_query.collect(), orig_resultset)
259
+
260
+ # snapshot is unaffected by base update()
261
+ status = video_t.update({'margin': video_t.margin + 1})
262
+ assert status.num_excs == 0
263
+ assert_resultset_eq(snap_query.collect(), orig_resultset)
264
+
265
+ # snapshot is unaffected by base delete()
266
+ status = video_t.delete()
267
+ assert status.num_excs == 0
268
+ assert_resultset_eq(snap_query.collect(), orig_resultset)
269
+
270
+ cl.drop_table(snap_path)
271
+ cl.drop_table(view_path)
272
+ cl.drop_table(base_path)
273
+
274
+ def test_chained_views(self, test_client: pxt.Client) -> None:
275
+ """Component view followed by a standard view"""
276
+ cl = test_client
277
+ # create video table
278
+ schema = {'video': VideoType(), 'int1': IntType(), 'int2': IntType()}
279
+ video_t = cl.create_table('video_tbl', schema)
280
+ video_filepaths = get_video_files()
281
+
282
+ # create first view
283
+ args = {'video': video_t.video}
284
+ v1 = cl.create_view('test_view', video_t, iterator_class=ConstantImgIterator, iterator_args=args)
285
+ # computed column that references stored base column
286
+ v1.add_column(int3=v1.int1 + 1)
287
+ # stored computed column that references an unstored and a stored computed view column
288
+ v1.add_column(img1=v1.frame.crop([v1.int3, v1.int3, v1.frame.width, v1.frame.height]), stored=True)
289
+ # computed column that references a stored computed view column
290
+ v1.add_column(int4=v1.frame_idx + 1)
291
+ # unstored computed column that references an unstored and a stored computed view column
292
+ v1.add_column(img2=v1.frame.crop([v1.int4, v1.int4, v1.frame.width, v1.frame.height]), stored=False)
293
+
294
+ # create second view
295
+ v2 = cl.create_view('chained_view', v1)
296
+ # computed column that references stored video_t column
297
+ v2.add_column(int5=v2.int1 + 1)
298
+ v2.add_column(int6=v2.int2 + 1)
299
+ # stored computed column that references a stored base column and a stored computed view column;
300
+ # indirectly references int1
301
+ v2.add_column(img3=v2.img1.crop([v2.int5, v2.int5, v2.img1.width, v2.img1.height]), stored=True)
302
+ # stored computed column that references an unstored base column and a manually updated column from video_t;
303
+ # indirectly references int2
304
+ v2.add_column(img4=v2.img2.crop([v2.int6, v2.int6, v2.img2.width, v2.img2.height]), stored=True)
305
+ # comuted column that indirectly references int1 and int2
306
+ v2.add_column(int7=v2.img3.width + v2.img4.width)
307
+
308
+ def check_view():
309
+ assert_resultset_eq(
310
+ v1.select(v1.int3).order_by(v1.video, v1.pos).collect(),
311
+ v1.select(v1.int1 + 1).order_by(v1.video, v1.pos).collect())
312
+ assert_resultset_eq(
313
+ v1.select(v1.int4).order_by(v1.video, v1.pos).collect(),
314
+ v1.select(v1.frame_idx + 1).order_by(v1.video, v1.pos).collect())
315
+ assert_resultset_eq(
316
+ v1\
317
+ .select(v1.video, v1.img1.width, v1.img1.height)\
318
+ .order_by(v1.video, v1.pos).collect(),
319
+ v1\
320
+ .select(v1.video, v1.frame.width - v1.int1 - 1, v1.frame.height - v1.int1 - 1)\
321
+ .order_by(v1.video, v1.pos).collect())
322
+ assert_resultset_eq(
323
+ v2.select(v2.int5).order_by(v2.video, v2.pos).collect(),
324
+ v2.select(v2.int1 + 1).order_by(v2.video, v2.pos).collect())
325
+ assert_resultset_eq(
326
+ v2.select(v2.int6).order_by(v2.video, v2.pos).collect(),
327
+ v2.select(v2.int2 + 1).order_by(v2.video, v2.pos).collect())
328
+ assert_resultset_eq(
329
+ v2 \
330
+ .select(v2.video, v2.img3.width, v2.img3.height) \
331
+ .order_by(v2.video, v2.pos).collect(),
332
+ v2 \
333
+ .select(v2.video, v2.frame.width - v2.int1 * 2 - 2, v2.frame.height - v2.int1 * 2 - 2) \
334
+ .order_by(v2.video, v2.pos).collect())
335
+ assert_resultset_eq(
336
+ v2 \
337
+ .select(v2.video, v2.img4.width, v2.img4.height) \
338
+ .order_by(v2.video, v2.pos).collect(),
339
+ v2 \
340
+ .select(
341
+ v2.video, v2.frame.width - v2.frame_idx - v2.int2 - 2,
342
+ v2.frame.height - v2.frame_idx - v2.int2 - 2) \
343
+ .order_by(v2.video, v2.pos).collect())
344
+ assert_resultset_eq(
345
+ v2.select(v2.int7).order_by(v2.video, v2.pos).collect(),
346
+ v2.select(v2.img3.width + v2.img4.width).order_by(v2.video, v2.pos).collect())
347
+ assert_resultset_eq(
348
+ v2.select(v2.int7).order_by(v2.video, v2.pos).collect(),
349
+ v2.select(v2.frame.width - v2.int1 * 2 - 2 + v2.frame.width - v2.frame_idx - v2.int2 - 2)\
350
+ .order_by(v2.video, v2.pos).collect())
351
+
352
+ # load data
353
+ rows = [{'video': p, 'int1': i, 'int2': len(video_filepaths) - i} for i, p in enumerate(video_filepaths)]
354
+ status = video_t.insert(rows)
355
+ assert status.num_rows == video_t.count() + v1.count() + v2.count()
356
+ check_view()
357
+
358
+ # update int1: propagates to int3, img1, int5, img3, int7
359
+ # TODO: how to test that img4 doesn't get recomputed as part of the computation of int7?
360
+ # need to collect more runtime stats (eg, called functions)
361
+ import urllib
362
+ video_url = urllib.parse.urljoin('file:', urllib.request.pathname2url(video_filepaths[0]))
363
+ status = video_t.update({'int1': video_t.int1 + 1}, where=video_t.video == video_url)
364
+ assert status.num_rows == 1 + v1.where(v1.video == video_url).count() + v2.where(v2.video == video_url).count()
365
+ assert sorted('int1 int3 img1 int5 img3 int7'.split()) == sorted([str.split('.')[1] for str in status.updated_cols])
366
+ check_view()
367
+
368
+ # update int2: propagates to img4, int6, int7
369
+ status = video_t.update({'int2': video_t.int2 + 1}, where=video_t.video == video_url)
370
+ assert status.num_rows == 1 + v2.where(v2.video == video_url).count()
371
+ assert sorted('int2 img4 int6 int7'.split()) == sorted([str.split('.')[1] for str in status.updated_cols])
372
+ check_view()