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). ┃
#  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛


class VirtualServerHandler:
    """
    An interface for implementing a Perspective `VirtualServer`. It operates
    thusly:

    -   A table is selected by name (validated via `get_hosted_tables`).

    -   The UI will ask the model to create a temporary table with the results
        of querying this table with a specific query `config`, a simple struct
        which reflects the UI configurable fields (see `get_features`).

    -   The UI will query slices of the temporary table as it needs them to
        render. This may be a rectangular slice, a whole column or the entire
        set, and it is returned from teh model via a custom push-only
        struct `PerspectiveColumn` for now, though in the future we will support
        e.g. Polars and other arrow-native formats directly.

    -   The UI will delete its own temporary tables via `view_delete` but it is
        ok for them to die intermittently, the UI will recover automatically.
    """

    def get_features(self):
        """
        [OPTIONAL] Toggle UI features through data model support. For example,
        setting `"group_by": False` would hide the "Group By" UI control, as
        well as prevent this field from appearing in `config` dicts later
        provided to `table_make_view`.

        This API defaults to just "columns", e.g. a simple flat datagrid in
        which you can just scroll, select and format columns.

        # Example

        ```python
        return {
            "group_by": True,
            "split_by": True,
            "sort": True,
            "expressions": True,
            "filter_ops": {
                "integer": ["==", "<"],
            },
            "aggregates": {
                "string": ["count"],
                "float": ["count", "sum"],
            },
        }
        ```
        """

        pass

    def get_hosted_tables(self) -> list[str]:
        """
        List of `Table` names available to query from.
        """

        pass

    def table_schema(self, table_name):
        """
        Get the _Perspective Schema_ for a `Table`, a mapping of column name to
        Perspective column types, a simplified set of six visually-relevant
        types mapped from DuckDB's much richer type system. Optionally,
        a model may also implement `view_schema` which describes temporary
        tables, but for DuckDB this method is identical.
        """

        pass

    def table_size(self, table_name):
        """
        Get a table's row count. Optionally, a model may also implement the
        `view_size` method to get the row count for temporary tables, but for
        DuckDB this method is identical.
        """

        pass

    def view_schema(self, view_name, config):
        return self.table_schema(view_name)

    def view_size(self, view_name):
        return self.table_size(view_name)

    def table_make_view(self, table_name, view_name, config):
        """
        Create a temporary table `view_name` from the results of querying
        `table_name` with a query configuration `config`.
        """

        pass

    def table_describe(self, table_name, config):
        """
        Validate a complete query configuration `config` against `table_name`
        and report the schema `table_make_view` would create, without creating
        it.
        """

        pass

    def view_delete(self, view_name):
        """
        Delete a temporary table. The UI will do this automatically, and it
        can recover.
        """

        pass

    def view_get_min_max(self, view_name, column_name, config):
        """
        [OPTIONAL] Get the min and max values of a column in a view.
        Returns a tuple of (min, max) as native Python values.
        """

        pass

    def view_get_data(self, view_name, config, viewport, data):
        """
        Serialize a rectangular slice `viewport` from temporary table
        `view_name`, into the `PerspectiveColumn` serialization API injected
        via `data`. The push-only `PerspectiveColumn` type can handle casting
        Python types as input, but once a type is pushed to a column name it
        must not be changed.
        """

        pass



def describe_via_make_view(handler, table_name, config):
    """A `table_describe` that builds a temporary table, reads its schema and
    deletes it."""
    view_name = f"__psp_describe_{id(config)}__"
    try:
        handler.table_make_view(table_name, view_name, config)
    except Exception as e:
        return {"config_error": str(e)}

    try:
        view_schema = handler.view_schema(view_name, config)
    finally:
        handler.view_delete(view_name)

    expression_schema = {
        name: view_schema[name]
        for name in config.get("expressions", {})
        if name in view_schema
    }

    return {"expression_schema": expression_schema, "view_schema": view_schema}


def sql_table_describe(sql_builder, table_name, config, describe_query, schema=None):
    """The `table_describe` flow shared by the SQL models."""
    expression_schema = {}
    query = sql_builder.expressions_describe(table_name, config)
    if query is not None:
        try:
            expression_schema = describe_query(query)
        except Exception as error:
            expression_errors = {}
            for name, expression in config.get("expressions", {}).items():
                try:
                    single = describe_query(
                        sql_builder.expression_describe(table_name, expression)
                    )
                    expression_schema[name] = next(iter(single.values()))
                except Exception as e:
                    expression_errors[name] = {
                        "error_message": str(e),
                        "line": 0,
                        "column": 0,
                    }

            if not expression_errors:
                return {"config_error": str(error)}

            return {
                "expression_schema": expression_schema,
                "expression_errors": expression_errors,
            }

    view_schema = {}
    query = sql_builder.table_describe(table_name, config, schema)
    if query is not None:
        try:
            view_schema = describe_query(query)
        except Exception as error:
            return {"config_error": str(error)}

    return {"expression_schema": expression_schema, "view_schema": view_schema}