chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,402 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 sys
|
||||
from random import randint, choice
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class CustomObjectStore(object):
|
||||
def __init__(self, value):
|
||||
self._value = value
|
||||
|
||||
def _psp_dtype_(self):
|
||||
return "object"
|
||||
|
||||
def __int__(self):
|
||||
return int(self._value)
|
||||
|
||||
def __repr__(self):
|
||||
return "test" if self._value == 1 else "test{}".format(self._value)
|
||||
|
||||
|
||||
def run():
|
||||
t = CustomObjectStore(1)
|
||||
t2 = CustomObjectStore(2)
|
||||
|
||||
assert sys.getrefcount(t) == 2
|
||||
assert sys.getrefcount(t2) == 2
|
||||
|
||||
data = {"a": [0], "b": [t]}
|
||||
assert sys.getrefcount(t) == 3
|
||||
|
||||
tbl = Table(data, index="a")
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
assert tbl.schema() == {"a": int, "b": object}
|
||||
assert tbl.size() == 1
|
||||
assert tbl.view().to_columns() == {"a": [0], "b": [t]}
|
||||
|
||||
# Count references
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
print("t:", id(t))
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
print(sys.getrefcount(t2), "should be", 2)
|
||||
print("t2:", id(t2))
|
||||
assert sys.getrefcount(t2) == 2
|
||||
|
||||
tbl.update([{"a": i, "b": None} for i in range(1, 6)])
|
||||
|
||||
assert tbl.size() == 6
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
print()
|
||||
tbl.update([data])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
print()
|
||||
tbl.update([data])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
print()
|
||||
tbl.update([data])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
assert sys.getrefcount(t) == 4
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
|
||||
print(sys.getrefcount(t), "should be", 5)
|
||||
assert sys.getrefcount(t) == 5
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
|
||||
print(sys.getrefcount(t), "should be", 5)
|
||||
assert sys.getrefcount(t) == 5
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 3, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
assert sys.getrefcount(t) == 6
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 3, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
assert sys.getrefcount(t) == 6
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 5, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 4for the table
|
||||
print(sys.getrefcount(t), "should be", 7)
|
||||
# assert sys.getrefcount(t) == 7
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
# clear some, overwrite some with same
|
||||
tbl.update([{"a": 0, "b": t}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 4 for the table
|
||||
print(sys.getrefcount(t), "should be", 7)
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 1, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 2, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 4)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 2, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 2, "b": t2}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 4)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 2, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
|
||||
print(sys.getrefcount(t), "should be", 6)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 3, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
|
||||
print(sys.getrefcount(t), "should be", 5)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 3, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
|
||||
print(sys.getrefcount(t), "should be", 5)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 5, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.update([{"a": 5, "b": None}])
|
||||
# 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t), "should be", 4)
|
||||
# 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
|
||||
print(sys.getrefcount(t2), "should be", 3)
|
||||
print(tbl.view().to_columns()["b"])
|
||||
assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
]
|
||||
|
||||
print()
|
||||
tbl.clear()
|
||||
assert tbl.size() == 0
|
||||
assert tbl.view().to_columns() == {}
|
||||
# 1 for `t`, one for `data`, one for argument to sys.getrefcount
|
||||
print(sys.getrefcount(t), "should be", 3)
|
||||
assert sys.getrefcount(t) == 3
|
||||
|
||||
|
||||
def run2():
|
||||
t = CustomObjectStore(1)
|
||||
t_ref_count = 2
|
||||
assert sys.getrefcount(t) == t_ref_count
|
||||
|
||||
indexes = set([0])
|
||||
|
||||
tbl = Table({"a": [0], "b": [t]}, index="a")
|
||||
assert sys.getrefcount(t) == 3
|
||||
t_ref_count += 1
|
||||
|
||||
assert tbl.schema() == {"a": int, "b": object}
|
||||
assert tbl.size() == 1
|
||||
assert tbl.view().to_columns() == {"a": [0], "b": [t]}
|
||||
|
||||
# seed a few to check
|
||||
tbl.remove([1])
|
||||
tbl.remove([1])
|
||||
tbl.remove([1])
|
||||
|
||||
for _ in range(10):
|
||||
pick = randint(1, 2) if indexes else 1
|
||||
if pick == 1:
|
||||
ind = randint(1, 10)
|
||||
while ind in indexes:
|
||||
ind = randint(1, 100)
|
||||
|
||||
print(
|
||||
"adding", ind, "refcount", t_ref_count, "should be", sys.getrefcount(t)
|
||||
)
|
||||
tbl.update({"a": [ind], "b": [t]})
|
||||
t_ref_count += 1
|
||||
indexes.add(ind)
|
||||
assert sys.getrefcount(t) == t_ref_count
|
||||
|
||||
else:
|
||||
ind = choice(list(indexes))
|
||||
indexes.remove(ind)
|
||||
tbl.remove([ind])
|
||||
t_ref_count -= 1
|
||||
print(
|
||||
"removing",
|
||||
ind,
|
||||
"refcount",
|
||||
t_ref_count,
|
||||
"should be",
|
||||
sys.getrefcount(t),
|
||||
)
|
||||
assert sys.getrefcount(t) == t_ref_count
|
||||
|
||||
print(t_ref_count)
|
||||
print(tbl.view().to_columns())
|
||||
|
||||
assert sys.getrefcount(t) == t_ref_count
|
||||
|
||||
print()
|
||||
tbl.clear()
|
||||
assert tbl.size() == 0
|
||||
assert tbl.view().to_columns() == {}
|
||||
# 1 for `t`, one for `data`, one for argument to sys.getrefcount
|
||||
print(sys.getrefcount(t), "should be", 2)
|
||||
assert sys.getrefcount(t) == 2
|
||||
@@ -0,0 +1,89 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
|
||||
|
||||
class TestViewColumnPaths(object):
|
||||
def test_column_paths(self, superstore):
|
||||
tbl = client.table(superstore)
|
||||
view = tbl.view()
|
||||
paths = view.column_paths()
|
||||
assert paths == [
|
||||
"index",
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
]
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
def test_column_paths_split_by(self, superstore):
|
||||
tbl = client.table(superstore)
|
||||
view = tbl.view(group_by=["State"], columns=["Sales"], split_by=["Ship Mode"])
|
||||
paths = view.column_paths()
|
||||
assert paths == [
|
||||
"First Class|Sales",
|
||||
"Second Class|Sales",
|
||||
"Standard Class|Sales",
|
||||
]
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
def test_column_paths_split_by_range(self, superstore):
|
||||
tbl = client.table(superstore)
|
||||
view = tbl.view(group_by=["State"], columns=["Sales"], split_by=["Ship Mode"])
|
||||
paths = view.column_paths(start_col=1)
|
||||
assert paths == [
|
||||
"Second Class|Sales",
|
||||
"Standard Class|Sales",
|
||||
]
|
||||
|
||||
view.delete()
|
||||
view = tbl.view(group_by=["State"], columns=["Sales"], split_by=["Ship Mode"])
|
||||
paths = view.column_paths(start_col=1, end_col=2)
|
||||
assert paths == [
|
||||
"Second Class|Sales",
|
||||
"Standard Class|Sales",
|
||||
]
|
||||
|
||||
view.delete()
|
||||
tbl = client.table(superstore)
|
||||
view = tbl.view(group_by=["State"], columns=["Sales"], split_by=["Ship Mode"])
|
||||
paths = view.column_paths(end_col=1)
|
||||
assert paths == [
|
||||
"First Class|Sales",
|
||||
"Second Class|Sales",
|
||||
]
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
@@ -0,0 +1,124 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestDelete(object):
|
||||
# delete
|
||||
|
||||
def test_table_delete(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
tbl.delete()
|
||||
# don't segfault
|
||||
|
||||
def test_table_delete_callback(self, sentinel):
|
||||
s = sentinel(False)
|
||||
|
||||
def callback():
|
||||
s.set(True)
|
||||
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
tbl.on_delete(callback)
|
||||
tbl.delete()
|
||||
assert s.get() is True
|
||||
|
||||
def test_table_delete_with_view(self, sentinel):
|
||||
s = sentinel(False)
|
||||
|
||||
def callback():
|
||||
s.set(True)
|
||||
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
tbl.on_delete(callback)
|
||||
view = tbl.view()
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
assert s.get() is True
|
||||
|
||||
def test_table_delete_multiple_callback(self, sentinel):
|
||||
s1 = sentinel(False)
|
||||
s2 = sentinel(False)
|
||||
|
||||
def callback1():
|
||||
s1.set(True)
|
||||
|
||||
def callback2():
|
||||
s2.set(True)
|
||||
|
||||
tbl = Table([{"a": 1}])
|
||||
tbl.on_delete(callback1)
|
||||
tbl.on_delete(callback2)
|
||||
|
||||
tbl.delete()
|
||||
|
||||
assert s1.get() is True
|
||||
assert s2.get() is True
|
||||
|
||||
def test_table_remove_delete_callback(self, sentinel):
|
||||
s = sentinel(False)
|
||||
|
||||
def callback():
|
||||
s.set(True)
|
||||
|
||||
tbl = Table([{"a": 1}])
|
||||
callback_id = tbl.on_delete(callback)
|
||||
tbl.remove_delete(callback_id)
|
||||
|
||||
tbl.delete()
|
||||
|
||||
assert s.get() is False
|
||||
|
||||
def test_view_delete_multiple_callback(self, sentinel):
|
||||
s1 = sentinel(False)
|
||||
s2 = sentinel(False)
|
||||
|
||||
def callback1():
|
||||
s1.set(True)
|
||||
|
||||
def callback2():
|
||||
s2.set(True)
|
||||
|
||||
tbl = Table([{"a": 1}])
|
||||
view = tbl.view()
|
||||
|
||||
view.on_delete(callback1)
|
||||
view.on_delete(callback2)
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
assert s1.get() is True
|
||||
assert s2.get() is True
|
||||
|
||||
def test_view_remove_delete_callback(self, sentinel):
|
||||
s = sentinel(False)
|
||||
|
||||
def callback():
|
||||
s.set(True)
|
||||
|
||||
tbl = Table([{"a": 1}])
|
||||
view = tbl.view()
|
||||
|
||||
callback_id = view.on_delete(callback)
|
||||
view.remove_delete(callback_id)
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
assert s.get() is False
|
||||
@@ -0,0 +1,65 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 pytest import raises
|
||||
from perspective import PerspectiveError
|
||||
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestException(object):
|
||||
def test_instantiation_exception_table(self):
|
||||
from perspective import Table
|
||||
with raises(TypeError) as ex:
|
||||
Table()
|
||||
assert "Do not call Table's constructor directly" in str(ex.value)
|
||||
|
||||
def test_instantiation_exception_view(self):
|
||||
from perspective import View
|
||||
with raises(TypeError) as ex:
|
||||
View()
|
||||
assert "Do not call View's constructor directly" in str(ex.value)
|
||||
|
||||
def test_exception_from_core(self):
|
||||
tbl = Table({"a": [1, 2, 3]})
|
||||
|
||||
with raises(PerspectiveError) as ex:
|
||||
# creating view with unknown column should throw
|
||||
tbl.view(group_by=["b"])
|
||||
|
||||
assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
|
||||
|
||||
def test_exception_from_core_catch_generic(self):
|
||||
tbl = Table({"a": [1, 2, 3]})
|
||||
# `PerspectiveCppError` should inherit from `Exception`
|
||||
with raises(Exception) as ex:
|
||||
tbl.view(group_by=["b"])
|
||||
|
||||
assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
|
||||
|
||||
def test_exception_from_core_correct_types(self):
|
||||
tbl = Table({"a": [1, 2, 3]})
|
||||
|
||||
# `PerspectiveError` should be raised from the Python layer
|
||||
with raises(PerspectiveError) as ex:
|
||||
tbl.view()
|
||||
tbl.delete()
|
||||
|
||||
assert str(ex.value) == "Abort(): Cannot delete table with views"
|
||||
|
||||
with raises(PerspectiveError) as ex:
|
||||
tbl.view(group_by=["b"])
|
||||
|
||||
assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
|
||||
@@ -0,0 +1,115 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
|
||||
|
||||
class TestJoin:
|
||||
def test_inner_join_two_tables(self):
|
||||
left = client.table(
|
||||
[{"id": 1, "x": 10}, {"id": 2, "x": 20}, {"id": 3, "x": 30}]
|
||||
)
|
||||
right = client.table(
|
||||
[{"id": 1, "y": "a"}, {"id": 2, "y": "b"}, {"id": 4, "y": "d"}]
|
||||
)
|
||||
joined = client.join(left, right, "id")
|
||||
view = joined.view()
|
||||
json = view.to_json()
|
||||
assert len(json) == 2
|
||||
view.delete()
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
|
||||
def test_join_has_correct_schema(self):
|
||||
left = client.table({"id": "integer", "x": "float"})
|
||||
right = client.table({"id": "integer", "y": "string"})
|
||||
joined = client.join(left, right, "id")
|
||||
schema = joined.schema()
|
||||
assert schema == {"id": "integer", "x": "float", "y": "string"}
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
|
||||
def test_join_reacts_to_left_updates(self):
|
||||
left = client.table(
|
||||
[{"id": 1, "x": 10}, {"id": 2, "x": 20}]
|
||||
)
|
||||
right = client.table(
|
||||
[{"id": 1, "y": "a"}, {"id": 2, "y": "b"}]
|
||||
)
|
||||
joined = client.join(left, right, "id")
|
||||
view = joined.view()
|
||||
left.update([{"id": 1, "x": 99}])
|
||||
json = view.to_json()
|
||||
assert json == [
|
||||
{"id": 1, "x": 10, "y": "a"},
|
||||
{"id": 2, "x": 20, "y": "b"},
|
||||
{"id": 1, "x": 99, "y": "a"},
|
||||
]
|
||||
view.delete()
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
|
||||
def test_left_join(self):
|
||||
left = client.table(
|
||||
[{"id": 1, "x": 10}, {"id": 2, "x": 20}, {"id": 3, "x": 30}]
|
||||
)
|
||||
right = client.table(
|
||||
[{"id": 1, "y": "a"}, {"id": 2, "y": "b"}]
|
||||
)
|
||||
joined = client.join(left, right, "id", "left")
|
||||
view = joined.view()
|
||||
json = view.to_json()
|
||||
assert len(json) == 3
|
||||
view.delete()
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
|
||||
def test_join_by_table_names(self):
|
||||
left = client.table(
|
||||
[{"id": 1, "x": 10}, {"id": 2, "x": 20}],
|
||||
name="left_py",
|
||||
)
|
||||
right = client.table(
|
||||
[{"id": 1, "y": "a"}, {"id": 2, "y": "b"}],
|
||||
name="right_py",
|
||||
)
|
||||
joined = client.join("left_py", "right_py", "id")
|
||||
view = joined.view()
|
||||
json = view.to_json()
|
||||
assert len(json) == 2
|
||||
view.delete()
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
|
||||
def test_join_mixed_table_and_string(self):
|
||||
left = client.table(
|
||||
[{"id": 1, "x": 10}, {"id": 2, "x": 20}]
|
||||
)
|
||||
right = client.table(
|
||||
[{"id": 1, "y": "a"}, {"id": 2, "y": "b"}],
|
||||
name="right_py_mixed",
|
||||
)
|
||||
joined = client.join(left, "right_py_mixed", "id")
|
||||
view = joined.view()
|
||||
json = view.to_json()
|
||||
assert len(json) == 2
|
||||
view.delete()
|
||||
joined.delete()
|
||||
right.delete()
|
||||
left.delete()
|
||||
@@ -0,0 +1,105 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 psutil
|
||||
import os
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestDelete(object):
|
||||
# delete
|
||||
|
||||
def test_table_delete(self):
|
||||
process = psutil.Process(os.getpid())
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
tbl.delete()
|
||||
mem = process.memory_info().rss
|
||||
|
||||
for x in range(10000):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
tbl.delete()
|
||||
|
||||
mem2 = process.memory_info().rss
|
||||
|
||||
# assert 1 < (max2 / max) < 1.01
|
||||
assert (mem2 - mem) < 2000000
|
||||
|
||||
def test_table_delete_with_view(self, sentinel):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
mem = process.memory_info().rss
|
||||
for x in range(10000):
|
||||
view = tbl.view()
|
||||
view.delete()
|
||||
|
||||
tbl.delete()
|
||||
mem2 = process.memory_info().rss
|
||||
assert (mem2 - mem) < 2000000
|
||||
|
||||
|
||||
class TestExpressionVocab(object):
|
||||
# Regression tests for the `t_expression_vocab` leak fixed in PR #3175.
|
||||
# Pre-fix, updates against a view with a string-producing expression
|
||||
# interned each result into a new 4KB vocab page once the prior page
|
||||
# filled, and never deduplicated against older pages. RSS grew without
|
||||
# bound. The fix adds cross-page deduplication; these tests assert the
|
||||
# steady state is flat.
|
||||
|
||||
def test_string_expression_update_no_leak(self):
|
||||
long_literal = "X" * 256
|
||||
tbl = Table({"x": "integer", "c": "string"}, index="x")
|
||||
view = tbl.view(expressions={"e": 'concat("c", \'' + long_literal + "')"})
|
||||
|
||||
for _ in range(100):
|
||||
tbl.update([{"x": 1, "c": "value"}])
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
mem = process.memory_info().rss
|
||||
for _ in range(5000):
|
||||
tbl.update([{"x": 1, "c": "value"}])
|
||||
mem2 = process.memory_info().rss
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
assert (mem2 - mem) < 2000000
|
||||
|
||||
def test_string_expression_update_bounded_vocab_no_leak(self):
|
||||
# Cycles through a small fixed set of distinct values. Exercises the
|
||||
# cross-page `string_exists` lookup the fix relies on: once the
|
||||
# vocabulary is fully populated, subsequent interns must resolve to
|
||||
# existing pointers rather than allocating new ones.
|
||||
values = ["alpha", "bravo", "charlie", "delta", "echo"]
|
||||
tbl = Table({"x": "integer", "c": "string"}, index="x")
|
||||
view = tbl.view(expressions={"e": 'upper("c")'})
|
||||
|
||||
for i in range(100):
|
||||
tbl.update([{"x": 1, "c": values[i % len(values)]}])
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
mem = process.memory_info().rss
|
||||
for i in range(5000):
|
||||
tbl.update([{"x": 1, "c": values[i % len(values)]}])
|
||||
mem2 = process.memory_info().rss
|
||||
|
||||
view.delete()
|
||||
tbl.delete()
|
||||
|
||||
assert (mem2 - mem) < 2000000
|
||||
@@ -0,0 +1,178 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 random
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
data = {"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"], "c": [True, False, True, False]}
|
||||
|
||||
|
||||
class TestPorts(object):
|
||||
def test_make_port_sequential(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
def test_make_port_sequential_and_update(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
for i in range(1, 11):
|
||||
table.update({"a": [i], "b": ["a"], "c": [True]}, port_id=i)
|
||||
|
||||
view = table.view()
|
||||
result = view.to_columns()
|
||||
|
||||
assert result == {
|
||||
"a": [1, 2, 3, 4] + [i for i in range(1, 11)],
|
||||
"b": ["a", "b", "c", "d"] + ["a" for i in range(10)],
|
||||
"c": [True, False, True, False] + [True for i in range(10)],
|
||||
}
|
||||
|
||||
def test_arbitary_port_updates(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
port = random.randint(0, 10)
|
||||
|
||||
table.update(data, port_id=port)
|
||||
|
||||
assert table.size() == 8
|
||||
|
||||
assert table.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4] * 2,
|
||||
"b": ["a", "b", "c", "d"] * 2,
|
||||
"c": [True, False, True, False] * 2,
|
||||
}
|
||||
|
||||
def test_ports_should_only_notify_if_they_have_a_queued_update(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
view = table.view()
|
||||
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
||||
|
||||
def callback(port_id):
|
||||
assert port_id in ports_to_update
|
||||
|
||||
view.on_update(callback)
|
||||
|
||||
for port in ports_to_update:
|
||||
table.update(data, port_id=port)
|
||||
|
||||
def test_ports_should_have_unique_deltas(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
view = table.view()
|
||||
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
||||
unique_data = {
|
||||
port: [{"a": port, "b": str(port), "c": True}] for port in ports_to_update
|
||||
}
|
||||
|
||||
def callback(port_id, delta):
|
||||
assert port_id in ports_to_update
|
||||
_t = Table(delta)
|
||||
_v = _t.view()
|
||||
assert _v.to_records() == unique_data[port]
|
||||
_v.delete()
|
||||
_t.delete()
|
||||
|
||||
view.on_update(callback, mode="row")
|
||||
|
||||
for port in ports_to_update:
|
||||
table.update(unique_data[port], port_id=port)
|
||||
|
||||
def test_ports_should_queue_updates_properly(self):
|
||||
table = Table(data)
|
||||
port_ids = []
|
||||
|
||||
for i in range(10):
|
||||
port_ids.append(table.make_port())
|
||||
|
||||
assert port_ids == list(range(1, 11))
|
||||
|
||||
view = table.view()
|
||||
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
||||
|
||||
def callback(port_id):
|
||||
assert port_id in ports_to_update
|
||||
|
||||
view.on_update(callback)
|
||||
|
||||
for port in ports_to_update:
|
||||
table.update(data, port_id=port)
|
||||
|
||||
def test_ports_multiple_tables_with_different_ports(self):
|
||||
server = Table(data)
|
||||
client = Table(data)
|
||||
|
||||
for i in range(random.randint(5, 15)):
|
||||
# reserve an arbitary number of ports
|
||||
server.make_port()
|
||||
|
||||
# port for client is now far above the ports "ON" the client, as the
|
||||
# client ports will begin creation at 1.
|
||||
server_port_for_client = server.make_port()
|
||||
client_port = client.make_port()
|
||||
|
||||
server_view = server.view()
|
||||
client_view = client.view()
|
||||
|
||||
# when the client updates, check whether the port id matches that
|
||||
# of the server, and complete the test.
|
||||
def client_callback(port_id, delta):
|
||||
if port_id == client_port:
|
||||
print("UPDATING SERVER")
|
||||
server.update(delta, port_id=server_port_for_client)
|
||||
|
||||
# when the server updates, pass the update back to the client
|
||||
def server_callback(port_id, delta):
|
||||
print("UPDATING CLIENT")
|
||||
assert port_id == server_port_for_client
|
||||
assert server.size() == 8
|
||||
server_view.delete()
|
||||
server.delete()
|
||||
client_view.delete()
|
||||
client.delete()
|
||||
|
||||
client_view.on_update(client_callback, mode="row")
|
||||
server_view.on_update(server_callback, mode="row")
|
||||
|
||||
client.update(data, port_id=client_port)
|
||||
@@ -0,0 +1,102 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestRemove(object):
|
||||
def test_remove_all(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
tbl.remove(["abc"])
|
||||
assert tbl.view().to_records() == []
|
||||
# assert tbl.size() == 0
|
||||
|
||||
def test_remove_nonsequential(self):
|
||||
tbl = Table(
|
||||
[{"a": "abc", "b": 123}, {"a": "def", "b": 456}, {"a": "efg", "b": 789}],
|
||||
index="a",
|
||||
)
|
||||
tbl.remove(["abc", "efg"])
|
||||
assert tbl.view().to_records() == [{"a": "def", "b": 456}]
|
||||
# assert tbl.size() == 1
|
||||
|
||||
def test_remove_multiple_single(self):
|
||||
tbl = Table({"a": "integer", "b": "string"}, index="a")
|
||||
for i in range(0, 10):
|
||||
tbl.update([{"a": i, "b": str(i)}])
|
||||
for i in range(1, 10):
|
||||
tbl.remove([i])
|
||||
assert tbl.view().to_records() == [{"a": 0, "b": "0"}]
|
||||
# assert tbl.size() == 0
|
||||
|
||||
def test_remove_expressions(self):
|
||||
schema = {"key": "string", "delta$": "float", "business_line": "string"}
|
||||
data = [
|
||||
{
|
||||
"key": "A",
|
||||
"delta$": 46412.3804275,
|
||||
},
|
||||
{
|
||||
"key": "B",
|
||||
"delta$": 2317615.875,
|
||||
},
|
||||
]
|
||||
|
||||
table = Table(schema, index="key")
|
||||
table.update(data)
|
||||
table.remove(["A"])
|
||||
view = table.view(
|
||||
group_by=["business_line"],
|
||||
columns=["delta$", "alias"],
|
||||
expressions={
|
||||
"alias": '"delta$"',
|
||||
},
|
||||
)
|
||||
|
||||
records = view.to_records()
|
||||
assert records == [
|
||||
{"__ROW_PATH__": [], "delta$": 2317615.875, "alias": 2317615.875},
|
||||
{"__ROW_PATH__": [None], "delta$": 2317615.875, "alias": 2317615.875},
|
||||
]
|
||||
|
||||
def test_remove_expressions_after_view(self):
|
||||
schema = {"key": "string", "delta$": "float", "business_line": "string"}
|
||||
data = [
|
||||
{
|
||||
"key": "A",
|
||||
"delta$": 46412.3804275,
|
||||
},
|
||||
{
|
||||
"key": "B",
|
||||
"delta$": 2317615.875,
|
||||
},
|
||||
]
|
||||
|
||||
table = Table(schema, index="key")
|
||||
table.update(data)
|
||||
view = table.view(
|
||||
group_by=["business_line"],
|
||||
columns=["delta$", "alias"],
|
||||
expressions={
|
||||
"alias": '"delta$"',
|
||||
},
|
||||
)
|
||||
|
||||
table.remove(["A"])
|
||||
records = view.to_records()
|
||||
assert records == [
|
||||
{"__ROW_PATH__": [], "delta$": 2317615.875, "alias": 2317615.875},
|
||||
{"__ROW_PATH__": [None], "delta$": 2317615.875, "alias": 2317615.875},
|
||||
]
|
||||
@@ -0,0 +1,641 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 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:
|
||||
# table constructors
|
||||
|
||||
@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_synchronous_process(self):
|
||||
# tbl = Table({"a": [1, 2, 3]})
|
||||
# assert _PerspectiveStateManager.TO_PROCESS == {}
|
||||
# tbl.update({"a": [4, 5, 6]})
|
||||
# assert _PerspectiveStateManager.TO_PROCESS == {}
|
||||
|
||||
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}]
|
||||
|
||||
# schema
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
# is_valid_filter
|
||||
|
||||
# def test_table_is_valid_filter_str(self):
|
||||
# filter = ["a", "<", 1]
|
||||
# data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
# tbl = Table(data)
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# def test_table_not_is_valid_filter_str(self):
|
||||
# filter = ["a", "<", None]
|
||||
# data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
# tbl = Table(data)
|
||||
# assert tbl.is_valid_filter(filter) is False
|
||||
|
||||
# def test_table_is_valid_filter_filter_op(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_IS_NULL]
|
||||
# data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
# tbl = Table(data)
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# def test_table_not_is_valid_filter_filter_op(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, None]
|
||||
# data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
# tbl = Table(data)
|
||||
# assert tbl.is_valid_filter(filter) is False
|
||||
|
||||
# def test_table_is_valid_filter_date(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, date.today()]
|
||||
# tbl = Table({"a": "date"})
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# def test_table_not_is_valid_filter_date(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, None]
|
||||
# tbl = Table({"a": "date"})
|
||||
# assert tbl.is_valid_filter(filter) is False
|
||||
|
||||
# def test_table_is_valid_filter_datetime(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, datetime.now()]
|
||||
# tbl = Table({"a": "datetime"})
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# def test_table_not_is_valid_filter_datetime(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, None]
|
||||
# tbl = Table({"a": "datetime"})
|
||||
# assert tbl.is_valid_filter(filter) is False
|
||||
|
||||
# def test_table_is_valid_filter_datetime_str(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, "7/11/2019 5:30PM"]
|
||||
# tbl = Table({"a": "datetime"})
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# def test_table_not_is_valid_filter_datetime_str(self):
|
||||
# filter = ["a", t_filter_op.FILTER_OP_GT, None]
|
||||
# tbl = Table({"a": "datetime"})
|
||||
# assert tbl.is_valid_filter(filter) is False
|
||||
|
||||
# def test_table_is_valid_filter_ignores_not_in_schema(self):
|
||||
# filter = ["not in schema", "<", 1]
|
||||
# data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
# tbl = Table(data)
|
||||
# assert tbl.is_valid_filter(filter) is True
|
||||
|
||||
# index
|
||||
|
||||
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}]
|
||||
|
||||
# index with None in column
|
||||
|
||||
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],
|
||||
} # second `None` replaces first
|
||||
|
||||
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],
|
||||
} # second `None` replaces first
|
||||
|
||||
def test_table_index_bool_with_none(self):
|
||||
# bools cannot be used as primary key columns
|
||||
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
|
||||
|
||||
# limit
|
||||
|
||||
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
|
||||
|
||||
# num_views
|
||||
|
||||
@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()
|
||||
|
||||
# clear
|
||||
|
||||
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() == []
|
||||
|
||||
# replace
|
||||
|
||||
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):
|
||||
# assert port_id == 0
|
||||
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__])
|
||||
@@ -0,0 +1,500 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 os.path
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from perspective.tests.conftest import Util
|
||||
import pyarrow as pa
|
||||
from datetime import date, datetime
|
||||
import perspective as psp
|
||||
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
DATE32_ARROW = os.path.join(os.path.dirname(__file__), "arrow", "date32.arrow")
|
||||
DATE64_ARROW = os.path.join(os.path.dirname(__file__), "arrow", "date64.arrow")
|
||||
DICT_ARROW = os.path.join(os.path.dirname(__file__), "arrow", "dict.arrow")
|
||||
|
||||
|
||||
names = ["a", "b", "c", "d"]
|
||||
|
||||
# Create sample data for every integer type
|
||||
ALL_INTEGERS_DATA = {
|
||||
"int8": pa.array([1, 2, 3], type=pa.int8()),
|
||||
"int16": pa.array([1000, 2000, 3000], type=pa.int16()),
|
||||
"int32": pa.array([100000, 200000, 300000], type=pa.int32()),
|
||||
"int64": pa.array([10000000000, 20000000000, 30000000000], type=pa.int64()),
|
||||
"uint8": pa.array([1, 2, 3], type=pa.uint8()),
|
||||
"uint16": pa.array([1000, 2000, 3000], type=pa.uint16()),
|
||||
"uint32": pa.array([100000, 200000, 300000], type=pa.uint32()),
|
||||
"uint64": pa.array([10000000000, 20000000000, 30000000000], type=pa.uint64()),
|
||||
"float32": pa.array([100000.0, 200000.0, 300000.0], type=pa.float32()),
|
||||
"float64": pa.array(
|
||||
[10000000000.0, 20000000000.0, 30000000000.0], type=pa.float64()
|
||||
),
|
||||
}
|
||||
|
||||
ALL_INTEGERS_TABLE = pa.Table.from_pydict(ALL_INTEGERS_DATA)
|
||||
|
||||
|
||||
class TestTableArrow(object):
|
||||
def test_table_with_integer_types(self):
|
||||
tbl = Table(ALL_INTEGERS_TABLE)
|
||||
assert tbl.size() == 3
|
||||
assert tbl.schema() == {
|
||||
"int8": "integer",
|
||||
"int16": "integer",
|
||||
"int32": "integer",
|
||||
"int64": "integer",
|
||||
"uint8": "integer",
|
||||
"uint16": "integer",
|
||||
"uint32": "integer",
|
||||
"uint64": "integer",
|
||||
"float32": "float",
|
||||
"float64": "float",
|
||||
}
|
||||
for k, values in ALL_INTEGERS_DATA.items():
|
||||
v = tbl.view(filter=[[k, "==", values[0].as_py()]])
|
||||
assert len(v.to_json()) == 1
|
||||
|
||||
def test_table_arrow_loads_date32_file(self, util: Util):
|
||||
with open(DATE32_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl = Table(file.read())
|
||||
assert tbl.schema() == {
|
||||
"jan-2019": "date",
|
||||
"feb-2020": "date",
|
||||
"mar-2019": "date",
|
||||
"apr-2020": "date",
|
||||
}
|
||||
assert tbl.size() == 31
|
||||
view = tbl.view()
|
||||
assert view.to_columns() == {
|
||||
"jan-2019": [
|
||||
util.to_timestamp(datetime(2019, 1, i)) for i in range(1, 32)
|
||||
],
|
||||
"feb-2020": [
|
||||
util.to_timestamp(datetime(2020, 2, i)) for i in range(1, 30)
|
||||
]
|
||||
+ [None, None],
|
||||
"mar-2019": [
|
||||
util.to_timestamp(datetime(2019, 3, i)) for i in range(1, 32)
|
||||
],
|
||||
"apr-2020": [
|
||||
util.to_timestamp(datetime(2020, 4, i)) for i in range(1, 31)
|
||||
]
|
||||
+ [None],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_date64_file(self, util: Util):
|
||||
with open(DATE64_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl = Table(file.read())
|
||||
assert tbl.schema() == {
|
||||
"jan-2019": "date",
|
||||
"feb-2020": "date",
|
||||
"mar-2019": "date",
|
||||
"apr-2020": "date",
|
||||
}
|
||||
assert tbl.size() == 31
|
||||
view = tbl.view()
|
||||
assert view.to_columns() == {
|
||||
"jan-2019": [
|
||||
util.to_timestamp(datetime(2019, 1, i)) for i in range(1, 32)
|
||||
],
|
||||
"feb-2020": [
|
||||
util.to_timestamp(datetime(2020, 2, i)) for i in range(1, 30)
|
||||
]
|
||||
+ [None, None],
|
||||
"mar-2019": [
|
||||
util.to_timestamp(datetime(2019, 3, i)) for i in range(1, 32)
|
||||
],
|
||||
"apr-2020": [
|
||||
util.to_timestamp(datetime(2020, 4, i)) for i in range(1, 31)
|
||||
]
|
||||
+ [None],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dict_file(self):
|
||||
with open(DICT_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl = Table(file.read())
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.size() == 5
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None, "abc"],
|
||||
"b": ["klm", "hij", None, "hij", "klm"],
|
||||
}
|
||||
|
||||
# streams
|
||||
|
||||
def test_table_arrow_loads_int_stream(self, util):
|
||||
data = [list(range(10)) for i in range(4)]
|
||||
arrow_data = util.make_arrow(names, data)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "integer",
|
||||
"c": "integer",
|
||||
"d": "integer",
|
||||
}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": data[0],
|
||||
"b": data[1],
|
||||
"c": data[2],
|
||||
"d": data[3],
|
||||
}
|
||||
|
||||
def test_empty_arrow(self, util):
|
||||
table = pa.table(
|
||||
{
|
||||
"col1": [1, 2, 3],
|
||||
"col2": ["abc", "foo", "bar"],
|
||||
}
|
||||
)
|
||||
|
||||
empty_table = table.schema.empty_table()
|
||||
assert client.table(table, name="table2").size() == 3
|
||||
assert client.table(empty_table, name="table_empty_bad").size() == 0
|
||||
assert client.table(table, name="table3").schema() == {
|
||||
"col1": "integer",
|
||||
"col2": "string",
|
||||
}
|
||||
|
||||
assert client.table(empty_table, name="table4").schema() == {
|
||||
"col1": "integer",
|
||||
"col2": "string",
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_float_stream(self, util):
|
||||
data = [[i for i in range(10)], [i * 1.5 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "float",
|
||||
}
|
||||
assert tbl.view().to_columns() == {"a": data[0], "b": data[1]}
|
||||
|
||||
def test_table_arrow_loads_decimal_stream(self, util):
|
||||
data = [[i * 1000 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.decimal128(4)])
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "float",
|
||||
}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_bool_stream(self, util):
|
||||
data = [[True if i % 2 == 0 else False for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "boolean"}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_date32_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date32()])
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_date64_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date64()])
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_timestamp_all_formats_stream(self, util):
|
||||
data = [
|
||||
[datetime(2019, 2, i, 9) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 10) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 11) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 12) for i in range(1, 11)],
|
||||
]
|
||||
arrow_data = util.make_arrow(
|
||||
names,
|
||||
data,
|
||||
types=[
|
||||
pa.timestamp("s"),
|
||||
pa.timestamp("ms"),
|
||||
pa.timestamp("us"),
|
||||
pa.timestamp("ns"),
|
||||
],
|
||||
)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "datetime",
|
||||
"b": "datetime",
|
||||
"c": "datetime",
|
||||
"d": "datetime",
|
||||
}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(i) for i in data[0]],
|
||||
"b": [util.to_timestamp(i) for i in data[1]],
|
||||
"c": [util.to_timestamp(i) for i in data[2]],
|
||||
"d": [util.to_timestamp(i) for i in data[3]],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_string_stream(self, util):
|
||||
data = [[str(i) for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.string()])
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_int8(self, util):
|
||||
data = [
|
||||
([0, 1, 1, None], ["abc", "def"]),
|
||||
([0, 1, None, 2], ["xx", "yy", "zz"]),
|
||||
]
|
||||
types = [[pa.int8(), pa.string()]] * 2
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data, types=types)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None],
|
||||
"b": ["xx", "yy", None, "zz"],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_int16(self, util):
|
||||
data = [
|
||||
([0, 1, 1, None], ["abc", "def"]),
|
||||
([0, 1, None, 2], ["xx", "yy", "zz"]),
|
||||
]
|
||||
types = [[pa.int16(), pa.string()]] * 2
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data, types=types)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None],
|
||||
"b": ["xx", "yy", None, "zz"],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_int32(self, util):
|
||||
data = [
|
||||
([0, 1, 1, None], ["abc", "def"]),
|
||||
([0, 1, None, 2], ["xx", "yy", "zz"]),
|
||||
]
|
||||
types = [[pa.int32(), pa.string()]] * 2
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data, types=types)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None],
|
||||
"b": ["xx", "yy", None, "zz"],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_int64(self, util):
|
||||
data = [
|
||||
([0, 1, 1, None], ["abc", "def"]),
|
||||
([0, 1, None, 2], ["xx", "yy", "zz"]),
|
||||
]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None],
|
||||
"b": ["xx", "yy", None, "zz"],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_nones(self, util):
|
||||
data = [([None, 0, 1, 2], ["", "abc", "def"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a"], data)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
assert tbl.view().to_columns() == {"a": [None, "", "abc", "def"]}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_nones_indexed(self, util):
|
||||
data = [
|
||||
([1, None, 0, 2], ["", "abc", "def"]),
|
||||
([2, 1, 0, None], ["", "hij", "klm"]),
|
||||
] # ["abc", None, "", "def"] # ["klm", "hij", "", None]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data, index="a") # column "a" is sorted
|
||||
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [None, "", "abc", "def"],
|
||||
"b": ["hij", "", "klm", None],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_dictionary_stream_nones_indexed_2(self, util):
|
||||
"""Test the other column, just in case."""
|
||||
data = [
|
||||
([1, None, 0, 2], ["", "abc", "def"]),
|
||||
([2, 1, 0, None], ["", "hij", "klm"]),
|
||||
] # ["abc", None, "", "def"] # ["klm", "hij", "", None]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data, index="b") # column "b" is sorted
|
||||
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["def", "", None, "abc"],
|
||||
"b": [None, "", "hij", "klm"],
|
||||
}
|
||||
|
||||
# legacy
|
||||
|
||||
def test_table_arrow_loads_int_legacy(self, util):
|
||||
data = [list(range(10)) for i in range(4)]
|
||||
arrow_data = util.make_arrow(names, data, legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "integer",
|
||||
"c": "integer",
|
||||
"d": "integer",
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_float_legacy(self, util):
|
||||
data = [[i for i in range(10)], [i * 1.5 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a", "b"], data, legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "float",
|
||||
}
|
||||
assert tbl.view().to_columns() == {"a": data[0], "b": data[1]}
|
||||
|
||||
def test_table_arrow_loads_decimal128_legacy(self, util):
|
||||
data = [[i * 1000 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.decimal128(4)], legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "float",
|
||||
}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_bool_legacy(self, util):
|
||||
data = [[True if i % 2 == 0 else False for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "boolean"}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_date32_legacy(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date32()], legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_date64_legacy(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date64()], legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_timestamp_all_formats_legacy(self, util):
|
||||
data = [
|
||||
[datetime(2019, 2, i, 9) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 10) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 11) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 12) for i in range(1, 11)],
|
||||
]
|
||||
arrow_data = util.make_arrow(
|
||||
names,
|
||||
data,
|
||||
types=[
|
||||
pa.timestamp("s"),
|
||||
pa.timestamp("ms"),
|
||||
pa.timestamp("us"),
|
||||
pa.timestamp("ns"),
|
||||
],
|
||||
legacy=True,
|
||||
)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {
|
||||
"a": "datetime",
|
||||
"b": "datetime",
|
||||
"c": "datetime",
|
||||
"d": "datetime",
|
||||
}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(i) for i in data[0]],
|
||||
"b": [util.to_timestamp(i) for i in data[1]],
|
||||
"c": [util.to_timestamp(i) for i in data[2]],
|
||||
"d": [util.to_timestamp(i) for i in data[3]],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_string_legacy(self, util):
|
||||
data = [[str(i) for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.string()], legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_table_arrow_loads_dictionary_legacy(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data, legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "string", "b": "string"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["a", "b", "b", None],
|
||||
"b": ["x", "y", None, "z"],
|
||||
}
|
||||
|
||||
def test_table_arrow_loads_arrow_from_df_with_nan(self):
|
||||
data = pd.DataFrame({"a": [1.5, 2.5, np.nan, 3.5, 4.5, np.nan, np.nan, np.nan]})
|
||||
|
||||
arrow_table = pa.Table.from_pandas(data, preserve_index=False)
|
||||
|
||||
assert arrow_table["a"].null_count == 4
|
||||
|
||||
tbl = Table(arrow_table)
|
||||
assert tbl.size() == 8
|
||||
|
||||
# check types
|
||||
assert tbl.schema() == {"a": "float"}
|
||||
|
||||
# check nans
|
||||
json = tbl.view().to_columns()
|
||||
|
||||
assert json["a"] == [1.5, 2.5, None, 3.5, 4.5, None, None, None]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 datetime import date, datetime
|
||||
from pytest import mark
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestTableInfer(object):
|
||||
def test_table_infer_int(self):
|
||||
data = {"a": [None, None, None, None, 1, 0, 1, 1, 1]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer"}
|
||||
|
||||
def test_table_infer_float(self):
|
||||
data = {"a": [None, None, None, None, 1.0, 2.0]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "float"}
|
||||
|
||||
def test_table_infer_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_infer_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"}
|
||||
|
||||
@mark.skip(reason="Unsupported python specific behavior")
|
||||
def test_table_bool_infer_str_all_formats_from_schema(self):
|
||||
bool_data = [
|
||||
{"a": "True", "b": "False"},
|
||||
{"a": "t", "b": "f"},
|
||||
{"a": "true", "b": "false"},
|
||||
{"a": 1, "b": 0},
|
||||
{"a": "on", "b": "off"},
|
||||
]
|
||||
tbl = Table(bool_data)
|
||||
assert tbl.schema() == {"a": "boolean", "b": "boolean"}
|
||||
assert tbl.size() == 5
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [True, True, True, True, True],
|
||||
"b": [False, False, False, False, False],
|
||||
}
|
||||
|
||||
def test_table_infer_bool_variant(self):
|
||||
data = {"a": [None, None, None, None, True, True, True]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "boolean"}
|
||||
|
||||
def test_table_infer_str(self):
|
||||
data = {"a": [None, None, None, None, None, None, "abc"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
@mark.skip(reason="Time is not a valid JSON type")
|
||||
def test_table_infer_time_as_string(self):
|
||||
# time objects are inferred as string
|
||||
data = {
|
||||
"a": [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
datetime(2019, 7, 11, 12, 30, 5).time(),
|
||||
]
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
@mark.skip(reason="Unsupported python specific behavior")
|
||||
def test_table_infer_date_from_datetime(self):
|
||||
# inferrence on non-pandas datasets defaults to datetime
|
||||
data = {"a": [None, None, None, None, None, None, datetime(2019, 7, 11)]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_date_from_date(self):
|
||||
# pass in a `date` to make sure it infers as date
|
||||
data = {"a": [None, None, None, None, None, None, date(2019, 7, 11)]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_valid_date(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/31/2019"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_ambiguous_date(self):
|
||||
data = {"a": [None, None, None, None, None, None, "01/03/2019"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_ymd_date(self):
|
||||
data = {"a": [None, None, None, None, None, None, "2019/01/03"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_invalid_date(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/55/2019"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
def test_table_infer_date_edge(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/31/2019 00:00:00"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_datetime_edge(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/31/2019 00:00:01"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_valid_datetime(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/31/2019 07:30:00"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_iso_datetime(self):
|
||||
data = {"a": [None, None, None, None, None, None, "2019/07/25T09:00:00"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_datetime_separators(self):
|
||||
data = {
|
||||
"a": [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"2019-07-25T09:00:00",
|
||||
"2019/07/25T09:00:00",
|
||||
]
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_datetime_tz(self):
|
||||
data = {"a": [None, None, None, None, None, "2019-07-25T09:00:00-05:00"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_infer_invalid_datetime(self):
|
||||
data = {"a": [None, None, None, None, None, None, "08/31/2019 25:30:00"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
def test_table_infer_mixed_date(self):
|
||||
data = {"a": [None, None, None, None, None, "08/11/2019"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_infer_mixed_datetime(self):
|
||||
data = {"a": [None, None, None, None, None, "08/11/2019 13:14:15"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
@mark.skip(reason="Numeric strings are not inferred as numbers")
|
||||
def test_table_strict_datetime_infer(self):
|
||||
data = {"a": ["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
def test_table_strict_date_infer(self):
|
||||
data = {"a": ["2019 09 10"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
def test_table_strict_datetime_separator_infer(self):
|
||||
data = {"a": ["2019-10-01 7:30"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
|
||||
def test_table_datetime_infer_no_false_positive(self):
|
||||
data = {"a": [" . - / but clearly not a date"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
|
||||
@mark.skip
|
||||
def test_table_datetime_infer_from_string_with_time(self):
|
||||
data = {"a": ["11:00 ABCD"]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
@@ -0,0 +1,45 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestTableInfer(object):
|
||||
def test_table_limit_wraparound_does_not_respect_partial_none(self):
|
||||
t = Table({"a": "float", "b": "float"}, limit=3)
|
||||
t.update([{"a": 10}, {"b": 1}, {"a": 20}, {"a": None, "b": 2}])
|
||||
d1 = t.view().to_json()
|
||||
|
||||
t2 = Table({"a": "float", "b": "float"}, limit=3)
|
||||
t2.update([{"a": 10}, {"b": 1}, {"a": 20}, {"b": 2}])
|
||||
d2 = t2.view().to_json()
|
||||
|
||||
assert d1[0] == d2[0]
|
||||
assert d1[1:] == d2[1:]
|
||||
|
||||
def test_table_limit_wraparound_does_not_respect_partial(self):
|
||||
t = Table({"a": "float", "b": "float"}, limit=3)
|
||||
t.update([{"a": 10}, {"b": 1}, {"a": 20}, {"a": 10, "b": 2}])
|
||||
d1 = t.view().to_columns()
|
||||
|
||||
t2 = Table({"a": "float", "b": "float"}, limit=3)
|
||||
t2.update([{"a": 10}, {"b": 1}, {"a": 20}, {"b": 2}])
|
||||
d2 = t2.view().to_columns()
|
||||
|
||||
assert d1 != d2
|
||||
|
||||
def test_table_limit_with_json(self):
|
||||
t = Table({"a": [1, 2, 3]}, limit=1)
|
||||
assert t.size() == 1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 os
|
||||
import tempfile
|
||||
|
||||
import perspective as psp
|
||||
from pytest import mark
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
def _perspective_temp_dirs():
|
||||
"""All `perspective_*` directories currently in the OS temp directory.
|
||||
|
||||
`BACKING_STORE_DISK` columns are written to a unique
|
||||
`<tempdir>/perspective_<uuid>/` directory.
|
||||
"""
|
||||
tmp = tempfile.gettempdir()
|
||||
try:
|
||||
entries = os.listdir(tmp)
|
||||
except OSError:
|
||||
return set()
|
||||
|
||||
print(tmp)
|
||||
return {
|
||||
os.path.join(tmp, e)
|
||||
for e in entries
|
||||
if e.startswith("perspective_") and os.path.isdir(os.path.join(tmp, e))
|
||||
}
|
||||
|
||||
|
||||
class TestTableOnDisk:
|
||||
def test_page_to_disk_schema_and_view(self):
|
||||
data = {
|
||||
"x": [1, 2, 3, 4],
|
||||
"y": ["a", "b", "c", "d"],
|
||||
"z": [True, False, True, False],
|
||||
}
|
||||
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
assert disk.schema() == mem.schema()
|
||||
assert disk.view().to_columns() == mem.view().to_columns()
|
||||
|
||||
def test_page_to_disk_csv(self):
|
||||
data = "x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false"
|
||||
tbl = Table(data, page_to_disk=True)
|
||||
assert tbl.schema() == {"x": "integer", "y": "string", "z": "boolean"}
|
||||
assert tbl.view().to_columns() == {
|
||||
"x": [1, 2, 3, 4],
|
||||
"y": ["a", "b", "c", "d"],
|
||||
"z": [True, False, True, False],
|
||||
}
|
||||
|
||||
def test_page_to_disk_update_indexed(self):
|
||||
mem = Table({"x": [1, 2, 3], "y": [1.0, 2.0, 3.0]}, index="x")
|
||||
disk = Table(
|
||||
{"x": [1, 2, 3], "y": [1.0, 2.0, 3.0]}, index="x", page_to_disk=True
|
||||
)
|
||||
update = {"x": [2, 4], "y": [20.0, 40.0]}
|
||||
mem.update(update)
|
||||
disk.update(update)
|
||||
assert disk.view().to_columns() == mem.view().to_columns()
|
||||
assert disk.size() == mem.size()
|
||||
|
||||
def test_page_to_disk_group_by_aggregation(self):
|
||||
data = {"g": ["a", "b", "a", "b", "a"], "v": [1, 2, 3, 4, 5]}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
config = {"group_by": ["g"], "columns": ["v"], "aggregates": {"v": "sum"}}
|
||||
assert disk.view(**config).to_columns() == mem.view(**config).to_columns()
|
||||
|
||||
def test_page_to_disk_arrow_roundtrip(self):
|
||||
data = {"x": list(range(100)), "y": [float(i) / 2 for i in range(100)]}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
assert disk.view().to_arrow() == mem.view().to_arrow()
|
||||
|
||||
def test_page_to_disk_creates_backing_files(self):
|
||||
before = _perspective_temp_dirs()
|
||||
tbl = Table({"x": [1, 2, 3], "y": ["a", "b", "c"]}, page_to_disk=True)
|
||||
|
||||
# Touch the table so it is not optimized away before we inspect the FS.
|
||||
assert tbl.size() == 3
|
||||
after = _perspective_temp_dirs()
|
||||
new_dirs = after - before
|
||||
assert new_dirs, "expected a new perspective_<uuid> directory on disk"
|
||||
assert any(os.listdir(d) for d in new_dirs), "expected column files on disk"
|
||||
|
||||
def test_memory_table_creates_no_backing_files(self):
|
||||
before = _perspective_temp_dirs()
|
||||
tbl = Table({"x": [1, 2, 3]})
|
||||
assert tbl.size() == 3
|
||||
after = _perspective_temp_dirs()
|
||||
assert after == before, "in-memory table must not write to disk"
|
||||
|
||||
def test_page_to_disk_growth_forces_resize(self):
|
||||
tbl = Table({"x": [0], "y": [0.0]}, index="x", page_to_disk=True)
|
||||
n = 50000
|
||||
tbl.update({"x": list(range(n)), "y": [float(i) for i in range(n)]})
|
||||
assert tbl.size() == n
|
||||
cols = tbl.view().to_columns()
|
||||
assert cols["x"][0] == 0
|
||||
assert cols["x"][-1] == n - 1
|
||||
assert cols["y"][-1] == float(n - 1)
|
||||
|
||||
def test_page_to_disk_clone_keeps_master_files(self):
|
||||
# Cloning a disk-backed column (e.g. the masked clone that serializing a
|
||||
# table with removed rows performs) must give the clone its OWN backing
|
||||
# file. Otherwise the clone aliases — and on teardown `rmfile`s — the
|
||||
# master's file, silently unlinking the master's named backing store.
|
||||
before = _perspective_temp_dirs()
|
||||
tbl = Table(
|
||||
{"x": [1, 2, 3, 4], "y": [10.0, 20.0, 30.0, 40.0]},
|
||||
index="x",
|
||||
page_to_disk=True,
|
||||
)
|
||||
view = tbl.view()
|
||||
new_dirs = _perspective_temp_dirs() - before
|
||||
master_dirs = [
|
||||
d
|
||||
for d in new_dirs
|
||||
if not os.path.basename(d).startswith("perspective_expr_")
|
||||
]
|
||||
assert len(master_dirs) == 1
|
||||
master_dir = master_dirs[0]
|
||||
master_files = set(os.listdir(master_dir))
|
||||
assert master_files, "expected master backing files on disk"
|
||||
|
||||
# Removing rows triggers a masked clone of the disk master columns.
|
||||
tbl.remove([2, 3])
|
||||
assert view.to_columns()["x"] == [1, 4]
|
||||
# The master's own backing files must survive the clone's teardown.
|
||||
survived = set(os.listdir(master_dir))
|
||||
assert master_files <= survived, (
|
||||
"clone teardown unlinked master backing files: {}".format(
|
||||
master_files - survived
|
||||
)
|
||||
)
|
||||
|
||||
# And the master must remain usable for subsequent updates.
|
||||
tbl.update({"x": [5, 6], "y": [50.0, 60.0]})
|
||||
assert sorted(view.to_columns()["x"]) == [1, 4, 5, 6]
|
||||
|
||||
def test_page_to_disk_larger_dataset_matches_memory(self):
|
||||
n = 20000
|
||||
data = {
|
||||
"i": list(range(n)),
|
||||
"f": [float(i) * 1.5 for i in range(n)],
|
||||
"s": ["row_{}".format(i % 97) for i in range(n)],
|
||||
}
|
||||
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
assert disk.view().to_arrow() == mem.view().to_arrow()
|
||||
|
||||
|
||||
def _perspective_expr_dirs():
|
||||
"""`perspective_expr_*` directories — the on-disk expression `m_master`."""
|
||||
tmp = tempfile.gettempdir()
|
||||
try:
|
||||
entries = os.listdir(tmp)
|
||||
except OSError:
|
||||
return set()
|
||||
|
||||
return {
|
||||
os.path.join(tmp, e)
|
||||
for e in entries
|
||||
if e.startswith("perspective_expr_") and os.path.isdir(os.path.join(tmp, e))
|
||||
}
|
||||
|
||||
|
||||
class TestTableOnDiskExpressions:
|
||||
def test_expression_numeric_equivalence(self):
|
||||
data = {"x": [1, 2, 3, 4], "y": [10.0, 20.0, 30.0, 40.0]}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
exprs = {"sum": '"x" + "y"', "prod": '"x" * "y"'}
|
||||
assert (
|
||||
disk.view(expressions=exprs).to_columns()
|
||||
== mem.view(expressions=exprs).to_columns()
|
||||
)
|
||||
|
||||
def test_expression_string_equivalence(self):
|
||||
data = {"a": ["foo", "bar", "baz"], "b": ["AA", "BB", "CC"]}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
exprs = {"up": 'upper("a")', "lo": 'lower("b")'}
|
||||
assert (
|
||||
disk.view(expressions=exprs).to_columns()
|
||||
== mem.view(expressions=exprs).to_columns()
|
||||
)
|
||||
|
||||
def test_expression_with_group_by_equivalence(self):
|
||||
data = {"g": ["a", "b", "a", "b"], "v": [1, 2, 3, 4]}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
config = {
|
||||
"expressions": {"v2": '"v" * 2'},
|
||||
"group_by": ["g"],
|
||||
"columns": ["v2"],
|
||||
"aggregates": {"v2": "sum"},
|
||||
}
|
||||
|
||||
assert disk.view(**config).to_columns() == mem.view(**config).to_columns()
|
||||
|
||||
def test_expression_master_is_page_to_disk(self):
|
||||
before = _perspective_expr_dirs()
|
||||
tbl = Table({"x": [1, 2, 3], "y": [10.0, 20.0, 30.0]}, page_to_disk=True)
|
||||
view = tbl.view(expressions={"e": '"x" + "y"'})
|
||||
assert view.to_columns()["e"] == [11.0, 22.0, 33.0]
|
||||
new_dirs = _perspective_expr_dirs() - before
|
||||
assert new_dirs, (
|
||||
"expected a perspective_expr_<uuid> dir for the expression master"
|
||||
)
|
||||
|
||||
assert any(os.listdir(d) for d in new_dirs), (
|
||||
"expected expression backing files on disk"
|
||||
)
|
||||
|
||||
def test_memory_expression_creates_no_expr_dir(self):
|
||||
before = _perspective_expr_dirs()
|
||||
tbl = Table({"x": [1, 2, 3], "y": [10.0, 20.0, 30.0]})
|
||||
view = tbl.view(expressions={"e": '"x" + "y"'})
|
||||
assert view.to_columns()["e"] == [11.0, 22.0, 33.0]
|
||||
assert _perspective_expr_dirs() == before, (
|
||||
"in-memory table must not write expression data to disk"
|
||||
)
|
||||
|
||||
def test_expression_page_to_disk_update_and_larger(self):
|
||||
n = 10000
|
||||
tbl = Table({"x": [0], "y": [0.0]}, index="x", page_to_disk=True)
|
||||
tbl.update({"x": list(range(n)), "y": [float(i) for i in range(n)]})
|
||||
cols = tbl.view(expressions={"e": '"x" + "y"'}).to_columns()
|
||||
assert cols["e"][0] == 0.0
|
||||
assert cols["e"][-1] == float((n - 1) + (n - 1))
|
||||
|
||||
|
||||
class TestResidency:
|
||||
# The residency manager evicts disk-backed column buffers to their files
|
||||
# when over the `PSP_MEMORY_BUDGET` and restores them transparently on
|
||||
# access. Under a tiny budget, eviction fires aggressively and data must
|
||||
# still round-trip identically to an in-memory table.
|
||||
|
||||
@mark.skip(reason="No secret hooks in the engine")
|
||||
def test_residency_evicts_and_data_is_correct(self):
|
||||
stats_fd, stats_path = tempfile.mkstemp(prefix="psp_residency_")
|
||||
os.close(stats_fd)
|
||||
os.environ["PSP_MEMORY_BUDGET"] = "1024"
|
||||
os.environ["PSP_RESIDENCY_STATS_FILE"] = stats_path
|
||||
try:
|
||||
n = 5000
|
||||
data = {
|
||||
"x": list(range(n)),
|
||||
"y": [float(i) * 1.5 for i in range(n)],
|
||||
"s": ["row_{}".format(i % 50) for i in range(n)],
|
||||
}
|
||||
mem = Table(data)
|
||||
disk = Table(data, page_to_disk=True)
|
||||
|
||||
# Each request is a safepoint that trims to budget; data read back
|
||||
# must be correct (round-tripped through evict -> restore).
|
||||
assert disk.view().to_columns() == mem.view().to_columns()
|
||||
assert disk.view().to_arrow() == mem.view().to_arrow()
|
||||
|
||||
upd = {
|
||||
"x": list(range(n, n + 200)),
|
||||
"y": [float(i) for i in range(n, n + 200)],
|
||||
"s": ["upd_{}".format(i) for i in range(200)],
|
||||
}
|
||||
mem.update(upd)
|
||||
disk.update(upd)
|
||||
assert disk.view().to_columns() == mem.view().to_columns()
|
||||
|
||||
# An expression view on an evicted disk table must still compute.
|
||||
exprs = {"e": '"x" + "y"'}
|
||||
assert (
|
||||
disk.view(expressions=exprs).to_columns()
|
||||
== mem.view(expressions=exprs).to_columns()
|
||||
)
|
||||
|
||||
# Confirm eviction actually occurred (otherwise the test is vacuous).
|
||||
with open(stats_path) as f:
|
||||
stats = f.read()
|
||||
evictions = int(stats.split("evictions=")[1].split()[0])
|
||||
assert evictions > 0, "expected evictions under a tiny budget: " + stats
|
||||
finally:
|
||||
os.environ.pop("PSP_MEMORY_BUDGET", None)
|
||||
os.environ.pop("PSP_RESIDENCY_STATS_FILE", None)
|
||||
os.remove(stats_path)
|
||||
# Drain a safepoint with residency disabled so any evicted stores
|
||||
# from other live tables are restored before subsequent tests.
|
||||
Table({"_": [0]}).view().to_columns()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,251 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 datetime import date, datetime
|
||||
import numpy as np
|
||||
import polars as pl
|
||||
from pytest import mark
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
def arrow_bytes_to_polars(view):
|
||||
import pyarrow
|
||||
|
||||
with pyarrow.ipc.open_stream(pyarrow.BufferReader(view.to_arrow())) as reader:
|
||||
return pl.from_dataframe(reader.read_pandas())
|
||||
|
||||
|
||||
class TestTablePolars(object):
|
||||
def test_empty_table(self):
|
||||
tbl = Table([])
|
||||
assert tbl.size() == 0
|
||||
assert tbl.schema() == {}
|
||||
|
||||
def test_table_dataframe(self):
|
||||
d = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
data = pl.DataFrame(d)
|
||||
tbl = Table(data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.schema() == {"a": "integer", "b": "integer"}
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 3, "b": 4},
|
||||
]
|
||||
|
||||
def test_table_lazyframe(self):
|
||||
d = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
data = pl.DataFrame(d).lazy()
|
||||
tbl = Table(data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.schema() == {"a": "integer", "b": "integer"}
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 3, "b": 4},
|
||||
]
|
||||
|
||||
def test_table_dataframe_column_order(self):
|
||||
d = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
||||
data = pl.DataFrame(d).select(["b", "c", "a", "d"])
|
||||
tbl = Table(data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.columns() == ["b", "c", "a", "d"]
|
||||
|
||||
def test_table_dataframe_selective_column_order(self):
|
||||
d = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
||||
data = pl.DataFrame(d).select(["b", "c", "a"])
|
||||
tbl = Table(data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.columns() == ["b", "c", "a"]
|
||||
|
||||
def test_table_dataframe_does_not_mutate(self):
|
||||
# make sure we don't mutate the dataframe that a user passes in
|
||||
data = pl.DataFrame(
|
||||
{
|
||||
"a": [None, 1, None, 2],
|
||||
"b": [1.5, None, 2.5, None],
|
||||
}
|
||||
)
|
||||
assert data["a"].to_list() == [None, 1, None, 2]
|
||||
assert data["b"].to_list() == [1.5, None, 2.5, None]
|
||||
|
||||
tbl = Table(data)
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
|
||||
assert data["a"].to_list() == [None, 1, None, 2]
|
||||
assert data["b"].to_list() == [1.5, None, 2.5, None]
|
||||
|
||||
def test_table_polars_from_schema_int(self):
|
||||
data = [None, 1, None, 2, None, 3, 4]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "integer"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == data
|
||||
|
||||
def test_table_polars_from_schema_bool(self):
|
||||
data = [True, False, True, False]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "boolean"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == data
|
||||
|
||||
def test_table_polars_from_schema_float(self):
|
||||
data = [None, 1.5, None, 2.5, None, 3.5, 4.5]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "float"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == data
|
||||
|
||||
def test_table_polars_from_schema_float_all_nan(self):
|
||||
data = [np.nan, np.nan, np.nan, np.nan]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "float"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == [None, None, None, None]
|
||||
|
||||
def test_table_polars_from_schema_float_to_int(self):
|
||||
data = [None, 1.5, None, 2.5, None, 3.5, 4.5]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "integer"})
|
||||
table.update(df)
|
||||
# truncates decimal
|
||||
assert table.view().to_columns()["a"] == [None, 1, None, 2, None, 3, 4]
|
||||
|
||||
def test_table_polars_from_schema_int_to_float(self):
|
||||
data = [None, 1, None, 2, None, 3, 4]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "float"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == [None, 1.0, None, 2.0, None, 3.0, 4.0]
|
||||
|
||||
def test_table_polars_from_schema_date(self, util):
|
||||
data = [date(2019, 8, 15), None, date(2019, 8, 16)]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "date"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == [
|
||||
util.to_timestamp(datetime(2019, 8, 15)),
|
||||
None,
|
||||
util.to_timestamp(datetime(2019, 8, 16)),
|
||||
]
|
||||
|
||||
def test_table_polars_from_schema_str(self):
|
||||
data = ["a", None, "b", None, "c"]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table({"a": "string"})
|
||||
table.update(df)
|
||||
assert table.view().to_columns()["a"] == data
|
||||
|
||||
def test_table_polars_none(self):
|
||||
data = [None, None, None]
|
||||
df = pl.DataFrame({"a": data})
|
||||
table = Table(df)
|
||||
assert table.view().to_columns()["a"] == data
|
||||
|
||||
def test_table_polars_symmetric_table(self):
|
||||
# make sure that updates are symmetric to table creation
|
||||
df = pl.DataFrame({"a": [1, 2, 3, 4], "b": [1.5, 2.5, 3.5, 4.5]})
|
||||
t1 = Table(df)
|
||||
t2 = Table({"a": "integer", "b": "float"})
|
||||
t2.update(df)
|
||||
assert t1.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": [1.5, 2.5, 3.5, 4.5],
|
||||
}
|
||||
|
||||
def test_table_polars_symmetric_stacked_updates(self):
|
||||
# make sure that updates are symmetric to table creation
|
||||
df = pl.DataFrame({"a": [1, 2, 3, 4], "b": [1.5, 2.5, 3.5, 4.5]})
|
||||
|
||||
t1 = Table(df)
|
||||
t1.update(df)
|
||||
|
||||
t2 = Table({"a": "integer", "b": "float"})
|
||||
t2.update(df)
|
||||
t2.update(df)
|
||||
|
||||
assert t1.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4, 1, 2, 3, 4],
|
||||
"b": [1.5, 2.5, 3.5, 4.5, 1.5, 2.5, 3.5, 4.5],
|
||||
}
|
||||
|
||||
@mark.skip(reason="Not supported, polars doesnt like input")
|
||||
def test_table_polars_transitive(self):
|
||||
# serialized output -> table -> serialized output
|
||||
records = {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": [1.5, 2.5, 3.5, 4.5],
|
||||
"c": [np.nan, np.nan, "abc", np.nan],
|
||||
"d": [None, True, None, False],
|
||||
"e": [
|
||||
float("nan"),
|
||||
datetime(2019, 7, 11, 12, 30),
|
||||
float("nan"),
|
||||
datetime(2019, 7, 11, 12, 30),
|
||||
],
|
||||
}
|
||||
|
||||
df = pl.DataFrame(records, strict=False)
|
||||
t1 = Table(df)
|
||||
out1 = arrow_bytes_to_polars(t1.view(columns=["a", "b", "c", "d", "e"]))
|
||||
t2 = Table(out1)
|
||||
assert t1.schema() == t2.schema()
|
||||
out2 = t2.view().to_columns()
|
||||
assert t1.view().to_columns() == out2
|
||||
|
||||
# dtype=object should have correct inferred types
|
||||
|
||||
def test_table_polars_object_to_int(self):
|
||||
df = pl.DataFrame({"a": [1, 2, None, 2, None, 3, 4]})
|
||||
table = Table(df)
|
||||
assert table.schema() == {"a": "integer"}
|
||||
assert table.view().to_columns()["a"] == [1, 2, None, 2, None, 3, 4]
|
||||
|
||||
def test_table_polars_object_to_float(self):
|
||||
df = pl.DataFrame({"a": [None, 1, None, 2, None, 3, 4]})
|
||||
table = Table(df)
|
||||
assert table.schema() == {"a": "integer"}
|
||||
assert table.view().to_columns()["a"] == [None, 1.0, None, 2.0, None, 3.0, 4.0]
|
||||
|
||||
def test_table_polars_object_to_bool(self):
|
||||
df = pl.DataFrame({"a": [True, False, True, False, True, False]})
|
||||
table = Table(df)
|
||||
assert table.schema() == {"a": "boolean"}
|
||||
assert table.view().to_columns()["a"] == [True, False, True, False, True, False]
|
||||
|
||||
def test_table_polars_object_to_datetime(self):
|
||||
df = pl.DataFrame(
|
||||
{
|
||||
"a": [
|
||||
datetime(2019, 7, 11, 1, 2, 3),
|
||||
datetime(2019, 7, 12, 1, 2, 3),
|
||||
None,
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
table = Table(df)
|
||||
assert table.schema() == {"a": "datetime"}
|
||||
assert table.view().to_columns()["a"] == [
|
||||
datetime(2019, 7, 11, 1, 2, 3).timestamp() * 1000,
|
||||
datetime(2019, 7, 12, 1, 2, 3).timestamp() * 1000,
|
||||
None,
|
||||
]
|
||||
|
||||
def test_table_polars_object_to_str(self):
|
||||
df = pl.DataFrame({"a": np.array(["abc", "def", None, "ghi"], dtype=object)})
|
||||
table = Table(df)
|
||||
assert table.schema() == {"a": "string"}
|
||||
assert table.view().to_columns()["a"] == ["abc", "def", None, "ghi"]
|
||||
@@ -0,0 +1,130 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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
|
||||
from perspective import PerspectiveError
|
||||
from datetime import date, datetime
|
||||
from pytest import approx, mark, raises
|
||||
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
def date_timestamp(date):
|
||||
return int(datetime.combine(date, datetime.min.time()).timestamp()) * 1000
|
||||
|
||||
|
||||
def compare_delta(received, expected):
|
||||
"""Compare an arrow-serialized row delta by constructing a Table."""
|
||||
tbl = Table(received)
|
||||
assert tbl.view().to_columns() == expected
|
||||
|
||||
|
||||
class TestView(object):
|
||||
def test_view_zero(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
tbl2 = Table(view)
|
||||
view2 = tbl2.view()
|
||||
dimms = view2.dimensions()
|
||||
assert dimms["num_view_rows"] == 2
|
||||
assert dimms["num_view_columns"] == 2
|
||||
assert view2.schema() == {"a": "integer", "b": "integer"}
|
||||
assert view2.to_records() == data
|
||||
|
||||
def test_view_one(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view(group_by=["a"])
|
||||
tbl2 = Table(view)
|
||||
view2 = tbl2.view()
|
||||
|
||||
dimms = view2.dimensions()
|
||||
assert dimms["num_view_rows"] == 3
|
||||
assert dimms["num_view_columns"] == 3
|
||||
assert view2.schema() == {
|
||||
"a (Group by 1)": "integer",
|
||||
"a": "integer",
|
||||
"b": "integer",
|
||||
}
|
||||
|
||||
assert view2.to_records() == [
|
||||
{"a (Group by 1)": None, "a": 4, "b": 6},
|
||||
{"a (Group by 1)": 1, "a": 1, "b": 2},
|
||||
{"a (Group by 1)": 3, "a": 3, "b": 4},
|
||||
]
|
||||
|
||||
def test_view_two(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view(group_by=["a"], split_by=["b"])
|
||||
tbl2 = Table(view)
|
||||
view2 = tbl2.view()
|
||||
dimms = view2.dimensions()
|
||||
assert dimms["num_view_rows"] == 3
|
||||
assert dimms["num_view_columns"] == 5
|
||||
assert view2.schema() == {
|
||||
"a (Group by 1)": "integer",
|
||||
"2|a": "integer",
|
||||
"2|b": "integer",
|
||||
"4|a": "integer",
|
||||
"4|b": "integer",
|
||||
}
|
||||
|
||||
assert view2.to_records() == [
|
||||
{
|
||||
"a (Group by 1)": None,
|
||||
"2|a": 1,
|
||||
"2|b": 2,
|
||||
"4|a": 3,
|
||||
"4|b": 4,
|
||||
},
|
||||
{
|
||||
"a (Group by 1)": 1,
|
||||
"2|a": 1,
|
||||
"2|b": 2,
|
||||
"4|a": None,
|
||||
"4|b": None,
|
||||
},
|
||||
{
|
||||
"a (Group by 1)": 3,
|
||||
"2|a": None,
|
||||
"2|b": None,
|
||||
"4|a": 3,
|
||||
"4|b": 4,
|
||||
},
|
||||
]
|
||||
|
||||
def test_view_two_column_only(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view(split_by=["b"])
|
||||
tbl2 = Table(view)
|
||||
view2 = tbl2.view()
|
||||
dimms = view2.dimensions()
|
||||
assert dimms["num_view_rows"] == 2
|
||||
assert dimms["num_view_columns"] == 4
|
||||
assert view2.schema() == {
|
||||
"2|a": "integer",
|
||||
"2|b": "integer",
|
||||
"4|a": "integer",
|
||||
"4|b": "integer",
|
||||
}
|
||||
|
||||
assert view2.to_records() == [
|
||||
{"2|a": 1, "2|b": 2, "4|a": None, "4|b": None},
|
||||
{"2|a": None, "2|b": None, "4|a": 3, "4|b": 4},
|
||||
]
|
||||
@@ -0,0 +1,417 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 pyarrow as pa
|
||||
from datetime import date, datetime
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestToArrow(object):
|
||||
def test_to_arrow_nones_symmetric(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_big_numbers_symmetric(self):
|
||||
data = {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": [
|
||||
1.7976931348623157e308,
|
||||
1.7976931348623157e308,
|
||||
1.7976931348623157e308,
|
||||
1.7976931348623157e308,
|
||||
],
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_boolean_symmetric(self):
|
||||
data = {"a": [True, False, None, False, True, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "boolean"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_str_symmetric(self):
|
||||
data = {"a": ["a", "b", "c", "d", "e", None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_str_dict(self):
|
||||
data = {
|
||||
"a": ["abcdefg", "abcdefg", "h"],
|
||||
"b": ["aaa", "bbb", "bbb"],
|
||||
"c": ["hello", "world", "world"],
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "string", "b": "string", "c": "string"}
|
||||
arr = tbl.view().to_arrow()
|
||||
|
||||
# assert that we are actually generating dict arrays
|
||||
buf = pa.BufferReader(arr)
|
||||
reader = pa.ipc.open_stream(buf)
|
||||
arrow_table = reader.read_all()
|
||||
arrow_schema = arrow_table.schema
|
||||
|
||||
for name in ("a", "b", "c"):
|
||||
arrow_type = arrow_schema.field(name).type
|
||||
assert pa.types.is_dictionary(arrow_type)
|
||||
|
||||
# assert that data is symmetrical
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_date_symmetric(self):
|
||||
# data = {"a": [date(2019, 7, 11), date(2016, 2, 29), date(2019, 12, 10)]}
|
||||
# tbl = Table(data)
|
||||
tbl = Table({"a": "date"})
|
||||
# data = {"a": map(lambda x: x.getTime(), [date(2019, 7, 11), date(2016, 2, 29), date(2019, 12, 10)])}
|
||||
data = {"a": [date(2019, 7, 11), date(2016, 2, 29), date(2019, 12, 10)]}
|
||||
tbl.update(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
|
||||
def ts(x):
|
||||
return int(datetime.timestamp(x) * 1000)
|
||||
|
||||
assert tbl2.schema() == tbl.schema()
|
||||
assert tbl2.view().to_columns() == {
|
||||
"a": [
|
||||
ts(datetime(2019, 7, 11)),
|
||||
ts(datetime(2016, 2, 29)),
|
||||
ts(datetime(2019, 12, 10)),
|
||||
]
|
||||
}
|
||||
|
||||
def test_to_arrow_date_symmetric_january(self, util):
|
||||
data = {"a": [date(2019, 1, 1), date(2016, 1, 1), date(2019, 1, 1)]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.schema() == tbl.schema()
|
||||
assert tbl2.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(x)
|
||||
for x in [
|
||||
datetime(2019, 1, 1),
|
||||
datetime(2016, 1, 1),
|
||||
datetime(2019, 1, 1),
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
def test_to_arrow_datetime_symmetric(self, util):
|
||||
data = {
|
||||
"a": [
|
||||
datetime(2019, 7, 11, 12, 30),
|
||||
datetime(2016, 2, 29, 11, 0),
|
||||
datetime(2019, 12, 10, 12, 0),
|
||||
]
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "datetime"}
|
||||
arr = tbl.view().to_arrow()
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.schema() == tbl.schema()
|
||||
assert tbl2.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(x)
|
||||
for x in [
|
||||
datetime(2019, 7, 11, 12, 30),
|
||||
datetime(2016, 2, 29, 11, 0),
|
||||
datetime(2019, 12, 10, 12, 0),
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
def test_to_arrow_one_symmetric(self):
|
||||
data = {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": ["a", "b", "c", "d"],
|
||||
"c": [
|
||||
datetime(2019, 7, 11, 12, 0),
|
||||
datetime(2019, 7, 11, 12, 10),
|
||||
datetime(2019, 7, 11, 12, 20),
|
||||
datetime(2019, 7, 11, 12, 30),
|
||||
],
|
||||
}
|
||||
tbl = Table(data)
|
||||
view = tbl.view(group_by=["a"])
|
||||
arrow = view.to_arrow()
|
||||
tbl2 = Table(arrow)
|
||||
assert tbl2.schema() == {
|
||||
"a (Group by 1)": "integer",
|
||||
"a": "integer",
|
||||
"b": "integer",
|
||||
"c": "integer",
|
||||
}
|
||||
d = view.to_columns()
|
||||
d["a (Group by 1)"] = [
|
||||
x[0] if len(x) > 0 else None for x in d.pop("__ROW_PATH__")
|
||||
]
|
||||
assert tbl2.view().to_columns() == d
|
||||
|
||||
def test_to_arrow_two_symmetric(self):
|
||||
data = {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": ["hello", "world", "hello2", "world2"],
|
||||
"c": [datetime(2019, 7, 11, 12, i) for i in range(0, 40, 10)],
|
||||
}
|
||||
tbl = Table(data)
|
||||
view = tbl.view(group_by=["a"], split_by=["b"])
|
||||
arrow = view.to_arrow()
|
||||
tbl2 = Table(arrow)
|
||||
assert tbl2.schema() == {
|
||||
"a (Group by 1)": "integer",
|
||||
"hello|a": "integer",
|
||||
"hello|b": "integer",
|
||||
"hello|c": "integer",
|
||||
"world|a": "integer",
|
||||
"world|b": "integer",
|
||||
"world|c": "integer",
|
||||
"hello2|a": "integer",
|
||||
"hello2|b": "integer",
|
||||
"hello2|c": "integer",
|
||||
"world2|a": "integer",
|
||||
"world2|b": "integer",
|
||||
"world2|c": "integer",
|
||||
}
|
||||
d = view.to_columns()
|
||||
d["a (Group by 1)"] = [
|
||||
x[0] if len(x) > 0 else None for x in d.pop("__ROW_PATH__")
|
||||
]
|
||||
assert tbl2.view().to_columns() == d
|
||||
|
||||
def test_to_arrow_column_only_symmetric(self):
|
||||
data = {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": ["a", "b", "c", "d"],
|
||||
"c": [datetime(2019, 7, 11, 12, i) for i in range(0, 40, 10)],
|
||||
}
|
||||
tbl = Table(data)
|
||||
view = tbl.view(split_by=["a"])
|
||||
arrow = view.to_arrow()
|
||||
tbl2 = Table(arrow)
|
||||
assert tbl2.schema() == {
|
||||
"1|a": "integer",
|
||||
"1|b": "string",
|
||||
"1|c": "datetime",
|
||||
"2|a": "integer",
|
||||
"2|b": "string",
|
||||
"2|c": "datetime",
|
||||
"3|a": "integer",
|
||||
"3|b": "string",
|
||||
"3|c": "datetime",
|
||||
"4|a": "integer",
|
||||
"4|b": "string",
|
||||
"4|c": "datetime",
|
||||
}
|
||||
d = view.to_columns()
|
||||
assert tbl2.view().to_columns() == d
|
||||
|
||||
# start and end row
|
||||
def test_to_arrow_start_row(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_row=3)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"a": data["a"][3:], "b": data["b"][3:]}
|
||||
|
||||
def test_to_arrow_end_row(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(end_row=2)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"a": data["a"][:2], "b": data["b"][:2]}
|
||||
|
||||
def test_to_arrow_start_end_row(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_row=2, end_row=3)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"a": data["a"][2:3], "b": data["b"][2:3]}
|
||||
|
||||
def test_to_arrow_start_end_row_equiv(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_row=2, end_row=2)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"a": [], "b": []}
|
||||
|
||||
def test_to_arrow_start_row_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_row=-1)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_end_row_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(end_row=6)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_start_end_row_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_row=-1, end_row=6)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_start_col(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=1)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"b": data["b"]}
|
||||
|
||||
def test_to_arrow_end_col(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(end_col=1)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"a": data["a"]}
|
||||
|
||||
def test_to_arrow_start_end_col(self):
|
||||
data = {
|
||||
"a": [None, 1, None, 2, 3],
|
||||
"b": [1.5, 2.5, None, 3.5, None],
|
||||
"c": [None, 1, None, 2, 3],
|
||||
"d": [1.5, 2.5, None, 3.5, None],
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "float",
|
||||
"c": "integer",
|
||||
"d": "float",
|
||||
}
|
||||
arr = tbl.view().to_arrow(start_col=1, end_col=3)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {"b": data["b"], "c": data["c"]}
|
||||
|
||||
def test_to_arrow_start_col_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=-1)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_end_col_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(end_col=6)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_start_end_col_invalid(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=-1, end_col=6)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == data
|
||||
|
||||
def test_to_arrow_start_end_col_equiv_row(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=1, end_col=1, start_row=2, end_row=3)
|
||||
tbl2 = Table(arr)
|
||||
# start/end col is a range - thus start=end provides no columns
|
||||
assert tbl2.view().to_columns() == {}
|
||||
|
||||
def test_to_arrow_start_end_col_equiv(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=1, end_col=1)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == {}
|
||||
|
||||
def test_to_arrow_start_end_row_end_col(self):
|
||||
data = {"a": [None, 1, None, 2, 3], "b": [1.5, 2.5, None, 3.5, None]}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
arr = tbl.view().to_arrow(end_col=1, start_row=2, end_row=3)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == tbl.view().to_columns(
|
||||
end_col=1, start_row=2, end_row=3
|
||||
)
|
||||
|
||||
def test_to_arrow_start_end_col_start_row(self):
|
||||
data = {
|
||||
"a": [None, 1, None, 2, 3],
|
||||
"b": [1.5, 2.5, None, 3.5, None],
|
||||
"c": [1.5, 2.5, None, 4.5, None],
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float", "c": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=1, end_col=2, start_row=2)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == tbl.view().to_columns(
|
||||
start_col=1, end_col=2, start_row=2
|
||||
)
|
||||
|
||||
def test_to_arrow_start_end_col_end_row(self):
|
||||
data = {
|
||||
"a": [None, 1, None, 2, 3],
|
||||
"b": [1.5, 2.5, None, 3.5, None],
|
||||
"c": [1.5, 2.5, None, 4.5, None],
|
||||
}
|
||||
tbl = Table(data)
|
||||
assert tbl.schema() == {"a": "integer", "b": "float", "c": "float"}
|
||||
arr = tbl.view().to_arrow(start_col=1, end_col=2, end_row=2)
|
||||
tbl2 = Table(arr)
|
||||
assert tbl2.view().to_columns() == tbl.view().to_columns(
|
||||
start_col=1, end_col=2, end_row=2
|
||||
)
|
||||
|
||||
def test_to_arrow_one_mean(self):
|
||||
data = {"a": [1, 2, 3, 4], "b": ["a", "a", "b", "b"]}
|
||||
|
||||
table = Table(data)
|
||||
view = table.view(group_by=["b"], columns=["a"], aggregates={"a": "mean"})
|
||||
arrow = view.to_arrow()
|
||||
|
||||
table2 = Table(arrow)
|
||||
view2 = table2.view()
|
||||
result = view2.to_columns()
|
||||
|
||||
assert result == {"b (Group by 1)": [None, "a", "b"], "a": [2.5, 1.5, 3.5]}
|
||||
@@ -0,0 +1,32 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestToArrowLZ4(object):
|
||||
def test_to_arrow_lz4_roundtrip(self, superstore):
|
||||
original_tbl = Table(superstore.to_dict(orient="records"))
|
||||
arrow_uncompressed = original_tbl.view().to_arrow(compression=None)
|
||||
|
||||
tbl = Table(arrow_uncompressed)
|
||||
arr = tbl.view().to_arrow(compression="lz4")
|
||||
assert len(arr) < len(arrow_uncompressed)
|
||||
tbl2 = Table(arr)
|
||||
arr2 = tbl2.view().to_arrow(compression=None)
|
||||
assert len(arr2) > len(arr)
|
||||
tbl3 = Table(arr)
|
||||
arr3 = tbl3.view().to_arrow(compression="lz4")
|
||||
assert len(arr3) == len(arr)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestToPolars(object):
|
||||
def test_to_polars(self, superstore):
|
||||
original_tbl = Table(superstore.to_dict(orient="records"))
|
||||
df = original_tbl.view().to_polars()
|
||||
tbl = Table(df)
|
||||
assert tbl.size() == original_tbl.size()
|
||||
df2 = tbl.view().to_polars()
|
||||
assert df.equals(df2)
|
||||
@@ -0,0 +1,536 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 numpy as np
|
||||
import pyarrow as pa
|
||||
import pandas as pd
|
||||
from datetime import date, datetime
|
||||
from pytest import mark
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestUpdate(object):
|
||||
@mark.skip(reason="Fix for null values update yet to be implemented")
|
||||
def test_update_with_missing_or_null_values(self):
|
||||
tbl = Table([{"a": "1", "b": "2"}, {"a": "3", "b": "4"}], index="a")
|
||||
tbl.update([{"a": "1", "b": None}])
|
||||
assert tbl.view().to_records() == [{"a": "1", "b": None}, {"a": "3", "b": "4"}]
|
||||
|
||||
data = pd.DataFrame({"a": ["1"], "b": [None]})
|
||||
|
||||
arrow_table = pa.Table.from_pandas(data, preserve_index=False)
|
||||
|
||||
tbl.update(arrow_table)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.view().to_records() == [{"a": "1", "b": ""}, {"a": "3", "b": "4"}]
|
||||
|
||||
def test_update_from_schema(self):
|
||||
tbl = Table({"a": "string", "b": "integer"})
|
||||
tbl.update([{"a": "abc", "b": 123}])
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 123}]
|
||||
|
||||
def test_update_columnar_from_schema(self):
|
||||
tbl = Table({"a": "string", "b": "integer"})
|
||||
tbl.update({"a": ["abc"], "b": [123]})
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 123}]
|
||||
|
||||
def test_update_csv(self):
|
||||
tbl = Table({"a": "string", "b": "integer"})
|
||||
|
||||
view = tbl.view()
|
||||
tbl.update("a,b\nxyz,123\ndef,100000000")
|
||||
|
||||
assert view.to_columns() == {"a": ["xyz", "def"], "b": [123, 100000000]}
|
||||
|
||||
def test_update_csv_indexed(self):
|
||||
tbl = Table({"a": "string", "b": "float"}, index="a")
|
||||
|
||||
view = tbl.view()
|
||||
tbl.update("a,b\nxyz,1.23456718\ndef,100000000.1")
|
||||
|
||||
assert view.to_columns() == {
|
||||
"a": ["def", "xyz"],
|
||||
"b": [100000000.1, 1.23456718],
|
||||
}
|
||||
|
||||
tbl.update("a,b\nxyz,0.00000001\ndef,1234.5678\nefg,100.2")
|
||||
|
||||
assert view.to_columns() == {
|
||||
"a": ["def", "efg", "xyz"],
|
||||
"b": [1234.5678, 100.2, 0.00000001],
|
||||
}
|
||||
|
||||
def test_update_append(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}])
|
||||
tbl.update([{"a": "def", "b": 456}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": 123},
|
||||
{"a": "def", "b": 456},
|
||||
]
|
||||
|
||||
def test_update_partial(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
tbl.update([{"a": "abc", "b": 456}])
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
def test_update_partial_add_row(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
tbl.update([{"a": "abc", "b": 456}, {"a": "def"}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": 456},
|
||||
{"a": "def", "b": None},
|
||||
]
|
||||
|
||||
def test_update_partial_noop(self):
|
||||
tbl = Table([{"a": "abc", "b": 1, "c": 2}], index="a")
|
||||
tbl.update([{"a": "abc", "b": 456}])
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 456, "c": 2}]
|
||||
|
||||
def test_update_partial_unset(self):
|
||||
tbl = Table(
|
||||
[{"a": "abc", "b": 1, "c": 2}, {"a": "def", "b": 3, "c": 4}], index="a"
|
||||
)
|
||||
tbl.update([{"a": "abc"}, {"a": "def", "c": None}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": 1, "c": 2},
|
||||
{"a": "def", "b": 3, "c": None},
|
||||
]
|
||||
|
||||
def test_update_columnar_partial_add_row(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
|
||||
tbl.update({"a": ["abc", "def"], "b": [456, None]})
|
||||
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": 456},
|
||||
{"a": "def", "b": None},
|
||||
]
|
||||
|
||||
def test_update_columnar_partial_noop(self):
|
||||
tbl = Table([{"a": "abc", "b": 1, "c": 2}], index="a")
|
||||
|
||||
# no-op because "c" is not in the update dataset
|
||||
tbl.update({"a": ["abc"], "b": [456]})
|
||||
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 456, "c": 2}]
|
||||
|
||||
def test_update_columnar_partial_unset(self):
|
||||
tbl = Table(
|
||||
[{"a": "abc", "b": 1, "c": 2}, {"a": "def", "b": 3, "c": 4}], index="a"
|
||||
)
|
||||
|
||||
tbl.update({"a": ["abc"], "b": [None]})
|
||||
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": None, "c": 2},
|
||||
{"a": "def", "b": 3, "c": 4},
|
||||
]
|
||||
|
||||
def test_update_partial_subcolumn(self):
|
||||
tbl = Table([{"a": "abc", "b": 123, "c": 456}], index="a")
|
||||
tbl.update([{"a": "abc", "c": 1234}])
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 123, "c": 1234}]
|
||||
|
||||
def test_update_partial_subcolumn_dict(self):
|
||||
tbl = Table([{"a": "abc", "b": 123, "c": 456}], index="a")
|
||||
tbl.update({"a": ["abc"], "c": [1234]})
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 123, "c": 1234}]
|
||||
|
||||
def test_update_partial_cols_more_columns_than_table(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
tbl.update([{"a": "abc", "b": 456, "c": 789}])
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
def test_update_columnar_append(self):
|
||||
tbl = Table({"a": ["abc"], "b": [123]})
|
||||
tbl.update({"a": ["def"], "b": [456]})
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": "abc", "b": 123},
|
||||
{"a": "def", "b": 456},
|
||||
]
|
||||
|
||||
def test_update_columnar_partial(self):
|
||||
tbl = Table({"a": ["abc"], "b": [123]}, index="a")
|
||||
tbl.update({"a": ["abc"], "b": [456]})
|
||||
assert tbl.view().to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
# make sure already created views are notified properly
|
||||
def test_update_from_schema_notify(self):
|
||||
tbl = Table({"a": "string", "b": "integer"})
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 0
|
||||
tbl.update([{"a": "abc", "b": 123}])
|
||||
assert view.to_records() == [{"a": "abc", "b": 123}]
|
||||
|
||||
def test_update_columnar_from_schema_notify(self):
|
||||
tbl = Table({"a": "string", "b": "integer"})
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 0
|
||||
tbl.update({"a": ["abc"], "b": [123]})
|
||||
assert view.to_records() == [{"a": "abc", "b": 123}]
|
||||
|
||||
def test_update_append_notify(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}])
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 1
|
||||
tbl.update([{"a": "def", "b": 456}])
|
||||
assert view.to_records() == [{"a": "abc", "b": 123}, {"a": "def", "b": 456}]
|
||||
|
||||
def test_update_partial_notify(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 1
|
||||
tbl.update([{"a": "abc", "b": 456}])
|
||||
assert view.to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
def test_update_partial_cols_not_in_schema_notify(self):
|
||||
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 1
|
||||
tbl.update([{"a": "abc", "b": 456, "c": 789}])
|
||||
assert view.to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
def test_update_columnar_append_notify(self):
|
||||
tbl = Table({"a": ["abc"], "b": [123]})
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 1
|
||||
tbl.update({"a": ["def"], "b": [456]})
|
||||
assert view.to_records() == [{"a": "abc", "b": 123}, {"a": "def", "b": 456}]
|
||||
|
||||
def test_update_columnar_partial_notify(self):
|
||||
tbl = Table({"a": ["abc"], "b": [123]}, index="a")
|
||||
view = tbl.view()
|
||||
assert view.num_rows() == 1
|
||||
tbl.update({"a": ["abc"], "b": [456]})
|
||||
assert view.to_records() == [{"a": "abc", "b": 456}]
|
||||
|
||||
# bool
|
||||
|
||||
def test_update_bool_from_schema(self):
|
||||
bool_data = [{"a": True, "b": False}, {"a": True, "b": True}]
|
||||
tbl = Table({"a": "boolean", "b": "boolean"})
|
||||
tbl.update(bool_data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.view().to_records() == bool_data
|
||||
|
||||
def test_update_bool_str_from_schema(self):
|
||||
bool_data = [{"a": "True", "b": "False"}, {"a": "True", "b": "True"}]
|
||||
tbl = Table({"a": "boolean", "b": "boolean"})
|
||||
tbl.update(bool_data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": True, "b": False},
|
||||
{"a": True, "b": True},
|
||||
]
|
||||
|
||||
@mark.skip(reason="We may not want to support this")
|
||||
def test_update_bool_str_all_formats_from_schema(self):
|
||||
bool_data = [
|
||||
{"a": "True", "b": "False"},
|
||||
{"a": "t", "b": "f"},
|
||||
{"a": "true", "b": "false"},
|
||||
{"a": 1, "b": 0},
|
||||
{"a": "on", "b": "off"},
|
||||
]
|
||||
tbl = Table({"a": "boolean", "b": "boolean"})
|
||||
tbl.update(bool_data)
|
||||
assert tbl.size() == 5
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [True, True, True, True, True],
|
||||
"b": [False, False, False, False, False],
|
||||
}
|
||||
|
||||
def test_update_bool_int_from_schema(self):
|
||||
bool_data = [{"a": 1, "b": 0}, {"a": 1, "b": 0}]
|
||||
tbl = Table({"a": "boolean", "b": "boolean"})
|
||||
tbl.update(bool_data)
|
||||
assert tbl.size() == 2
|
||||
assert tbl.view().to_columns() == {"a": [True, True], "b": [False, False]}
|
||||
|
||||
# dates and datetimes
|
||||
def test_update_date(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
tbl.update([{"a": date(2019, 7, 12)}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12))},
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy.
|
||||
def test_update_date_np(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
tbl.update([{"a": np.datetime64(date(2019, 7, 12))}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12))},
|
||||
]
|
||||
|
||||
def test_update_datetime(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": datetime(2019, 7, 12, 11, 0)}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11, 11, 0))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))},
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy.
|
||||
def test_update_datetime_np(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": np.datetime64(datetime(2019, 7, 12, 11, 0))}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11, 11, 0))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))},
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy.
|
||||
def test_update_datetime_np_ts(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": np.datetime64("2019-07-12T11:00")}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11, 11, 0))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))},
|
||||
]
|
||||
|
||||
def test_update_datetime_timestamp_seconds(self, util):
|
||||
ts = util.to_timestamp(datetime(2019, 7, 12, 11, 0, 0))
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": ts}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11, 11, 0))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))},
|
||||
]
|
||||
|
||||
def test_update_datetime_timestamp_ms(self, util):
|
||||
ts = util.to_timestamp(datetime(2019, 7, 12, 11, 0, 0))
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": ts}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 11, 11, 0))},
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))},
|
||||
]
|
||||
|
||||
# partial date & datetime updates
|
||||
|
||||
def test_update_date_partial(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)], "b": [1]}, index="b")
|
||||
tbl.update([{"a": date(2019, 7, 12), "b": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12)), "b": 1}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_date_np_partial(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)], "b": [1]}, index="b")
|
||||
tbl.update([{"a": np.datetime64(date(2019, 7, 12)), "b": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12)), "b": 1}
|
||||
]
|
||||
|
||||
def test_update_datetime_partial(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "b": [1]}, index="b")
|
||||
tbl.update([{"a": datetime(2019, 7, 12, 11, 0), "b": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0)), "b": 1}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_datetime_np_partial(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "b": [1]}, index="b")
|
||||
tbl.update([{"a": np.datetime64(datetime(2019, 7, 12, 11, 0)), "b": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0)), "b": 1}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_datetime_np_ts_partial(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "b": [1]}, index="b")
|
||||
tbl.update([{"a": np.datetime64("2019-07-12T11:00"), "b": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0)), "b": 1}
|
||||
]
|
||||
|
||||
def test_update_datetime_timestamp_seconds_partial(self, util):
|
||||
ts = util.to_timestamp(datetime(2019, 7, 12, 11, 0, 0))
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "idx": [1]}, index="idx")
|
||||
tbl.update([{"a": ts, "idx": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0)), "idx": 1}
|
||||
]
|
||||
|
||||
def test_update_datetime_timestamp_ms_partial(self, util):
|
||||
ts = util.to_timestamp(datetime(2019, 7, 12, 11, 0, 0))
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "idx": [1]}, index="idx")
|
||||
tbl.update([{"a": ts, "idx": 1}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0)), "idx": 1}
|
||||
]
|
||||
|
||||
# updating dates using implicit index
|
||||
|
||||
def test_update_date_partial_implicit(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
tbl.update([{"a": date(2019, 7, 12), "__INDEX__": 0}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12))}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_date_np_partial_implicit(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
tbl.update([{"a": np.datetime64(date(2019, 7, 12)), "__INDEX__": 0}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12))}
|
||||
]
|
||||
|
||||
def test_update_datetime_partial_implicit(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": datetime(2019, 7, 12, 11, 0), "__INDEX__": 0}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_datetime_np_partial_implicit(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": np.datetime64(datetime(2019, 7, 12, 11, 0)), "__INDEX__": 0}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))}
|
||||
]
|
||||
|
||||
@mark.skip # We do not support numpy anymore.
|
||||
def test_update_datetime_np_ts_partial_implicit(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
tbl.update([{"a": np.datetime64("2019-07-12T11:00"), "__INDEX__": 0}])
|
||||
assert tbl.view().to_records() == [
|
||||
{"a": util.to_timestamp(datetime(2019, 7, 12, 11, 0))}
|
||||
]
|
||||
|
||||
# implicit index
|
||||
|
||||
def test_update_implicit_index(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
tbl.update([{"__INDEX__": 0, "a": 3, "b": 15}])
|
||||
assert view.to_records() == [{"a": 3, "b": 15}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_implicit_index_dict_noop(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
|
||||
# "b" does not exist in dataset, so no-op
|
||||
tbl.update(
|
||||
{
|
||||
"__INDEX__": [0],
|
||||
"a": [3],
|
||||
}
|
||||
)
|
||||
assert view.to_records() == [{"a": 3, "b": 2}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_implicit_index_dict_unset_with_null(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
|
||||
# unset because "b" is null
|
||||
tbl.update({"__INDEX__": [0], "a": [3], "b": [None]})
|
||||
assert view.to_records() == [{"a": 3, "b": None}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_implicit_index_multi(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}, {"a": 4, "b": 5}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
tbl.update(
|
||||
[
|
||||
{
|
||||
"__INDEX__": 0,
|
||||
"a": 3,
|
||||
},
|
||||
{"__INDEX__": 2, "a": 5},
|
||||
]
|
||||
)
|
||||
assert view.to_records() == [
|
||||
{"a": 3, "b": 2},
|
||||
{"a": 2, "b": 3},
|
||||
{"a": 5, "b": 5},
|
||||
]
|
||||
|
||||
def test_update_implicit_index_symmetric(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data)
|
||||
view = tbl.view()
|
||||
records = view.to_records(index=True)
|
||||
idx = records[0]["__INDEX__"][0] # XXX: How should we grab this?
|
||||
tbl.update([{"__INDEX__": idx, "a": 3}])
|
||||
assert view.to_records() == [{"a": 3, "b": 2}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_explicit_index(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update([{"a": 1, "b": 3}])
|
||||
assert view.to_records() == [{"a": 1, "b": 3}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_explicit_index_multi(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update([{"a": 1, "b": 3}, {"a": 3, "b": 5}])
|
||||
assert view.to_records() == [
|
||||
{"a": 1, "b": 3},
|
||||
{"a": 2, "b": 3},
|
||||
{"a": 3, "b": 5},
|
||||
]
|
||||
|
||||
def test_update_explicit_index_multi_append(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update([{"a": 1, "b": 3}, {"a": 12, "b": 5}])
|
||||
assert view.to_records() == [
|
||||
{"a": 1, "b": 3},
|
||||
{"a": 2, "b": 3},
|
||||
{"a": 3, "b": 4},
|
||||
{"a": 12, "b": 5},
|
||||
]
|
||||
|
||||
def test_update_explicit_index_multi_append_noindex(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}, {"a": 3, "b": 4}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update([{"a": 1, "b": 3}, {"b": 5}])
|
||||
assert view.to_records() == [
|
||||
{"a": None, "b": 5},
|
||||
{"a": 1, "b": 3},
|
||||
{"a": 2, "b": 3},
|
||||
{"a": 3, "b": 4},
|
||||
]
|
||||
|
||||
def test_update_implicit_index_with_explicit_unset(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update([{"__INDEX__": 1, "b": 3}])
|
||||
assert view.to_records() == [{"a": 1, "b": 3}, {"a": 2, "b": 3}]
|
||||
|
||||
def test_update_implicit_index_with_explicit_set(self):
|
||||
data = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
tbl = Table(data, index="a")
|
||||
view = tbl.view()
|
||||
tbl.update(
|
||||
[{"__INDEX__": 1, "a": 1, "b": 3}]
|
||||
) # should ignore re-specification of pkey
|
||||
assert view.to_records() == [{"a": 1, "b": 3}, {"a": 2, "b": 3}]
|
||||
@@ -0,0 +1,980 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 os
|
||||
import random
|
||||
import uuid
|
||||
import pyarrow as pa
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import date, datetime
|
||||
from pytest import mark
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
SOURCE_STREAM_ARROW = os.path.join(
|
||||
os.path.dirname(__file__), "arrow", "int_float_str.arrow"
|
||||
)
|
||||
SOURCE_FILE_ARROW = os.path.join(
|
||||
os.path.dirname(__file__), "arrow", "int_float_str.arrow"
|
||||
)
|
||||
PARTIAL_ARROW = os.path.join(
|
||||
os.path.dirname(__file__), "arrow", "int_float_str_update.arrow"
|
||||
)
|
||||
DICT_ARROW = os.path.join(os.path.dirname(__file__), "arrow", "dict.arrow")
|
||||
DICT_UPDATE_ARROW = os.path.join(
|
||||
os.path.dirname(__file__), "arrow", "dict_update.arrow"
|
||||
)
|
||||
|
||||
names = ["a", "b", "c", "d"]
|
||||
|
||||
|
||||
class TestUpdateArrow(object):
|
||||
# files
|
||||
|
||||
def test_update_arrow_updates_stream_file(self):
|
||||
tbl = Table({"a": "integer", "b": "float", "c": "string"})
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "integer", "b": "float", "c": "string"}
|
||||
|
||||
with open(SOURCE_FILE_ARROW, mode="rb") as file:
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4] * 2,
|
||||
"b": [1.5, 2.5, 3.5, 4.5] * 2,
|
||||
"c": ["a", "b", "c", "d"] * 2,
|
||||
}
|
||||
|
||||
def test_update_arrow_partial_updates_file(self):
|
||||
tbl = Table({"a": "integer", "b": "float", "c": "string"}, index="a")
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 4
|
||||
|
||||
with open(PARTIAL_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4],
|
||||
"b": [100.5, 2.5, 3.5, 400.5],
|
||||
"c": ["x", "b", "c", "y"],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_dict_file(self):
|
||||
tbl = Table({"a": "string", "b": "string"})
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 5
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None, "abc", None, "update1", "update2"],
|
||||
"b": ["klm", "hij", None, "hij", "klm", "update3", None, "update4"],
|
||||
}
|
||||
|
||||
@mark.skip
|
||||
def test_update_arrow_updates_dict_partial_file(self):
|
||||
tbl = None
|
||||
v = None
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl = Table(src.read(), index="a")
|
||||
v = tbl.view()
|
||||
assert v.num_rows() == 2
|
||||
assert v.to_columns() == {"a": ["abc", "def"], "b": ["klm", "hij"]}
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
v.num_rows() == 4
|
||||
assert v.to_columns() == {
|
||||
"a": ["abc", "def", "update1", "update2"],
|
||||
"b": ["klm", "hij", None, "update4"],
|
||||
}
|
||||
|
||||
# update with file arrow with more columns than in schema
|
||||
|
||||
def test_update_arrow_updates_more_columns_stream_file(self):
|
||||
tbl = Table(
|
||||
{
|
||||
"a": "integer",
|
||||
"b": "float",
|
||||
}
|
||||
)
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "integer", "b": "float"}
|
||||
|
||||
with open(SOURCE_FILE_ARROW, mode="rb") as file:
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4] * 2,
|
||||
"b": [1.5, 2.5, 3.5, 4.5] * 2,
|
||||
}
|
||||
|
||||
def test_update_arrow_partial_updates_more_columns_file(self):
|
||||
tbl = Table({"a": "integer", "c": "string"}, index="a")
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 4
|
||||
|
||||
with open(PARTIAL_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4],
|
||||
"c": ["x", "b", "c", "y"],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_dict_more_columns_file(self):
|
||||
tbl = Table(
|
||||
{
|
||||
"a": "string",
|
||||
}
|
||||
)
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 5
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None, "abc", None, "update1", "update2"]
|
||||
}
|
||||
|
||||
@mark.skip
|
||||
def test_update_arrow_updates_dict_more_columns_partial_file(self):
|
||||
tbl = Table({"a": "string"}, index="a")
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 4
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "update1", "update2"]
|
||||
}
|
||||
|
||||
# update with file arrow with less columns than in schema
|
||||
|
||||
def test_update_arrow_updates_less_columns_stream_file(self):
|
||||
tbl = Table(
|
||||
{
|
||||
"a": "integer",
|
||||
"x": "float",
|
||||
}
|
||||
)
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as file: # b is important -> binary
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.schema() == {"a": "integer", "x": "float"}
|
||||
|
||||
with open(SOURCE_FILE_ARROW, mode="rb") as file:
|
||||
tbl.update(file.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4] * 2,
|
||||
"x": [None for i in range(8)],
|
||||
}
|
||||
|
||||
def test_update_arrow_partial_updates_less_columns_file(self):
|
||||
tbl = Table({"a": "integer", "x": "string"}, index="a")
|
||||
|
||||
with open(SOURCE_STREAM_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 4
|
||||
|
||||
with open(PARTIAL_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4],
|
||||
"x": [None for i in range(4)],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_dict_less_columns_file(self):
|
||||
tbl = Table({"a": "string", "x": "string"})
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 5
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "def", None, "abc", None, "update1", "update2"],
|
||||
"x": [None for i in range(8)],
|
||||
}
|
||||
|
||||
@mark.skip
|
||||
def test_update_arrow_updates_dict_less_columns_partial_file(self):
|
||||
tbl = Table({"a": "string", "x": "string"}, index="a")
|
||||
|
||||
with open(DICT_ARROW, mode="rb") as src:
|
||||
tbl.update(src.read())
|
||||
assert tbl.size() == 4
|
||||
|
||||
with open(DICT_UPDATE_ARROW, mode="rb") as partial:
|
||||
tbl.update(partial.read())
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["abc", "def", "update1", "update2"],
|
||||
"x": [None for i in range(4)],
|
||||
}
|
||||
|
||||
# update int schema with int
|
||||
|
||||
def test_update_arrow_update_int_schema_with_uint8(self, util):
|
||||
array = [random.randint(0, 127) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint8)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint8()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_uint16(self, util):
|
||||
array = [random.randint(0, 32767) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint16)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint16()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_uint32(self, util):
|
||||
array = [random.randint(0, 2000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint32)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_uint64(self, util):
|
||||
array = [random.randint(0, 20000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint64)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_int8(self, util):
|
||||
array = [random.randint(-127, 127) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int8)})
|
||||
|
||||
schema = pa.schema({"a": pa.int8()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_int16(self, util):
|
||||
array = [random.randint(-32767, 32767) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int16)})
|
||||
|
||||
schema = pa.schema({"a": pa.int16()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_int32(self, util):
|
||||
array = [random.randint(-2000000, 2000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int32)})
|
||||
|
||||
schema = pa.schema({"a": pa.int32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_int_schema_with_int64(self, util):
|
||||
array = [random.randint(-20000000, 20000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int64)})
|
||||
|
||||
schema = pa.schema({"a": pa.int64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == [x * 1.0 for x in array]
|
||||
|
||||
# updating float schema with int
|
||||
|
||||
def test_update_arrow_update_float_schema_with_uint8(self, util):
|
||||
array = [random.randint(0, 127) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint8)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint8()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_uint16(self, util):
|
||||
array = [random.randint(0, 32767) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint16)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint16()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_uint32(self, util):
|
||||
array = [random.randint(0, 2000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint32)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_uint64(self, util):
|
||||
array = [random.randint(0, 20000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.uint64)})
|
||||
|
||||
schema = pa.schema({"a": pa.uint64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_int8(self, util):
|
||||
array = [random.randint(-127, 127) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int8)})
|
||||
|
||||
schema = pa.schema({"a": pa.int8()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_int16(self, util):
|
||||
array = [random.randint(-32767, 32767) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int16)})
|
||||
|
||||
schema = pa.schema({"a": pa.int16()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_int32(self, util):
|
||||
array = [random.randint(-2000000, 2000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int32)})
|
||||
|
||||
schema = pa.schema({"a": pa.int32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_int64(self, util):
|
||||
array = [random.randint(-20000000, 20000000) for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.int64)})
|
||||
|
||||
schema = pa.schema({"a": pa.int64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
# updating int schema with float
|
||||
def test_update_arrow_update_int_schema_with_float32(self, util):
|
||||
array = [random.randint(-2000000, 2000000) * 0.5 for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.float32)})
|
||||
|
||||
schema = pa.schema({"a": pa.float32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == [int(x) for x in array]
|
||||
|
||||
def test_update_arrow_update_int_schema_with_float64(self, util):
|
||||
array = [
|
||||
random.randint(-20000000, 20000000) * random.random() for i in range(100)
|
||||
]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.float64)})
|
||||
|
||||
schema = pa.schema({"a": pa.float64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == [int(x) for x in array]
|
||||
|
||||
# updating float schema with float
|
||||
|
||||
def test_update_arrow_update_float_schema_with_float32(self, util):
|
||||
array = [random.randint(-2000000, 2000000) * 0.5 for i in range(100)]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.float32)})
|
||||
|
||||
schema = pa.schema({"a": pa.float32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
def test_update_arrow_update_float_schema_with_float64(self, util):
|
||||
array = [
|
||||
random.randint(-20000000, 20000000) * random.random() for i in range(100)
|
||||
]
|
||||
data = pd.DataFrame({"a": np.array(array, dtype=np.float64)})
|
||||
|
||||
schema = pa.schema({"a": pa.float64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
tbl = Table({"a": "float"})
|
||||
tbl.update(arrow)
|
||||
assert tbl.view().to_columns()["a"] == array
|
||||
|
||||
# updating date schema
|
||||
|
||||
def test_update_arrow_update_date_schema_with_date32(self, util):
|
||||
array = [date(2019, 2, i) for i in range(1, 11)]
|
||||
data = pd.DataFrame({"a": array})
|
||||
|
||||
schema = pa.schema({"a": pa.date32()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
|
||||
tbl = Table({"a": "date"})
|
||||
|
||||
tbl.update(arrow)
|
||||
|
||||
assert tbl.view().to_columns()["a"] == [
|
||||
util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)
|
||||
]
|
||||
|
||||
def test_update_arrow_update_date_schema_with_date64(self, util):
|
||||
array = [date(2019, 2, i) for i in range(1, 11)]
|
||||
data = pd.DataFrame({"a": array})
|
||||
|
||||
schema = pa.schema({"a": pa.date64()})
|
||||
|
||||
arrow = util.make_arrow_from_pandas(data, schema)
|
||||
|
||||
tbl = Table({"a": "date"})
|
||||
|
||||
tbl.update(arrow)
|
||||
|
||||
assert tbl.view().to_columns()["a"] == [
|
||||
util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)
|
||||
]
|
||||
|
||||
def test_update_arrow_update_datetime_schema_with_timestamp(self, util):
|
||||
data = [
|
||||
[datetime(2019, 2, i, 9) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 10) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 11) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 12) for i in range(1, 11)],
|
||||
]
|
||||
|
||||
arrow_data = util.make_arrow(
|
||||
names,
|
||||
data,
|
||||
types=[
|
||||
pa.timestamp("s"),
|
||||
pa.timestamp("ms"),
|
||||
pa.timestamp("us"),
|
||||
pa.timestamp("ns"),
|
||||
],
|
||||
)
|
||||
|
||||
tbl = Table(
|
||||
{
|
||||
"a": "datetime",
|
||||
"b": "datetime",
|
||||
"c": "datetime",
|
||||
"d": "datetime",
|
||||
}
|
||||
)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(d) for d in data[0]],
|
||||
"b": [util.to_timestamp(d) for d in data[1]],
|
||||
"c": [util.to_timestamp(d) for d in data[2]],
|
||||
"d": [util.to_timestamp(d) for d in data[3]],
|
||||
}
|
||||
|
||||
# streams
|
||||
|
||||
def test_update_arrow_updates_int_stream(self, util):
|
||||
data = [list(range(10)) for i in range(4)]
|
||||
arrow_data = util.make_arrow(names, data)
|
||||
tbl = Table({"a": "integer", "b": "integer", "c": "integer", "d": "integer"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": data[0],
|
||||
"b": data[1],
|
||||
"c": data[2],
|
||||
"d": data[3],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_float_stream(self, util):
|
||||
data = [[i for i in range(10)], [i * 1.5 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a", "b"], data)
|
||||
tbl = Table(
|
||||
{
|
||||
"a": "integer",
|
||||
"b": "float",
|
||||
}
|
||||
)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {"a": data[0], "b": data[1]}
|
||||
|
||||
@mark.skip(reason="Decimal128 isn't part of our schema yet")
|
||||
def test_update_arrow_updates_decimal128_stream(self, util):
|
||||
data = [[i * 1000000000 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.decimal128(10)])
|
||||
tbl = Table({"a": "integer"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_update_arrow_updates_bool_stream(self, util):
|
||||
data = [[True if i % 2 == 0 else False for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data)
|
||||
tbl = Table({"a": "boolean"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_update_arrow_updates_date32_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date32()])
|
||||
tbl = Table({"a": "date"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [int(1000 * datetime(2019, 2, i).timestamp()) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_date64_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date64()])
|
||||
tbl = Table({"a": "date"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 2, i)) for i in range(1, 11)]
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_timestamp_all_formats_stream(self, util):
|
||||
data = [
|
||||
[datetime(2019, 2, i, 9) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 10) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 11) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 12) for i in range(1, 11)],
|
||||
]
|
||||
arrow_data = util.make_arrow(
|
||||
names,
|
||||
data,
|
||||
types=[
|
||||
pa.timestamp("s"),
|
||||
pa.timestamp("ms"),
|
||||
pa.timestamp("us"),
|
||||
pa.timestamp("ns"),
|
||||
],
|
||||
)
|
||||
tbl = Table(
|
||||
{"a": "datetime", "b": "datetime", "c": "datetime", "d": "datetime"}
|
||||
)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(d) for d in data[0]],
|
||||
"b": [util.to_timestamp(d) for d in data[1]],
|
||||
"c": [util.to_timestamp(d) for d in data[2]],
|
||||
"d": [util.to_timestamp(d) for d in data[3]],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_string_stream(self, util):
|
||||
data = [[str(i) for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.string()])
|
||||
tbl = Table({"a": "string"})
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 10
|
||||
assert tbl.view().to_columns() == {"a": data[0]}
|
||||
|
||||
def test_update_arrow_updates_dictionary_stream(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table({"a": "string", "b": "string"})
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["a", "b", "b", None],
|
||||
"b": ["x", "y", None, "z"],
|
||||
}
|
||||
|
||||
@mark.skip(reason="Arrow no longer supports partial updates per row")
|
||||
def test_update_arrow_partial_updates_dictionary_stream(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table({"a": "string", "b": "string"}, index="a")
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {"a": [None, "a", "b"], "b": ["z", "x", "y"]}
|
||||
|
||||
@mark.skip
|
||||
def test_update_arrow_partial_updates_dictionary_stream_duplicates(self, util):
|
||||
"""If there are duplicate values in the dictionary, primary keys
|
||||
may be duplicated if the column is used as an index. Skip this test
|
||||
for now - still looking for the best way to fix."""
|
||||
data = [
|
||||
([0, 1, 1, None, 2], ["a", "b", "a"]),
|
||||
([0, 1, None, 2, 1], ["x", "y", "z"]),
|
||||
]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
|
||||
tbl = Table({"a": "string", "b": "string"}, index="a")
|
||||
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {"a": [None, "a", "b"], "b": ["z", "x", "y"]}
|
||||
|
||||
def test_update_arrow_partial_updates_more_columns_dictionary_stream(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
|
||||
tbl = Table({"a": "string"}, index="a")
|
||||
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {"a": [None, "a", "b"]}
|
||||
|
||||
@mark.skip(reason="Arrow no longer supports partial updates per row")
|
||||
def test_update_arrow_partial_updates_less_columns_dictionary_stream(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table({"a": "string", "b": "string", "x": "string"}, index="a")
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [None, "a", "b"],
|
||||
"b": ["z", "x", "y"],
|
||||
"x": [None, None, None],
|
||||
}
|
||||
|
||||
def test_update_arrow_arbitary_order(self, util):
|
||||
data = [[1, 2, 3, 4], ["a", "b", "c", "d"], [1, 2, 3, 4], ["a", "b", "c", "d"]]
|
||||
update_data = [[5, 6], ["e", "f"], [5, 6], ["e", "f"]]
|
||||
arrow = util.make_arrow(["a", "b", "c", "d"], data)
|
||||
update_arrow = util.make_arrow(["c", "b", "a", "d"], update_data)
|
||||
tbl = Table(arrow)
|
||||
assert tbl.schema() == {
|
||||
"a": "integer",
|
||||
"b": "string",
|
||||
"c": "integer",
|
||||
"d": "string",
|
||||
}
|
||||
tbl.update(update_arrow)
|
||||
assert tbl.size() == 6
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4, 5, 6],
|
||||
"b": ["a", "b", "c", "d", "e", "f"],
|
||||
"c": [1, 2, 3, 4, 5, 6],
|
||||
"d": ["a", "b", "c", "d", "e", "f"],
|
||||
}
|
||||
|
||||
# append
|
||||
|
||||
def test_update_arrow_updates_append_int_stream(self, util):
|
||||
data = [list(range(10)) for i in range(4)]
|
||||
arrow_data = util.make_arrow(names, data)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": data[0] + data[0],
|
||||
"b": data[1] + data[1],
|
||||
"c": data[2] + data[2],
|
||||
"d": data[3] + data[3],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_float_stream(self, util):
|
||||
data = [[i for i in range(10)], [i * 1.5 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": data[0] + data[0],
|
||||
"b": data[1] + data[1],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_decimal_stream(self, util):
|
||||
data = [[i * 1000 for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.decimal128(4)])
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {"a": data[0] + data[0]}
|
||||
|
||||
def test_update_arrow_updates_append_bool_stream(self, util):
|
||||
data = [[True if i % 2 == 0 else False for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {"a": data[0] + data[0]}
|
||||
|
||||
def test_update_arrow_updates_append_date32_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
out_data = [datetime(2019, 2, i) for i in range(1, 11)]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date32()])
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(d) for d in out_data + out_data]
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_date64_stream(self, util):
|
||||
data = [[date(2019, 2, i) for i in range(1, 11)]]
|
||||
out_data = [datetime(2019, 2, i) for i in range(1, 11)]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.date64()])
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(d) for d in out_data + out_data]
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_timestamp_all_formats_stream(self, util):
|
||||
data = [
|
||||
[datetime(2019, 2, i, 9) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 10) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 11) for i in range(1, 11)],
|
||||
[datetime(2019, 2, i, 12) for i in range(1, 11)],
|
||||
]
|
||||
arrow_data = util.make_arrow(
|
||||
names,
|
||||
data,
|
||||
types=[
|
||||
pa.timestamp("s"),
|
||||
pa.timestamp("ms"),
|
||||
pa.timestamp("us"),
|
||||
pa.timestamp("ns"),
|
||||
],
|
||||
)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(d) for d in data[0] + data[0]],
|
||||
"b": [util.to_timestamp(d) for d in data[1] + data[1]],
|
||||
"c": [util.to_timestamp(d) for d in data[2] + data[2]],
|
||||
"d": [util.to_timestamp(d) for d in data[3] + data[3]],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_string_stream(self, util):
|
||||
data = [[str(i) for i in range(10)]]
|
||||
arrow_data = util.make_arrow(["a"], data, types=[pa.string()])
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
assert tbl.size() == 20
|
||||
assert tbl.view().to_columns() == {"a": data[0] + data[0]}
|
||||
|
||||
def test_update_arrow_updates_append_dictionary_stream(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["a", "b", "b", None, "a", "b", "b", None],
|
||||
"b": ["x", "y", None, "z", "x", "y", None, "z"],
|
||||
}
|
||||
|
||||
def test_update_arrow_updates_append_dictionary_stream_legacy(self, util):
|
||||
data = [([0, 1, 1, None], ["a", "b"]), ([0, 1, None, 2], ["x", "y", "z"])]
|
||||
arrow_data = util.make_dictionary_arrow(["a", "b"], data, legacy=True)
|
||||
tbl = Table(arrow_data)
|
||||
tbl.update(arrow_data)
|
||||
|
||||
assert tbl.size() == 8
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["a", "b", "b", None, "a", "b", "b", None],
|
||||
"b": ["x", "y", None, "z", "x", "y", None, "z"],
|
||||
}
|
||||
|
||||
# indexed
|
||||
|
||||
def test_update_arrow_partial_indexed(self, util):
|
||||
data = [[1, 2, 3, 4], ["a", "b", "c", "d"]]
|
||||
update_data = [[2, 4], ["x", "y"]]
|
||||
arrow = util.make_arrow(["a", "b"], data)
|
||||
update_arrow = util.make_arrow(["a", "b"], update_data)
|
||||
tbl = Table(arrow, index="a")
|
||||
assert tbl.schema() == {"a": "integer", "b": "string"}
|
||||
tbl.update(update_arrow)
|
||||
assert tbl.size() == 4
|
||||
assert tbl.view().to_columns() == {"a": [1, 2, 3, 4], "b": ["a", "x", "c", "y"]}
|
||||
|
||||
# update specific columns
|
||||
|
||||
def test_update_arrow_specific_column(self, util):
|
||||
data = [[1, 2, 3, 4], ["a", "b", "c", "d"]]
|
||||
update_data = [[2, 3, 4]]
|
||||
arrow = util.make_arrow(["a", "b"], data)
|
||||
update_arrow = util.make_arrow(["a"], update_data)
|
||||
tbl = Table(arrow)
|
||||
assert tbl.schema() == {"a": "integer", "b": "string"}
|
||||
tbl.update(update_arrow)
|
||||
assert tbl.size() == 7
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4, 2, 3, 4],
|
||||
"b": ["a", "b", "c", "d", None, None, None],
|
||||
}
|
||||
|
||||
# try to fuzz column order
|
||||
|
||||
def test_update_arrow_column_order_str(self, util):
|
||||
# use str so it doesn't get promoted
|
||||
data = [["a", "b", "c"] for i in range(10)]
|
||||
names = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
|
||||
names_scrambled = names[::-1]
|
||||
arrow = util.make_arrow(names_scrambled, data)
|
||||
tbl = Table({name: "string" for name in names})
|
||||
tbl.update(arrow)
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {name: data[0] for name in names}
|
||||
|
||||
def test_update_arrow_column_order_int(self, util):
|
||||
data = [[1, 2, 3] for i in range(10)]
|
||||
names = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
|
||||
names_scrambled = names[::-1]
|
||||
arrow = util.make_arrow(names_scrambled, data)
|
||||
tbl = Table({name: "integer" for name in names})
|
||||
tbl.update(arrow)
|
||||
assert tbl.size() == 3
|
||||
assert tbl.view().to_columns() == {name: data[0] for name in names}
|
||||
|
||||
def test_update_arrow_thread_safe_int_index(self, util):
|
||||
data = [["a", "b", "c"] for i in range(10)]
|
||||
data += [[1, 2, 3]]
|
||||
names = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "uid"]
|
||||
arrow = util.make_arrow(names, data)
|
||||
tbl = Table(arrow, index="uid")
|
||||
|
||||
for i in range(100):
|
||||
idx = (1, 2, 3)[random.randint(0, 2)]
|
||||
update_data = [
|
||||
[str(uuid.uuid4()) + str(random.randint(100, 1000000000))],
|
||||
[idx],
|
||||
]
|
||||
update_names = [names[random.randint(0, 9)], "uid"]
|
||||
update_arrow = util.make_arrow(update_names, update_data)
|
||||
tbl.update(update_arrow)
|
||||
|
||||
assert tbl.size() == 3
|
||||
|
||||
def test_update_arrow_thread_safe_datetime_index(self, util):
|
||||
data = [["a", "b", "c"] for i in range(10)]
|
||||
data += [
|
||||
[
|
||||
datetime(2020, 1, 15, 12, 17),
|
||||
datetime(2020, 1, 15, 12, 18),
|
||||
datetime(2020, 1, 15, 12, 19),
|
||||
]
|
||||
]
|
||||
names = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "uid"]
|
||||
arrow = util.make_arrow(names, data)
|
||||
tbl = Table(arrow, index="uid")
|
||||
|
||||
for i in range(100):
|
||||
idx = (
|
||||
datetime(2020, 1, 15, 12, 17),
|
||||
datetime(2020, 1, 15, 12, 18),
|
||||
datetime(2020, 1, 15, 12, 19),
|
||||
)[random.randint(0, 2)]
|
||||
update_data = [
|
||||
[str(uuid.uuid4()) + str(random.randint(100, 1000000000))],
|
||||
[idx],
|
||||
]
|
||||
update_names = [names[random.randint(0, 9)], "uid"]
|
||||
update_arrow = util.make_arrow(update_names, update_data)
|
||||
tbl.update(update_arrow)
|
||||
|
||||
assert tbl.size() == 3
|
||||
|
||||
def test_update_arrow_thread_safe_str_index(self, util):
|
||||
data = [["a", "b", "c"] for i in range(11)]
|
||||
names = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "uid"]
|
||||
arrow = util.make_arrow(names, data)
|
||||
tbl = Table(arrow, index="uid")
|
||||
|
||||
for i in range(100):
|
||||
idx = ("a", "b", "c")[random.randint(0, 2)]
|
||||
update_data = [
|
||||
[str(uuid.uuid4()) + str(random.randint(100, 1000000000))],
|
||||
[idx],
|
||||
]
|
||||
update_names = [names[random.randint(0, 9)], "uid"]
|
||||
update_arrow = util.make_arrow(update_names, update_data)
|
||||
tbl.update(update_arrow)
|
||||
|
||||
assert tbl.size() == 3
|
||||
@@ -0,0 +1,211 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ 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 numpy as np
|
||||
import pandas as pd
|
||||
from datetime import date, datetime
|
||||
from pytest import mark
|
||||
import pytest
|
||||
import perspective as psp
|
||||
|
||||
client = psp.Server().new_local_client()
|
||||
Table = client.table
|
||||
|
||||
|
||||
class TestUpdatePandas(object):
|
||||
def test_update_df(self):
|
||||
tbl = Table({"a": [1, 2, 3, 4]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7, 8]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {"a": [1, 2, 3, 4, 5, 6, 7, 8]}
|
||||
|
||||
def test_update_df_i32_vs_i64(self):
|
||||
tbl = Table({"a": "integer"})
|
||||
|
||||
update_data = pd.DataFrame({"a": np.array([5, 6, 7, 8], dtype="int64")})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {"a": [5, 6, 7, 8]}
|
||||
|
||||
def test_update_df_bool(self):
|
||||
tbl = Table({"a": [True, False, True, False]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [True, False, True, False]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [True, False, True, False, True, False, True, False]
|
||||
}
|
||||
|
||||
def test_update_df_str(self):
|
||||
tbl = Table({"a": ["a", "b", "c", "d"]})
|
||||
|
||||
update_data = pd.DataFrame({"a": ["a", "b", "c", "d"]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": ["a", "b", "c", "d", "a", "b", "c", "d"]
|
||||
}
|
||||
|
||||
def test_update_df_date(self, util):
|
||||
# set_global_serializer(serializer)
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
update_data = pd.DataFrame({"a": [date(2019, 7, 12)]})
|
||||
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(d)
|
||||
for d in [datetime(2019, 7, 11), datetime(2019, 7, 12)]
|
||||
]
|
||||
}
|
||||
|
||||
@mark.skip(reason="this test relies on a lossy conversion from datetime -> date.")
|
||||
def test_update_df_date_timestamp(self, util):
|
||||
tbl = Table({"a": [date(2019, 7, 11)]})
|
||||
|
||||
assert tbl.schema() == {"a": "date"}
|
||||
|
||||
update_data = pd.DataFrame({"a": [datetime(2019, 7, 12, 0, 0, 0)]})
|
||||
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(datetime(2019, 7, 11)),
|
||||
util.to_timestamp(datetime(2019, 7, 12)),
|
||||
]
|
||||
}
|
||||
|
||||
def test_update_df_datetime(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [datetime(2019, 7, 12, 11, 0)]})
|
||||
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(datetime(2019, 7, 11, 11, 0)),
|
||||
util.to_timestamp(datetime(2019, 7, 12, 11, 0)),
|
||||
]
|
||||
}
|
||||
|
||||
def test_update_df_datetime_timestamp_seconds(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [datetime(2019, 7, 12, 11, 0)]})
|
||||
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(datetime(2019, 7, 11, 11, 0)),
|
||||
util.to_timestamp(datetime(2019, 7, 12, 11, 0)),
|
||||
]
|
||||
}
|
||||
|
||||
def test_update_df_datetime_timestamp_ms(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [datetime(2019, 7, 12, 11, 0)]})
|
||||
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [
|
||||
util.to_timestamp(datetime(2019, 7, 11, 11, 0)),
|
||||
util.to_timestamp(datetime(2019, 7, 12, 11, 0)),
|
||||
]
|
||||
}
|
||||
|
||||
def test_update_df_partial(self):
|
||||
tbl = Table({"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"]}, index="b")
|
||||
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7, 8], "b": ["a", "b", "c", "d"]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {"a": [5, 6, 7, 8], "b": ["a", "b", "c", "d"]}
|
||||
|
||||
def test_df_update_index(self):
|
||||
tbl = Table(pd.DataFrame({"a": [1, 2, 3, 4]}))
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7, 8], "__INDEX__": [0, 1, 2, 3]})
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {"a": [5, 6, 7, 8], "index": [0, 1, 2, 3]}
|
||||
|
||||
def test_update_df_partial_implicit(self):
|
||||
tbl = Table({"a": [1, 2, 3, 4]})
|
||||
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7, 8], "__INDEX__": [0, 1, 2, 3]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {"a": [5, 6, 7, 8]}
|
||||
|
||||
def test_update_df_datetime_partial(self, util):
|
||||
tbl = Table({"a": [datetime(2019, 7, 11, 11, 0)], "b": [1]}, index="b")
|
||||
|
||||
update_data = pd.DataFrame({"a": [datetime(2019, 7, 12, 11, 0)], "b": [1]})
|
||||
|
||||
tbl.update(update_data)
|
||||
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [util.to_timestamp(datetime(2019, 7, 12, 11, 0))],
|
||||
"b": [1],
|
||||
}
|
||||
|
||||
def test_update_df_one_col(self):
|
||||
tbl = Table({"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"]})
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7]})
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [1, 2, 3, 4, 5, 6, 7],
|
||||
"b": ["a", "b", "c", "d", None, None, None],
|
||||
}
|
||||
|
||||
def test_update_df_nonseq_partial(self):
|
||||
tbl = Table({"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"]}, index="b")
|
||||
update_data = pd.DataFrame({"a": [5, 6, 7], "b": ["a", "c", "d"]})
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {"a": [5, 2, 6, 7], "b": ["a", "b", "c", "d"]}
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_update_df_with_none_partial(self):
|
||||
tbl = Table({"a": [1, float("nan"), 3], "b": ["a", None, "d"]}, index="b")
|
||||
update_data = pd.DataFrame({"a": [4, 5], "b": ["a", "d"]})
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {
|
||||
"a": [None, 4, 5],
|
||||
"b": [None, "a", "d"],
|
||||
} # pkeys are ordered
|
||||
|
||||
def test_update_df_unset_partial(self):
|
||||
tbl = Table({"a": [1, 2, 3], "b": ["a", "b", "c"]}, index="b")
|
||||
update_data = pd.DataFrame(
|
||||
{"a": pd.Series([None, None], dtype=pd.Int32Dtype()), "b": ["a", "c"]}
|
||||
)
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {"a": [None, 2, None], "b": ["a", "b", "c"]}
|
||||
|
||||
def test_update_df_nan_partial(self):
|
||||
tbl = Table({"a": [1, 2, 3], "b": ["a", "b", "c"]}, index="b")
|
||||
update_data = pd.DataFrame(
|
||||
{"a": pd.Series([None, None], dtype=pd.Int32Dtype()), "b": ["a", "c"]}
|
||||
)
|
||||
tbl.update(update_data)
|
||||
assert tbl.view().to_columns() == {"a": [None, 2, None], "b": ["a", "b", "c"]}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user