perspective-python 5.6.0__cp311-abi3-pyemscripten_2026_0_wasm32.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.
- perspective/__init__.py +403 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +11 -0
- perspective/handlers/aiohttp.py +69 -0
- perspective/handlers/starlette.py +63 -0
- perspective/handlers/tornado.py +192 -0
- perspective/perspective.abi3.so +0 -0
- perspective/templates/exported_widget.html.template +39 -0
- perspective/tests/__init__.py +11 -0
- perspective/tests/async/test_async_client.py +83 -0
- perspective/tests/async/test_websocket_client.py +124 -0
- perspective/tests/column_paths.py +33 -0
- perspective/tests/conftest.py +276 -0
- perspective/tests/core/__init__.py +11 -0
- perspective/tests/core/test_async.py +351 -0
- perspective/tests/multi_threaded/__init__.py +11 -0
- perspective/tests/multi_threaded/test_derived_threaded.py +103 -0
- perspective/tests/multi_threaded/test_multi_threaded.py +293 -0
- perspective/tests/server/__init__.py +11 -0
- perspective/tests/server/test_server.py +1016 -0
- perspective/tests/server/test_session.py +110 -0
- perspective/tests/table/__init__.py +11 -0
- perspective/tests/table/arrow/date32.arrow +0 -0
- perspective/tests/table/arrow/date64.arrow +0 -0
- perspective/tests/table/arrow/dict.arrow +0 -0
- perspective/tests/table/arrow/dict_update.arrow +0 -0
- perspective/tests/table/arrow/int_float_str.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_file.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_update.arrow +0 -0
- perspective/tests/table/object_sequence.py +402 -0
- perspective/tests/table/test_column_paths.py +88 -0
- perspective/tests/table/test_delete.py +124 -0
- perspective/tests/table/test_derived.py +145 -0
- perspective/tests/table/test_exception.py +73 -0
- perspective/tests/table/test_join.py +115 -0
- perspective/tests/table/test_leaks.py +105 -0
- perspective/tests/table/test_on_remove.py +140 -0
- perspective/tests/table/test_ports.py +178 -0
- perspective/tests/table/test_remove.py +102 -0
- perspective/tests/table/test_table.py +641 -0
- perspective/tests/table/test_table_arrow.py +500 -0
- perspective/tests/table/test_table_arrow_nested.py +535 -0
- perspective/tests/table/test_table_datetime.py +2544 -0
- perspective/tests/table/test_table_infer.py +201 -0
- perspective/tests/table/test_table_limit.py +45 -0
- perspective/tests/table/test_table_numpy.py +1022 -0
- perspective/tests/table/test_table_page_to_disk.py +304 -0
- perspective/tests/table/test_table_pandas.py +1018 -0
- perspective/tests/table/test_table_polars.py +251 -0
- perspective/tests/table/test_table_view_table.py +130 -0
- perspective/tests/table/test_to_arrow.py +416 -0
- perspective/tests/table/test_to_arrow_lz4.py +32 -0
- perspective/tests/table/test_to_arrow_zstd.py +41 -0
- perspective/tests/table/test_to_format.py +1030 -0
- perspective/tests/table/test_to_polars.py +26 -0
- perspective/tests/table/test_update.py +561 -0
- perspective/tests/table/test_update_arrow.py +980 -0
- perspective/tests/table/test_update_pandas.py +211 -0
- perspective/tests/table/test_view.py +2503 -0
- perspective/tests/table/test_view_expression.py +2256 -0
- perspective/tests/test_dependencies.py +53 -0
- perspective/tests/viewer/__init__.py +11 -0
- perspective/tests/viewer/test_viewer.py +246 -0
- perspective/tests/virtual_servers/__init__.py +12 -0
- perspective/tests/virtual_servers/test_coerce_types.py +464 -0
- perspective/tests/virtual_servers/test_duckdb.py +1280 -0
- perspective/tests/virtual_servers/test_polars.py +1075 -0
- perspective/tests/virtual_servers/test_postgres.py +964 -0
- perspective/tests/widget/__init__.py +11 -0
- perspective/tests/widget/test_anywidget_smoke.py +109 -0
- perspective/tests/widget/test_widget.py +278 -0
- perspective/tests/widget/test_widget_html_export.py +113 -0
- perspective/tests/widget/test_widget_pandas.py +453 -0
- perspective/virtual_servers/__init__.py +206 -0
- perspective/virtual_servers/clickhouse.py +296 -0
- perspective/virtual_servers/duckdb.py +367 -0
- perspective/virtual_servers/polars.py +750 -0
- perspective/virtual_servers/postgres.py +448 -0
- perspective/widget/__init__.py +351 -0
- perspective/widget/static/perspective-anywidget.css +1 -0
- perspective/widget/static/perspective-anywidget.js +31 -0
- perspective/widget/viewer/__init__.py +15 -0
- perspective/widget/viewer/viewer.py +355 -0
- perspective/widget/viewer/viewer_traitlets.py +98 -0
- perspective_python-5.6.0.dist-info/METADATA +127 -0
- perspective_python-5.6.0.dist-info/RECORD +90 -0
- perspective_python-5.6.0.dist-info/WHEEL +4 -0
- perspective_python-5.6.0.dist-info/licenses/LICENSE.md +193 -0
- perspective_python-5.6.0.dist-info/licenses/LICENSE_THIRDPARTY_cargo.yml +16075 -0
- perspective_python-5.6.0.dist-info/sboms/perspective-python.cyclonedx.json +5334 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import io
|
|
14
|
+
from decimal import Decimal
|
|
15
|
+
|
|
16
|
+
import pyarrow as pa
|
|
17
|
+
import pyarrow.ipc as ipc
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
import perspective
|
|
21
|
+
from perspective import Client
|
|
22
|
+
from perspective.virtual_servers import VirtualServerHandler
|
|
23
|
+
|
|
24
|
+
FEATURES = {
|
|
25
|
+
"group_by": True,
|
|
26
|
+
"split_by": True,
|
|
27
|
+
"sort": True,
|
|
28
|
+
"group_rollup_mode": ["rollup", "flat", "total"],
|
|
29
|
+
"filter_ops": {
|
|
30
|
+
"integer": ["=="],
|
|
31
|
+
"float": ["=="],
|
|
32
|
+
"string": ["=="],
|
|
33
|
+
"boolean": ["=="],
|
|
34
|
+
"date": ["=="],
|
|
35
|
+
"datetime": ["=="],
|
|
36
|
+
},
|
|
37
|
+
"aggregates": {
|
|
38
|
+
"integer": ["sum"],
|
|
39
|
+
"float": ["sum"],
|
|
40
|
+
"string": ["count"],
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ArrowFixtureHandler(VirtualServerHandler):
|
|
46
|
+
def __init__(self, arrow_table, schema, ipc_bytes=None):
|
|
47
|
+
self.arrow_table = arrow_table
|
|
48
|
+
self.schema = schema
|
|
49
|
+
# `write_table` *drops* zero-row batches, so the only way to serve
|
|
50
|
+
# a stream that carries one is to hand over the bytes.
|
|
51
|
+
self.ipc_bytes = ipc_bytes
|
|
52
|
+
|
|
53
|
+
def get_features(self):
|
|
54
|
+
return FEATURES
|
|
55
|
+
|
|
56
|
+
def get_hosted_tables(self):
|
|
57
|
+
return ["fixture"]
|
|
58
|
+
|
|
59
|
+
def table_schema(self, table_name):
|
|
60
|
+
return self.schema
|
|
61
|
+
|
|
62
|
+
def table_size(self, table_name):
|
|
63
|
+
return self.arrow_table.num_rows
|
|
64
|
+
|
|
65
|
+
def table_make_view(self, table_name, view_name, config):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def view_delete(self, view_name):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def view_get_data(self, view_name, config, schema, viewport, data):
|
|
72
|
+
if self.ipc_bytes is not None:
|
|
73
|
+
data.from_arrow_ipc(self.ipc_bytes)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
buf = io.BytesIO()
|
|
77
|
+
with ipc.new_stream(buf, self.arrow_table.schema) as writer:
|
|
78
|
+
writer.write_table(self.arrow_table)
|
|
79
|
+
|
|
80
|
+
data.from_arrow_ipc(buf.getvalue())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SetColFixtureHandler(VirtualServerHandler):
|
|
84
|
+
def __init__(self, row_path, schema):
|
|
85
|
+
self.row_path = row_path
|
|
86
|
+
self.schema = schema
|
|
87
|
+
|
|
88
|
+
def get_features(self):
|
|
89
|
+
return FEATURES
|
|
90
|
+
|
|
91
|
+
def get_hosted_tables(self):
|
|
92
|
+
return ["fixture"]
|
|
93
|
+
|
|
94
|
+
def table_schema(self, table_name):
|
|
95
|
+
return self.schema
|
|
96
|
+
|
|
97
|
+
def table_size(self, table_name):
|
|
98
|
+
return len(self.row_path)
|
|
99
|
+
|
|
100
|
+
def table_make_view(self, table_name, view_name, config):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
def view_delete(self, view_name):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def view_get_data(self, view_name, config, schema, viewport, data):
|
|
107
|
+
for row_idx, value in enumerate(self.row_path):
|
|
108
|
+
data.set_col("string", "__ROW_PATH_0__", row_idx, value, 0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def make_client(handler):
|
|
112
|
+
session = perspective.VirtualServer(handler)
|
|
113
|
+
|
|
114
|
+
def handle_request(msg):
|
|
115
|
+
handle_response(session.handle_request(msg))
|
|
116
|
+
|
|
117
|
+
def handle_response(msg):
|
|
118
|
+
client.handle_response(msg)
|
|
119
|
+
|
|
120
|
+
client = Client(handle_request)
|
|
121
|
+
return client
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def arrow_client(arrow_table, schema=None, ipc_bytes=None):
|
|
125
|
+
if schema is None:
|
|
126
|
+
schema = {
|
|
127
|
+
name: "string"
|
|
128
|
+
for name in arrow_table.column_names
|
|
129
|
+
if not name.startswith("__")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return make_client(ArrowFixtureHandler(arrow_table, schema, ipc_bytes))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def round_trip(arrow_table, schema=None, **config):
|
|
136
|
+
table = arrow_client(arrow_table, schema).open_table("fixture")
|
|
137
|
+
view = table.view(**config)
|
|
138
|
+
result = view.to_columns()
|
|
139
|
+
view.delete()
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def dictionary(values, indices, index_type, value_type=pa.utf8()):
|
|
144
|
+
return pa.DictionaryArray.from_arrays(
|
|
145
|
+
pa.array(indices, type=index_type),
|
|
146
|
+
pa.array(values, type=value_type),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestCoerceSmallIntegers:
|
|
151
|
+
def test_coerce_int8(self):
|
|
152
|
+
table = pa.table({"col": pa.array([-1, 127, None], type=pa.int8())})
|
|
153
|
+
result = round_trip(table, {"col": "integer"})
|
|
154
|
+
assert result["col"] == [-1, 127, None]
|
|
155
|
+
|
|
156
|
+
def test_coerce_int16(self):
|
|
157
|
+
table = pa.table({"col": pa.array([-300, 32000, None], type=pa.int16())})
|
|
158
|
+
result = round_trip(table, {"col": "integer"})
|
|
159
|
+
assert result["col"] == [-300, 32000, None]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestCoerceUnsignedIntegers:
|
|
163
|
+
def test_coerce_uint8(self):
|
|
164
|
+
table = pa.table({"col": pa.array([0, 255, None], type=pa.uint8())})
|
|
165
|
+
result = round_trip(table, {"col": "integer"})
|
|
166
|
+
assert result["col"] == [0, 255, None]
|
|
167
|
+
|
|
168
|
+
def test_coerce_uint16(self):
|
|
169
|
+
table = pa.table({"col": pa.array([0, 65535, None], type=pa.uint16())})
|
|
170
|
+
result = round_trip(table, {"col": "integer"})
|
|
171
|
+
assert result["col"] == [0, 65535, None]
|
|
172
|
+
|
|
173
|
+
def test_coerce_uint32(self):
|
|
174
|
+
table = pa.table({"col": pa.array([0, 4_294_967_295, None], type=pa.uint32())})
|
|
175
|
+
result = round_trip(table, {"col": "float"})
|
|
176
|
+
assert result["col"] == [0.0, 4_294_967_295.0, None]
|
|
177
|
+
|
|
178
|
+
def test_coerce_uint64(self):
|
|
179
|
+
table = pa.table({"col": pa.array([0, 1 << 53, None], type=pa.uint64())})
|
|
180
|
+
result = round_trip(table, {"col": "float"})
|
|
181
|
+
assert result["col"] == [0.0, 9_007_199_254_740_992.0, None]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestCoerceFloats:
|
|
185
|
+
def test_coerce_float32(self):
|
|
186
|
+
table = pa.table({"col": pa.array([3.14, -0.0, None], type=pa.float32())})
|
|
187
|
+
result = round_trip(table, {"col": "float"})
|
|
188
|
+
assert result["col"][0] == pytest.approx(3.14, abs=0.001)
|
|
189
|
+
assert result["col"][1] == 0.0
|
|
190
|
+
assert result["col"][2] is None
|
|
191
|
+
|
|
192
|
+
def test_coerce_float16(self):
|
|
193
|
+
table = pa.table({"col": pa.array([1.5, -2.0, None], type=pa.float16())})
|
|
194
|
+
result = round_trip(table, {"col": "float"})
|
|
195
|
+
assert result["col"] == [1.5, -2.0, None]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TestCoerceDecimal:
|
|
199
|
+
def test_coerce_decimal128(self):
|
|
200
|
+
table = pa.table(
|
|
201
|
+
{
|
|
202
|
+
"col": pa.array(
|
|
203
|
+
[Decimal("1.234"), Decimal("-5.678"), None],
|
|
204
|
+
type=pa.decimal128(18, 3),
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
result = round_trip(table, {"col": "float"})
|
|
209
|
+
assert result["col"][0] == pytest.approx(1.234)
|
|
210
|
+
assert result["col"][1] == pytest.approx(-5.678)
|
|
211
|
+
assert result["col"][2] is None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestCoerceDates:
|
|
215
|
+
def test_coerce_date64(self):
|
|
216
|
+
day = 19738
|
|
217
|
+
table = pa.table({"col": pa.array([day * 86_400_000, None], type=pa.date64())})
|
|
218
|
+
result = round_trip(table, {"col": "date"})
|
|
219
|
+
assert result["col"] == [day * 86_400_000, None]
|
|
220
|
+
|
|
221
|
+
def test_coerce_date32(self):
|
|
222
|
+
day = 19738
|
|
223
|
+
table = pa.table({"col": pa.array([day, None], type=pa.date32())})
|
|
224
|
+
result = round_trip(table, {"col": "date"})
|
|
225
|
+
assert result["col"] == [day * 86_400_000, None]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestCoerceTimes:
|
|
229
|
+
def test_coerce_time32_seconds(self):
|
|
230
|
+
table = pa.table({"col": pa.array([3661, None], type=pa.time32("s"))})
|
|
231
|
+
result = round_trip(table, {"col": "datetime"})
|
|
232
|
+
assert result["col"] == [3_661_000, None]
|
|
233
|
+
|
|
234
|
+
def test_coerce_time32_millis(self):
|
|
235
|
+
table = pa.table({"col": pa.array([3_661_000, None], type=pa.time32("ms"))})
|
|
236
|
+
result = round_trip(table, {"col": "datetime"})
|
|
237
|
+
assert result["col"] == [3_661_000, None]
|
|
238
|
+
|
|
239
|
+
def test_coerce_time64_micros(self):
|
|
240
|
+
table = pa.table({"col": pa.array([3_661_000_000, None], type=pa.time64("us"))})
|
|
241
|
+
result = round_trip(table, {"col": "datetime"})
|
|
242
|
+
assert result["col"] == [3_661_000, None]
|
|
243
|
+
|
|
244
|
+
def test_coerce_time64_nanos(self):
|
|
245
|
+
table = pa.table(
|
|
246
|
+
{"col": pa.array([3_661_000_000_000, None], type=pa.time64("ns"))}
|
|
247
|
+
)
|
|
248
|
+
result = round_trip(table, {"col": "datetime"})
|
|
249
|
+
assert result["col"] == [3_661_000, None]
|
|
250
|
+
|
|
251
|
+
@pytest.mark.parametrize(
|
|
252
|
+
"unit,value",
|
|
253
|
+
[
|
|
254
|
+
("s", 1_700_000_000),
|
|
255
|
+
("ms", 1_700_000_000_000),
|
|
256
|
+
("us", 1_700_000_000_000_000),
|
|
257
|
+
("ns", 1_700_000_000_000_000_000),
|
|
258
|
+
],
|
|
259
|
+
)
|
|
260
|
+
def test_coerce_timestamp_with_timezone(self, unit, value):
|
|
261
|
+
table = pa.table(
|
|
262
|
+
{"col": pa.array([value, None], type=pa.timestamp(unit, tz="UTC"))}
|
|
263
|
+
)
|
|
264
|
+
result = round_trip(table, {"col": "datetime"})
|
|
265
|
+
assert result["col"] == [1_700_000_000_000, None]
|
|
266
|
+
|
|
267
|
+
def test_coerce_timestamp_seconds(self):
|
|
268
|
+
table = pa.table(
|
|
269
|
+
{"col": pa.array([1_700_000_000, None], type=pa.timestamp("s"))}
|
|
270
|
+
)
|
|
271
|
+
result = round_trip(table, {"col": "datetime"})
|
|
272
|
+
assert result["col"] == [1_700_000_000_000, None]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestCoerceStrings:
|
|
276
|
+
def test_coerce_utf8(self):
|
|
277
|
+
table = pa.table({"col": pa.array(["a", None, "c"], type=pa.utf8())})
|
|
278
|
+
result = round_trip(table)
|
|
279
|
+
assert result["col"] == ["a", None, "c"]
|
|
280
|
+
|
|
281
|
+
def test_coerce_large_utf8(self):
|
|
282
|
+
table = pa.table({"col": pa.array(["a", None, "c"], type=pa.large_utf8())})
|
|
283
|
+
result = round_trip(table)
|
|
284
|
+
assert result["col"] == ["a", None, "c"]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class TestFallback:
|
|
288
|
+
def test_fallback_fixed_size_binary(self):
|
|
289
|
+
# No canonical mapping - lossy but total, and warned about.
|
|
290
|
+
table = pa.table(
|
|
291
|
+
{"col": pa.array([b"ab", b"cd", None], type=pa.binary(2))}
|
|
292
|
+
)
|
|
293
|
+
result = round_trip(table)
|
|
294
|
+
assert isinstance(result["col"][0], str)
|
|
295
|
+
assert isinstance(result["col"][1], str)
|
|
296
|
+
assert result["col"][0] != result["col"][1]
|
|
297
|
+
assert result["col"][2] is None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class TestDictionary:
|
|
301
|
+
@pytest.mark.parametrize(
|
|
302
|
+
"index_type",
|
|
303
|
+
[
|
|
304
|
+
pa.int8(),
|
|
305
|
+
pa.int16(),
|
|
306
|
+
pa.int32(),
|
|
307
|
+
pa.int64(),
|
|
308
|
+
pa.uint8(),
|
|
309
|
+
pa.uint16(),
|
|
310
|
+
pa.uint32(),
|
|
311
|
+
pa.uint64(),
|
|
312
|
+
],
|
|
313
|
+
)
|
|
314
|
+
@pytest.mark.parametrize("value_type", [pa.utf8(), pa.large_utf8()])
|
|
315
|
+
def test_dictionary_keys_and_values(self, index_type, value_type):
|
|
316
|
+
col = dictionary(
|
|
317
|
+
["alpha", "beta"], [0, 1, 0, None], index_type, value_type
|
|
318
|
+
)
|
|
319
|
+
result = round_trip(pa.table({"col": col}))
|
|
320
|
+
assert result["col"] == ["alpha", "beta", "alpha", None]
|
|
321
|
+
|
|
322
|
+
def test_dictionary_null_value_slot(self):
|
|
323
|
+
col = pa.DictionaryArray.from_arrays(
|
|
324
|
+
pa.array([0, 1], type=pa.int8()),
|
|
325
|
+
pa.array(["alpha", None], type=pa.utf8()),
|
|
326
|
+
)
|
|
327
|
+
result = round_trip(pa.table({"col": col}))
|
|
328
|
+
assert result["col"] == ["alpha", None]
|
|
329
|
+
|
|
330
|
+
def test_dictionary_of_integers(self):
|
|
331
|
+
col = pa.DictionaryArray.from_arrays(
|
|
332
|
+
pa.array([0, 1, 0], type=pa.uint8()),
|
|
333
|
+
pa.array([-7, 42], type=pa.int16()),
|
|
334
|
+
)
|
|
335
|
+
result = round_trip(pa.table({"col": col}), {"col": "integer"})
|
|
336
|
+
assert result["col"] == [-7, 42, -7]
|
|
337
|
+
|
|
338
|
+
def test_dictionary_group_by(self):
|
|
339
|
+
table = pa.table(
|
|
340
|
+
{
|
|
341
|
+
"__GROUPING_ID__": pa.array([1, 0, 0], type=pa.uint64()),
|
|
342
|
+
"__ROW_PATH_0__": dictionary(
|
|
343
|
+
["alpha", "beta"], [None, 0, 1], pa.uint8()
|
|
344
|
+
),
|
|
345
|
+
"Sales": pa.array([3.0, 1.0, 2.0], type=pa.float64()),
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
result = round_trip(
|
|
350
|
+
table,
|
|
351
|
+
{"Region": "string", "Sales": "float"},
|
|
352
|
+
group_by=["Region"],
|
|
353
|
+
columns=["Sales"],
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
assert result["__ROW_PATH__"] == [[], ["alpha"], ["beta"]]
|
|
357
|
+
assert result["Sales"] == [3.0, 1.0, 2.0]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class TestGroupingId:
|
|
361
|
+
def test_unsigned_grouping_id(self):
|
|
362
|
+
table = pa.table(
|
|
363
|
+
{
|
|
364
|
+
"__GROUPING_ID__": pa.array([1, 0, 0], type=pa.uint64()),
|
|
365
|
+
"__ROW_PATH_0__": pa.array([None, "alpha", "beta"], type=pa.utf8()),
|
|
366
|
+
"Sales": pa.array([3.0, 1.0, 2.0], type=pa.float64()),
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
result = round_trip(
|
|
371
|
+
table,
|
|
372
|
+
{"Region": "string", "Sales": "float"},
|
|
373
|
+
group_by=["Region"],
|
|
374
|
+
columns=["Sales"],
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
assert result["__ROW_PATH__"] == [[], ["alpha"], ["beta"]]
|
|
378
|
+
|
|
379
|
+
def test_decimal_row_path(self):
|
|
380
|
+
table = pa.table(
|
|
381
|
+
{
|
|
382
|
+
"__GROUPING_ID__": pa.array([1, 0], type=pa.int64()),
|
|
383
|
+
"__ROW_PATH_0__": pa.array(
|
|
384
|
+
[None, Decimal("1.500")], type=pa.decimal128(18, 3)
|
|
385
|
+
),
|
|
386
|
+
"Sales": pa.array([3.0, 3.0], type=pa.float64()),
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
result = round_trip(
|
|
391
|
+
table,
|
|
392
|
+
{"Price": "float", "Sales": "float"},
|
|
393
|
+
group_by=["Price"],
|
|
394
|
+
columns=["Sales"],
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
assert result["__ROW_PATH__"] == [[], [1.5]]
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class TestEmpty:
|
|
401
|
+
def test_zero_row_batch(self):
|
|
402
|
+
# One batch, no rows - written explicitly, since `write_table`
|
|
403
|
+
# would drop it.
|
|
404
|
+
schema = pa.schema([("col", pa.int32())])
|
|
405
|
+
buf = io.BytesIO()
|
|
406
|
+
writer = ipc.RecordBatchStreamWriter(buf, schema)
|
|
407
|
+
writer.write_batch(pa.record_batch([[]], schema=schema))
|
|
408
|
+
writer.close()
|
|
409
|
+
|
|
410
|
+
table = pa.Table.from_batches([], schema)
|
|
411
|
+
client = arrow_client(table, {"col": "integer"}, ipc_bytes=buf.getvalue())
|
|
412
|
+
view = client.open_table("fixture").view()
|
|
413
|
+
assert view.to_columns()["col"] == []
|
|
414
|
+
view.delete()
|
|
415
|
+
|
|
416
|
+
def test_no_batches_at_all(self):
|
|
417
|
+
schema = pa.schema([("col", pa.int32())])
|
|
418
|
+
table = pa.Table.from_batches([], schema)
|
|
419
|
+
result = round_trip(table, {"col": "integer"})
|
|
420
|
+
assert result["col"] == []
|
|
421
|
+
|
|
422
|
+
def test_row_path_only_batch(self):
|
|
423
|
+
table = pa.table(
|
|
424
|
+
{
|
|
425
|
+
"__GROUPING_ID__": pa.array([1, 0, 0], type=pa.int64()),
|
|
426
|
+
"__ROW_PATH_0__": pa.array([None, "alpha", "beta"], type=pa.utf8()),
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
client = arrow_client(table, {"Region": "string"})
|
|
431
|
+
view = client.open_table("fixture").view(group_by=["Region"], columns=[])
|
|
432
|
+
assert view.to_json() == [
|
|
433
|
+
{"__ROW_PATH__": []},
|
|
434
|
+
{"__ROW_PATH__": ["alpha"]},
|
|
435
|
+
{"__ROW_PATH__": ["beta"]},
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
view.delete()
|
|
439
|
+
|
|
440
|
+
def test_metadata_only_batch_preserves_row_count(self):
|
|
441
|
+
table = pa.table({"__GROUPING_ID__": pa.array([0, 0, 0], type=pa.int64())})
|
|
442
|
+
client = arrow_client(table, {"Region": "string"})
|
|
443
|
+
view = client.open_table("fixture").view(columns=[])
|
|
444
|
+
assert view.to_json() == [{}, {}, {}]
|
|
445
|
+
view.delete()
|
|
446
|
+
|
|
447
|
+
def test_set_col_row_path_only(self):
|
|
448
|
+
handler = SetColFixtureHandler(["alpha", "beta"], {"Region": "string"})
|
|
449
|
+
view = make_client(handler).open_table("fixture").view(
|
|
450
|
+
group_by=["Region"], columns=[]
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
assert view.to_json() == [
|
|
454
|
+
{"__ROW_PATH__": ["alpha"]},
|
|
455
|
+
{"__ROW_PATH__": ["beta"]},
|
|
456
|
+
]
|
|
457
|
+
|
|
458
|
+
view.delete()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class TestNotConstructible:
|
|
462
|
+
def test_virtual_data_slice_is_not_constructible(self):
|
|
463
|
+
with pytest.raises(TypeError):
|
|
464
|
+
perspective.VirtualDataSlice()
|