# Implementing a custom Virtual Server

You can connect Perspective to any data source by subclassing
`VirtualServerHandler` and wrapping it with `VirtualServer`.

For background on virtual servers, see the
[Virtual Servers overview](../../../explanation/virtual_servers.md).

## Example

```python
from perspective import VirtualServerHandler, VirtualServer

class MyModel(VirtualServerHandler):
    def get_features(self):
        return {
            "group_by": True,
            "split_by": False,
            "sort": True,
            "filter_ops": {
                "string": ["==", "!=", "contains"],
                "float": ["==", "!=", ">", "<"],
            },
            "aggregates": {
                "float": ["sum", "avg", "count"],
                "string": ["count"],
            },
        }

    def get_hosted_tables(self):
        return ["my_table"]

    def table_schema(self, table_name):
        return {"name": "string", "price": "float"}

    def table_size(self, table_name):
        return 1000

    def table_make_view(self, table_name, view_id, config):
        # Translate `config` (group_by, sort, filter, etc.) into a
        # query against your data source. Store the query keyed by
        # `view_id` for later data retrieval.
        pass

    def view_delete(self, view_id):
        # Clean up resources for this view
        pass

    def view_get_data(self, view_id, start_row, end_row, start_col, end_col, ctx):
        # Execute the stored query with the given row/column window.
        # Push results via `ctx`.
        pass
```

The `VirtualServer` instance can then be passed to a Tornado, Starlette, or
AIOHTTP handler just like a regular `Server`:

```python
from perspective.handlers.tornado import PerspectiveTornadoHandler

app = tornado.web.Application([
    (r"/websocket", PerspectiveTornadoHandler, {"perspective_server": VirtualServer(MyModel)}),
])
```
