chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
@@ -0,0 +1,11 @@
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
# ┃ This file is part of the Perspective library, distributed under the terms ┃
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
@@ -0,0 +1,278 @@
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
# ┃ This file is part of the Perspective library, distributed under the terms ┃
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
from functools import partial
from types import MethodType
import numpy as np
from perspective.widget import PerspectiveWidget
from pytest import raises
import pytest
import perspective as psp
client = psp.Server().new_local_client()
Table = client.table
pytest.skip(allow_module_level=True)
def mock_post(self, msg, msg_id=None, assert_msg=None):
"""Mock the widget's `post()` method so we can introspect the contents."""
assert msg == assert_msg
class TestWidget:
def test_widget(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data, plugin="X Bar")
assert widget.plugin == "X Bar"
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {}},
}
def test_widget_indexed(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data, plugin="X Bar", index="a")
assert widget.plugin == "X Bar"
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {"index": "a"}},
}
def test_widget_no_data(self):
widget = PerspectiveWidget(None, plugin="X Bar", group_by=["a"])
assert widget.plugin == "X Bar"
assert widget.group_by == ["a"]
def test_widget_schema(self):
schema = {
"a": "integer",
"b": "float",
"c": "boolean",
"d": "date",
"e": "datetime",
"f": "string",
}
widget = PerspectiveWidget(schema)
assert widget.table.schema() == schema
def test_widget_schema_with_index(self):
widget = PerspectiveWidget({"a": "integer"}, index="a")
assert widget.table.get_index() == "a"
def test_widget_schema_with_limit(self):
widget = PerspectiveWidget({"a": "integer"}, limit=5)
assert widget.table.get_limit() == 5
def test_widget_no_data_with_index(self):
# should fail
with raises(TypeError):
PerspectiveWidget(None, index="a")
def test_widget_no_data_with_limit(self):
# should fail
with raises(TypeError):
PerspectiveWidget(None, limit=5)
def test_widget_eventual_data(self):
table = Table({"a": np.arange(0, 50)})
widget = PerspectiveWidget(None, plugin="X Bar")
assert widget.plugin == "X Bar"
with raises(TypeError):
widget._make_load_message()
widget.load(table)
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {}},
}
def test_widget_eventual_data_server(self):
widget = PerspectiveWidget(None, plugin="X Bar", server=True)
assert widget.plugin == "X Bar"
widget.load({"a": np.arange(0, 50)}, index="a")
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {
"table_name": widget.table_name,
},
}
def test_widget_eventual_data_indexed(self):
widget = PerspectiveWidget(None, plugin="X Bar")
assert widget.plugin == "X Bar"
widget.load({"a": np.arange(0, 50)}, index="a")
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {"index": "a"}},
}
def test_widget_eventual_table_indexed(self):
table = Table({"a": np.arange(0, 50)}, index="a")
widget = PerspectiveWidget(None, plugin="X Bar")
assert widget.plugin == "X Bar"
widget.load(table)
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {"index": "a"}},
}
def test_widget_load_table(self):
table = Table({"a": np.arange(0, 50)})
widget = PerspectiveWidget(table, plugin="X Bar")
assert widget.plugin == "X Bar"
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {}},
}
def test_widget_load_table_indexed(self):
table = Table({"a": np.arange(0, 50)}, index="a")
widget = PerspectiveWidget(table, plugin="X Bar")
assert widget.plugin == "X Bar"
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name, "options": {"index": "a"}},
}
def test_widget_load_table_ignore_limit(self):
table = Table({"a": np.arange(0, 50)})
widget = PerspectiveWidget(table, limit=1)
assert widget.table.size() == 50
def test_widget_pass_index(self):
data = {"a": [1, 2, 3, 1]}
widget = PerspectiveWidget(data, index="a")
assert widget.table.size() == 3
def test_widget_pass_limit(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data, limit=1)
assert widget.table.size() == 1
def test_widget_pass_options_invalid(self):
data = {"a": np.arange(0, 50)}
with raises(TypeError):
PerspectiveWidget(data, index="index", limit=1)
# server mode
def test_widget_load_table_server(self):
table = Table({"a": np.arange(0, 50)})
widget = PerspectiveWidget(table, server=True)
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name},
}
def test_widget_no_data_with_server(self):
# should fail
widget = PerspectiveWidget(None, server=True)
with raises(TypeError):
widget._make_load_message()
def test_widget_eventual_data_with_server(self):
# should fail
widget = PerspectiveWidget(None, server=True)
with raises(TypeError):
widget._make_load_message()
# then succeed
widget.load(Table({"a": np.arange(0, 50)}))
load_msg = widget._make_load_message()
assert load_msg.to_columns() == {
"id": -2,
"type": "table",
"data": {"table_name": widget.table_name},
}
# clear
def test_widget_clear(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data)
widget.clear()
assert widget.table.size() == 0
def test_widget_clear_server(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data, server=True)
widget.clear()
assert widget.table.size() == 0
# replace
def test_widget_replace(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data)
widget.replace({"a": [1]})
assert widget.table.size() == 1
def test_widget_replace_server(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data, server=True)
widget.replace({"a": [1]})
assert widget.table.size() == 1
# delete
def test_widget_delete(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data)
mocked_post = partial(mock_post, assert_msg={"cmd": "delete"})
widget.post = MethodType(mocked_post, widget)
widget.delete()
assert widget.table is None
def test_widget_delete_with_view(self):
data = {"a": np.arange(0, 50)}
widget = PerspectiveWidget(data)
# create a view on the manager
table_name, table = list(widget.manager._tables.items())[0]
make_view_message = {
"id": 1,
"table_name": table_name,
"view_name": "view1",
"cmd": "view",
"config": {"group_by": ["a"]},
}
widget.manager._process(make_view_message, lambda x: True)
assert len(widget.manager._views) == 1
mocked_post = partial(mock_post, assert_msg={"cmd": "delete"})
widget.post = MethodType(mocked_post, widget)
widget.delete()
assert widget.table is None
@@ -0,0 +1,453 @@
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
# ┃ This file is part of the Perspective library, distributed under the terms ┃
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import pandas as pd
import numpy as np
import pytest
from perspective.widget import PerspectiveWidget
import perspective as psp
client = psp.Server().new_local_client()
Table = client.table
pytest.skip(allow_module_level=True)
class TestWidgetPandas:
def test_widget_load_table_df(self, superstore):
table = Table(superstore)
widget = PerspectiveWidget(table)
assert widget.table.schema() == {
"index": "integer",
"Country": "string",
"Region": "string",
"Category": "string",
"City": "string",
"Customer ID": "string",
"Discount": "float",
"Order Date": "date",
"Order ID": "string",
"Postal Code": "string",
"Product ID": "string",
"Profit": "float",
"Quantity": "integer",
"Row ID": "integer",
"Sales": "integer",
"Segment": "string",
"Ship Date": "date",
"Ship Mode": "string",
"State": "string",
"Sub-Category": "string",
}
assert sorted(widget.columns) == sorted(
[
"index",
"Category",
"City",
"Country",
"Customer ID",
"Discount",
"Order Date",
"Order ID",
"Postal Code",
"Product ID",
"Profit",
"Quantity",
"Region",
"Row ID",
"Sales",
"Segment",
"Ship Date",
"Ship Mode",
"State",
"Sub-Category",
]
)
view = widget.table.view()
assert view.num_rows() == len(superstore)
assert view.num_columns() == len(superstore.columns) + 1 # index
def test_widget_load_data_df(self, superstore):
widget = PerspectiveWidget(superstore)
assert sorted(widget.columns) == sorted(
[
"index",
"Category",
"City",
"Country",
"Customer ID",
"Discount",
"Order Date",
"Order ID",
"Postal Code",
"Product ID",
"Profit",
"Quantity",
"Region",
"Row ID",
"Sales",
"Segment",
"Ship Date",
"Ship Mode",
"State",
"Sub-Category",
]
)
view = widget.table.view()
assert view.num_rows() == len(superstore)
assert view.num_columns() == 20
def test_widget_load_series(self, superstore):
series = pd.Series(superstore["Profit"].values, name="profit")
widget = PerspectiveWidget(series)
assert widget.table.schema() == {"index": "integer", "profit": "float"}
assert sorted(widget.columns) == sorted(["index", "profit"])
view = widget.table.view()
assert view.num_rows() == len(superstore)
assert view.num_columns() == 2
def test_widget_load_pivot_table(self, superstore):
pivot_table = pd.pivot_table(
superstore,
values="Discount",
index=["Country", "Region"],
columns=["Category", "Segment"],
)
widget = PerspectiveWidget(pivot_table)
assert widget.group_by == ["Country", "Region"]
assert widget.split_by == ["Category", "Segment"]
assert widget.columns == ["value"]
# table should host flattened data
view = widget.table.view()
assert view.num_rows() == 60
assert view.num_columns() == 6
def test_widget_load_pivot_table_with_user_pivots(self, superstore):
pivot_table = pd.pivot_table(
superstore,
values="Discount",
index=["Country", "Region"],
columns="Category",
)
widget = PerspectiveWidget(pivot_table, group_by=["Category", "Segment"])
assert widget.group_by == ["Category", "Segment"]
assert widget.split_by == []
assert widget.columns == [
"index",
"Country",
"Region",
"Financials",
"Industrials",
"Technology",
]
# table should host flattened data
view = widget.table.view()
assert view.num_rows() == 5
assert view.num_columns() == 6
def test_widget_load_group_by(self, superstore):
df_pivoted = superstore.set_index(["Country", "Region"])
widget = PerspectiveWidget(df_pivoted)
assert widget.group_by == ["Country", "Region"]
assert widget.split_by == []
assert sorted(widget.columns) == sorted(
[
"index",
"Category",
"Country",
"City",
"Customer ID",
"Discount",
"Order Date",
"Order ID",
"Postal Code",
"Product ID",
"Profit",
"Quantity",
"Region",
"Row ID",
"Sales",
"Segment",
"Ship Date",
"Ship Mode",
"State",
"Sub-Category",
]
)
assert widget.table.size() == 100
view = widget.table.view()
assert view.num_rows() == len(superstore)
assert view.num_columns() == len(superstore.columns) + 1 # index
def test_widget_load_group_by_with_user_pivots(self, superstore):
df_pivoted = superstore.set_index(["Country", "Region"])
widget = PerspectiveWidget(df_pivoted, group_by=["Category", "Segment"])
assert widget.group_by == ["Category", "Segment"]
assert widget.split_by == []
assert sorted(widget.columns) == sorted(
[
"index",
"Category",
"Country",
"City",
"Customer ID",
"Discount",
"Order Date",
"Order ID",
"Postal Code",
"Product ID",
"Profit",
"Quantity",
"Region",
"Row ID",
"Sales",
"Segment",
"Ship Date",
"Ship Mode",
"State",
"Sub-Category",
]
)
assert widget.table.size() == 100
view = widget.table.view()
assert view.num_rows() == len(superstore)
assert view.num_columns() == len(superstore.columns) + 1 # index
def test_widget_load_split_by(self, superstore):
arrays = [
np.array(
[
"bar",
"bar",
"bar",
"bar",
"baz",
"baz",
"baz",
"baz",
"foo",
"foo",
"foo",
"foo",
"qux",
"qux",
"qux",
"qux",
]
),
np.array(
[
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
]
),
np.array(
[
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
]
),
]
tuples = list(zip(*arrays))
index = pd.MultiIndex.from_tuples(tuples, names=["first", "second", "third"])
df_both = pd.DataFrame(
np.random.randn(3, 16), index=["A", "B", "C"], columns=index
)
widget = PerspectiveWidget(df_both)
assert widget.columns == ["value"]
assert widget.split_by == ["first", "second", "third"]
assert widget.group_by == ["index"]
def test_widget_load_split_by_preserve_user_settings(self, superstore):
arrays = [
np.array(
[
"bar",
"bar",
"bar",
"bar",
"baz",
"baz",
"baz",
"baz",
"foo",
"foo",
"foo",
"foo",
"qux",
"qux",
"qux",
"qux",
]
),
np.array(
[
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
]
),
np.array(
[
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
]
),
]
tuples = list(zip(*arrays))
index = pd.MultiIndex.from_tuples(tuples, names=["first", "second", "third"])
df_both = pd.DataFrame(
np.random.randn(3, 16), index=["A", "B", "C"], columns=index
)
widget = PerspectiveWidget(df_both, columns=["first", "third"])
assert widget.columns == ["first", "third"]
assert widget.split_by == ["first", "second", "third"]
assert widget.group_by == ["index"]
def test_pivottable_values_index(self, superstore):
arrays = {
"A": [
"bar",
"bar",
"bar",
"bar",
"baz",
"baz",
"baz",
"baz",
"foo",
"foo",
"foo",
"foo",
"qux",
"qux",
"qux",
"qux",
],
"B": [
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
"one",
"one",
"two",
"two",
],
"C": [
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
"X",
"Y",
],
"D": np.arange(16),
}
df = pd.DataFrame(arrays)
df_pivot = df.pivot_table(
values=["D"], index=["A"], columns=["B", "C"], aggfunc={"D": "count"}
)
widget = PerspectiveWidget(df_pivot)
assert widget.columns == ["value"]
assert widget.split_by == ["B", "C"]
assert widget.group_by == ["A"]
def test_pivottable_multi_values(self, superstore):
pt = pd.pivot_table(
superstore,
values=["Discount", "Sales"],
index=["Country", "Region"],
aggfunc={"Discount": "count", "Sales": "sum"},
columns=["State", "Quantity"],
)
widget = PerspectiveWidget(pt)
assert widget.columns == ["Discount", "Sales"]
assert widget.split_by == ["State", "Quantity"]
assert widget.group_by == ["Country", "Region"]