from datetime import date, datetime
import perspective
from pytest import mark, raises
import pytest
import perspective as psp
client = psp.Server().new_local_client()
Table = client.table
class TestTable:
@mark.skip
def test_empty_table(self):
tbl = Table([])
assert tbl.size() == 0
def test_table_not_iterable(self):
data = {"a": 1}
with raises(TypeError):
Table(data)
def test_table_csv(self):
data = "x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false"
tbl = Table(data)
assert tbl.schema() == {"x": "integer", "y": "string", "z": "boolean"}
view = tbl.view()
assert view.to_columns() == {
"x": [1, 2, 3, 4],
"y": ["a", "b", "c", "d"],
"z": [True, False, True, False],
}
def test_table_csv_with_nulls(self):
tbl = Table("x,y\n1,")
assert tbl.schema() == {"x": "integer", "y": "string"}
view = tbl.view()
assert view.to_columns() == {"x": [1], "y": [None]}
def test_table_csv_with_nulls_updated(self):
tbl = Table("x,y\n1,", index="x")
assert tbl.schema() == {"x": "integer", "y": "string"}
view = tbl.view()
assert view.to_columns() == {"x": [1], "y": [None]}
tbl.update("x,y\n1,abc\n2,123")
assert view.to_columns() == {"x": [1, 2], "y": ["abc", "123"]}
def test_table_correct_csv_nan_end(self):
tbl = Table("string,integer\n,1\n,2\nabc,3")
assert tbl.schema() == {"string": "string", "integer": "integer"}
assert tbl.size() == 3
assert tbl.view().to_columns() == {
"string": ["", "", "abc"],
"integer": [1, 2, 3],
}
def test_table_correct_csv_nan_intermittent(self):
tbl = Table("string,float\nabc,\n,2.5\nghi,")
assert tbl.schema() == {"string": "string", "float": "float"}
assert tbl.size() == 3
assert tbl.view().to_columns() == {
"string": ["abc", "", "ghi"],
"float": [None, 2.5, None],
}
def test_table_records_from_string_with_format_override(self):
data = '{"x": [1,2,3], "y": [4,5,6]}'
tbl = Table(data, format="columns")
view = tbl.view()
assert view.to_columns() == {
"x": [1, 2, 3],
"y": [4, 5, 6],
}
def test_table_string_column_with_nulls_update_and_filter(self):
tbl = Table(
[
{"a": "1", "b": 2, "c": "3"},
{"a": "2", "b": 3, "c": "4"},
{"a": "3", "b": 3, "c": None},
],
index="a",
)
view = tbl.view(filter=[["c", "==", "4"]])
records = view.to_records()
tbl.update([{"a": "4", "b": 10}])
assert records == view.to_records()
def test_table_int(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "integer", "b": "integer"}
def test_table_int_column_names(self):
data = {"a": [1, 2, 3], 0: [4, 5, 6]}
Table(data)
def test_table_nones(self):
none_data = [{"a": 1, "b": None}, {"a": None, "b": 2}]
tbl = Table(none_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "integer", "b": "integer"}
def test_table_bool(self):
bool_data = [{"a": True, "b": False}, {"a": True, "b": True}]
tbl = Table(bool_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "boolean", "b": "boolean"}
def test_table_bool_str(self):
bool_data = [{"a": "True", "b": "False"}, {"a": "True", "b": "True"}]
tbl = Table(bool_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "boolean", "b": "boolean"}
assert tbl.view().to_columns() == {"a": [True, True], "b": [False, True]}
def test_table_float(self):
float_data = [{"a": 1.5, "b": 2.5}, {"a": 3.2, "b": 3.1}]
tbl = Table(float_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "float", "b": "float"}
def test_table_str(self):
str_data = [{"a": "b", "b": "b"}, {"a": "3", "b": "3"}]
tbl = Table(str_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "string", "b": "string"}
def test_table_str_with_escape(self):
str_data = [
{"a": 'abc"def"', "b": 'abc"def"'},
{"a": "abc'def'", "b": "abc'def'"},
]
tbl = Table(str_data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "string", "b": "string"}
assert tbl.view().to_records() == str_data
def test_table_str_unicode(self):
str_data = [{"a": "ȀȁȀȃȀȁȀȃȀȁȀȃȀȁȀȃ", "b": "ЖДфйЖДфйЖДфйЖДфй"}]
tbl = Table(str_data)
assert tbl.size() == 1
assert tbl.schema() == {"a": "string", "b": "string"}
assert tbl.view().to_records() == str_data
def test_table_date(self):
str_data = [{"a": date.today(), "b": date.today()}]
tbl = Table(str_data)
assert tbl.size() == 1
assert tbl.schema() == {"a": "date", "b": "date"}
def test_table_datetime(self):
str_data = [{"a": datetime.now(), "b": datetime.now()}]
tbl = Table(str_data)
assert tbl.size() == 1
assert tbl.schema() == {"a": "datetime", "b": "datetime"}
def test_table_columnar(self):
data = {"a": [1, 2, 3], "b": [4, 5, 6]}
tbl = Table(data)
assert tbl.columns() == ["a", "b"]
assert tbl.size() == 3
assert tbl.schema() == {"a": "integer", "b": "integer"}
def test_table_columnar_mixed_length(self):
data = [{"a": 1.5, "b": 2.5}, {"a": 3.2}]
tbl = Table(data)
assert tbl.size() == 2
assert tbl.schema() == {"a": "float", "b": "float"}
assert tbl.view().to_records() == [{"a": 1.5, "b": 2.5}, {"a": 3.2, "b": None}]
def test_table_schema(self):
data = {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_readable_string_schema(self):
data = {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_output_readable_schema(self):
data = {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_mixed_schema(self):
data = {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_output_string_schema(self):
data = {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_symmetric_schema(self):
data = {
"a": [1, 2, 3],
"b": [1.5, 2.5, 3.5],
"c": ["a", "b", "c"],
"d": [True, False, True],
"e": [date.today(), date.today(), date.today()],
"f": [datetime.now(), datetime.now(), datetime.now()],
}
tbl = Table(data)
schema = tbl.schema()
assert schema == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl2 = Table(schema)
assert tbl2.schema() == schema
def test_table_symmetric_string_schema(self):
data = {
"a": [1, 2, 3],
"b": [1.5, 2.5, 3.5],
"c": ["a", "b", "c"],
"d": [True, False, True],
"e": [date.today(), date.today(), date.today()],
"f": [datetime.now(), datetime.now(), datetime.now()],
}
tbl = Table(data)
schema = tbl.schema()
assert schema == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
tbl2 = Table(schema)
assert tbl2.schema() == schema
def test_table_python_schema(self):
data = {
"a": int,
"b": float,
"c": str,
"d": bool,
"e": date,
"f": datetime,
}
tbl = Table(data)
assert tbl.schema() == {
"a": "integer",
"b": "float",
"c": "string",
"d": "boolean",
"e": "date",
"f": "datetime",
}
def test_table_index(self):
data = [{"a": 1, "b": 2}, {"a": 1, "b": 4}]
tbl = Table(data, index="a")
assert tbl.size() == 1
assert tbl.view().to_records() == [{"a": 1, "b": 4}]
def test_table_index_from_schema(self):
data = [{"a": 1, "b": 2}, {"a": 1, "b": 4}]
tbl = Table({"a": "integer", "b": "integer"}, index="a")
assert tbl.size() == 0
tbl.update(data)
assert tbl.view().to_records() == [{"a": 1, "b": 4}]
def test_table_index_int_with_none(self):
tbl = Table({"a": [0, 1, 2, None, None], "b": [4, 3, 2, 1, 0]}, index="a")
assert tbl.view().to_columns() == {
"a": [None, 0, 1, 2],
"b": [0, 4, 3, 2],
}
def test_table_index_float_with_none(self):
tbl = Table({"a": [0.0, 1.5, 2.5, None, None], "b": [4, 3, 2, 1, 0]}, index="a")
assert tbl.view().to_columns() == {
"a": [None, 0, 1.5, 2.5],
"b": [0, 4, 3, 2],
}
def test_table_index_bool_with_none(self):
with raises(perspective.PerspectiveError):
Table({"a": [True, False, None, True], "b": [4, 3, 2, 1]}, index="a")
def test_table_index_date_with_none(self):
tbl = Table(
{
"a": [date(2019, 7, 11), None, date(2019, 3, 12), date(2011, 3, 10)],
"b": [4, 3, 2, 1],
},
index="a",
)
def ts(x):
return int(datetime.timestamp(x) * 1000)
assert tbl.view().to_columns() == {
"a": [
None,
ts(datetime(2011, 3, 10)),
ts(datetime(2019, 3, 12)),
ts(datetime(2019, 7, 11)),
],
"b": [3, 1, 2, 4],
}
def test_table_index_datetime_with_none(self, util):
tbl = Table(
{
"a": [
datetime(2019, 7, 11, 15, 30),
None,
datetime(2019, 7, 11, 12, 10),
datetime(2019, 7, 11, 5, 0),
],
"b": [4, 3, 2, 1],
},
index="a",
)
assert tbl.view().to_columns() == {
"a": [
None,
util.to_timestamp(datetime(2019, 7, 11, 5, 0)),
util.to_timestamp(datetime(2019, 7, 11, 12, 10)),
util.to_timestamp(datetime(2019, 7, 11, 15, 30)),
],
"b": [3, 1, 2, 4],
}
def test_table_index_str_with_none(self):
tbl = Table({"a": ["", "a", None, "b"], "b": [4, 3, 2, 1]}, index="a")
assert tbl.view().to_columns() == {"a": [None, "", "a", "b"], "b": [2, 4, 3, 1]}
def test_table_get_index(self):
tbl = Table({"a": ["", "a", None, "b"], "b": [4, 3, 2, 1]}, index="a")
assert tbl.get_index() == "a"
def test_table_get_index_none(self):
tbl = Table({"a": ["", "a", None, "b"], "b": [4, 3, 2, 1]})
assert tbl.get_index() is None
def test_table_limit(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data, limit=1)
assert tbl.size() == 1
assert tbl.view().to_records() == [{"a": 3, "b": 4}]
def test_table_get_limit(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data, limit=1)
assert tbl.get_limit() == 1
def test_table_get_limit_none(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data)
assert tbl.get_limit() is None
@pytest.mark.skip
def test_table_get_num_views(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data)
assert tbl.get_num_views() == 0
v1 = tbl.view()
v2 = tbl.view()
v3 = tbl.view()
assert tbl.get_num_views() == 3
v1.delete()
v2.delete()
assert tbl.get_num_views() == 1
failed = False
try:
tbl.delete()
except perspective.PerspectiveError:
failed = True
assert failed
v3.delete()
assert tbl.get_num_views() == 0
tbl.delete()
def test_table_clear(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
tbl = Table(data)
tbl.clear()
view = tbl.view()
assert view.to_records() == []
def test_table_replace(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
data2 = [{"a": 3, "b": 4}, {"a": 1, "b": 2}]
tbl = Table(data)
tbl.replace(data2)
assert tbl.view().to_records() == data2
def test_table_replace_views_should_preserve(self):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
data2 = [{"a": 3, "b": 4}, {"a": 1, "b": 2}]
tbl = Table(data)
view = tbl.view(group_by=["a"], split_by=["b"])
first = view.to_records()
tbl.replace(data2)
assert view.to_records() == first
def test_table_replace_should_fire_on_update(self, sentinel):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
data2 = [{"a": 3, "b": 4}, {"a": 1, "b": 2}]
tbl = Table(data)
view = tbl.view()
s = sentinel(False)
def updater(port_id):
s.set(True)
view.on_update(updater)
tbl.replace(data2)
tbl.size()
assert s.get()
def test_table_replace_should_fire_on_update_with_delta(self, sentinel):
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
data2 = [{"a": 3, "b": 4}, {"a": 1, "b": 2}]
tbl = Table(data)
view = tbl.view()
s = sentinel(False)
def updater(port_id, delta):
table2 = Table(delta)
assert table2.view().to_records() == data2
s.set(True)
view.on_update(updater, mode="row")
tbl.replace(data2)
tbl.size()
assert s.get() is True
def test_float32_table_construction(self):
float_data = [{"a": 1.5, "b": 2.5}, {"a": 3.2, "b": 3.1}]
tbl = Table(float_data, index="a")
assert tbl.size() == 2
assert tbl.schema() == {"a": "float", "b": "float"}
if __name__ == "__main__":
import pytest
pytest.main(["-vv", "-s", __file__])