perspective-python 5.6.0

Interactive data grid, pivot table and charts for large, real-time and streaming datasets, with a Jupyter widget and WebSocket server.
Documentation
#  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
#  ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
#  ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
#  ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
#  ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
#  ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
#  ┃ 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 perspective import Server

import random
import threading


PARENTS = {
    "flat": dict(filter=[["y", ">", 2]]),
    "group_by": dict(
        group_by=["g"],
        columns=["x", "y"],
        aggregates={"x": "sum", "y": "mean"},
        group_rollup_mode="flat",
    ),
    "split_by": dict(
        group_by=["g"],
        split_by=["s"],
        columns=["x"],
        group_rollup_mode="flat",
    ),
}


def _row(rand, i):
    return {
        "id": i,
        "g": rand.choice(["a", "b", "c", "d"]),
        "s": rand.choice(["Mon", "Tue", "Wed"]),
        "x": rand.randint(0, 4000) / 4,
        "y": rand.randint(0, 9),
    }


def _key(row):
    return sorted((k, str(v)) for k, v in row.items())


def _run(config):
    rand = random.Random(7)
    client = Server().new_local_client()
    table = client.table(
        {"id": "integer", "g": "string", "s": "string", "x": "float", "y": "integer"},
        index="id",
    )

    table.update([_row(rand, i) for i in range(60)])
    view = table.view(**config)
    derived = client.table(view)
    child = derived.view()
    errors = []
    done = threading.Event()

    def read():
        try:
            while not done.is_set():
                child.to_columns()
                derived.size()
        except Exception as e:
            errors.append(e)

    readers = [threading.Thread(target=read) for _ in range(4)]
    for reader in readers:
        reader.start()

    try:
        for _ in range(500):
            if rand.random() < 0.2:
                table.remove([rand.randint(0, 59) for _ in range(3)])
            else:
                table.update([_row(rand, rand.randint(0, 59)) for _ in range(4)])
    finally:
        done.set()
        for reader in readers:
            reader.join()

    assert errors == []
    fresh = client.table(view)
    expected = fresh.view().to_records()
    actual = child.to_records()
    assert sorted(map(_key, actual)) == sorted(map(_key, expected))


class TestDerivedTableThreaded(object):
    def test_flat_parent(self):
        _run(PARENTS["flat"])

    def test_group_by_parent(self):
        _run(PARENTS["group_by"])

    def test_split_by_parent(self):
        _run(PARENTS["split_by"])