Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Submit tornado write_message to IOLoop, add websocket tests in Python #1156

Merged
merged 2 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpp/perspective/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD)
find_package( Python ${PSP_PYTHON_VERSION} REQUIRED COMPONENTS Interpreter Development)
link_directories( ${Python_LIBRARY_DIRS} )
endif()
message("${Cyan}Using Python_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}, Python_LIBRARIES: ${Python_LIBRARIES} ${ColorReset}")
message("${Cyan}Using Python ${Python_VERSION}, \nPython_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}, \nPython_LIBRARIES: ${Python_LIBRARIES}, \nPython_EXECUTABLE: ${Python_EXECUTABLE} ${ColorReset}")
include_directories( ${Python_INCLUDE_DIRS} )

if(MACOS)
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/examples/remote.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script>
<script src="https://unpkg.com/@finos/perspective"></script>

<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.dark.css">
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.css">

<style>
perspective-viewer{position:absolute;top:0;left:0;right:0;bottom:0;}
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/examples/streaming.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script>
<script src="https://unpkg.com/@finos/perspective"></script>

<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.dark.css">
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.css">


<style>
Expand Down
7 changes: 7 additions & 0 deletions python/perspective/perspective/tests/table/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
import json
import random
import pytest

import tornado
from tornado import gen
from tornado.websocket import websocket_connect
from datetime import datetime

from ...table import Table
from ...manager import PerspectiveManager
from ...manager.manager_internal import DateTimeEncoder
from ...tornado_handler import PerspectiveTornadoHandler


data = {
"a": [i for i in range(10)],
"b": [i * 1.5 for i in range(10)],
"c": [str(i) for i in range(10)],
"d": [datetime(2020, 3, i, i, 30, 45) for i in range(1, 11)],
}

MANAGER = PerspectiveManager()

APPLICATION = tornado.web.Application(
[
(
r"/websocket",
PerspectiveTornadoHandler,
{"manager": MANAGER, "check_origin": True},
)
]
)


@pytest.fixture(scope="module")
def app():
return APPLICATION


class TestPerspectiveTornadoHandler(object):
def setup_method(self):
"""Flush manager state before each test method execution."""
MANAGER._tables = {}
MANAGER._views = {}

@gen.coroutine
def websocket_client(self, port):
"""Connect and initialize a websocket client connection to the
Perspective tornado server.

If the initialization response is incorrect, raise an `AssertionError`.
"""
client = yield websocket_connect(
"ws://127.0.0.1:{0}/websocket".format(port)
)

yield client.write_message(json.dumps({"id": -1, "cmd": "init"}))

response = yield client.read_message()

assert json.loads(response) == {"id": -1, "data": None}

# Compatibility with Python < 3.3
raise gen.Return(client)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_init(self, app, http_client, http_port):
"""Using Tornado's websocket client, test the websocket provided by
PerspectiveTornadoHandler.

All test methods must import `app`, `http_client`, and `http_port`,
otherwise a mysterious timeout will occur."""
yield self.websocket_client(http_port)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_table_method(self, app, http_client, http_port):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"name": table_name,
"cmd": "table_method",
"method": "schema",
"args": [],
}
)
)

response = yield client.read_message()

assert json.loads(response) == {
"id": 0,
"data": {
"a": "integer",
"b": "float",
"c": "string",
"d": "datetime",
},
}

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_dict(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_dict",
"args": [],
}
)
)

response = yield client.read_message()

assert response == json.dumps(
{"id": 1, "data": data}, cls=DateTimeEncoder
)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_dict_one_sided(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
"config": {
"row_pivots": ["c"]
}
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_dict",
"args": [],
}
)
)

response = yield client.read_message()

assert response == json.dumps(
{
"id": 1,
"data": {
"__ROW_PATH__": [[]] + [[item] for item in data["c"]],
"a": [sum(data["a"])] + data["a"],
"b": [sum(data["b"])] + data["b"],
"c": [10] + [1 for i in range(10)],
"d": [10] + [1 for i in range(10)],
}
}, cls=DateTimeEncoder
)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_arrow(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_arrow",
"args": [],
}
)
)

response = yield client.read_message()

assert json.loads(response) == {
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_arrow",
"args": [],
"is_transferable": True
}

binary = yield client.read_message()
new_table = Table(binary)
assert new_table.view().to_dict() == data
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def post(self, message, binary=False):
message (:obj:`str`): a JSON-serialized string containing a message to the
front-end `perspective-viewer`.
'''
self.write_message(message, binary)
loop = IOLoop.current()
loop.add_callback(self.write_message, message, binary)

def on_close(self):
'''Remove the views associated with the client when the websocket
Expand Down
1 change: 1 addition & 0 deletions python/perspective/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
'pytest>=4.3.0',
'pytest-cov>=2.6.1',
'pytest-check-links',
'pytest-tornado',
'pytz>=2018.9',
'Sphinx>=1.8.4',
'sphinx-markdown-builder>=0.5.2',
Expand Down