diff --git a/rust/perspective-python/perspective/__init__.py b/rust/perspective-python/perspective/__init__.py index 2ce0087fa3..ca09995ecd 100644 --- a/rust/perspective-python/perspective/__init__.py +++ b/rust/perspective-python/perspective/__init__.py @@ -71,8 +71,11 @@ def handle_request(bytes): def handle_response(bytes): client.handle_response(bytes) + def handle_close(): + session.close() + session = server.new_session(handle_response) - client = Client(handle_request) + client = Client(handle_request, handle_close) return client diff --git a/rust/perspective-python/perspective/psp_cffi.py b/rust/perspective-python/perspective/psp_cffi.py index 851c82072e..c9f62027b3 100644 --- a/rust/perspective-python/perspective/psp_cffi.py +++ b/rust/perspective-python/perspective/psp_cffi.py @@ -77,7 +77,7 @@ def __init__(self, server: "ServerBase"): self._server: "ServerBase" = server self._session_id = lib.psp_new_session(server._server) - def __del__(self): + def close(self): lib.psp_close_session(self._server._server, self._session_id) def handle_request(self, bytes_msg): diff --git a/rust/perspective-python/perspective/tests/core/test_async.py b/rust/perspective-python/perspective/tests/core/test_async.py index 691f8de396..812af3d6e6 100644 --- a/rust/perspective-python/perspective/tests/core/test_async.py +++ b/rust/perspective-python/perspective/tests/core/test_async.py @@ -48,7 +48,6 @@ class TestAsync(object): @classmethod def setup_class(cls): cls.loop = tornado.ioloop.IOLoop() - cls.loop.make_current() cls.thread = threading.Thread(target=cls.loop.start) cls.thread.daemon = True cls.thread.start() @@ -56,7 +55,6 @@ def setup_class(cls): @classmethod def teardown_class(cls): cls.loop.add_callback(lambda: tornado.ioloop.IOLoop.current().stop()) - cls.loop.clear_current() cls.thread.join() cls.loop.close(all_fds=True) diff --git a/rust/perspective-python/perspective/tests/server/test_server.py b/rust/perspective-python/perspective/tests/server/test_server.py index e001ad7c1f..69abd51dc9 100644 --- a/rust/perspective-python/perspective/tests/server/test_server.py +++ b/rust/perspective-python/perspective/tests/server/test_server.py @@ -43,6 +43,12 @@ def test_server_host_table(self): table2 = client.open_table("table1") assert table2.schema() == {"a": "integer", "b": "string"} + def test_session_close(self): + server = Server() + client = Client.from_server(server) + client.table(data) + client.terminate() + def test_server_host(self): server = Server() client = Client.from_server(server) diff --git a/rust/perspective-python/perspective/tests/server/test_session.py b/rust/perspective-python/perspective/tests/server/test_session.py index 7764c52ed8..45a6706036 100644 --- a/rust/perspective-python/perspective/tests/server/test_session.py +++ b/rust/perspective-python/perspective/tests/server/test_session.py @@ -32,7 +32,7 @@ def handle_response(bytes): sub_client.handle_response(bytes) sub_session = ProxySession(client, handle_response) - sub_client = Client(handle_request) + sub_client = Client(handle_request, sub_session.close) table = sub_client.table(data, name="table1") assert table.schema() == {"a": "integer", "b": "string"} @@ -47,7 +47,7 @@ def handle_response(bytes): sub_client.handle_response(bytes) sub_session = ProxySession(client, handle_response) - sub_client = Client(handle_request) + sub_client = Client(handle_request, sub_session.close) table = sub_client.table(data, name="table1") table2 = client.open_table("table1") assert table.size() == 3 diff --git a/rust/perspective-python/perspective/tests/table/test_table_pandas.py b/rust/perspective-python/perspective/tests/table/test_table_pandas.py index 258c4483d8..23e8eb5357 100644 --- a/rust/perspective-python/perspective/tests/table/test_table_pandas.py +++ b/rust/perspective-python/perspective/tests/table/test_table_pandas.py @@ -117,7 +117,7 @@ def test_table_time_series(self, util): @mark.skip(reason="pyarrow dataframe does not support date inference") def test_table_dataframe_infer_date(self, util): - data = util.make_dataframe(freq="M") + data = util.make_dataframe(freq="ME") tbl = Table(data) assert tbl.size() == 10 @@ -143,7 +143,7 @@ def test_table_dataframe_infer_date(self, util): ] def test_table_dataframe_infer_date_fixed(self, util): - data = util.make_dataframe(freq="M") + data = util.make_dataframe(freq="ME") tbl = Table(data) assert tbl.size() == 10 @@ -169,7 +169,7 @@ def test_table_dataframe_infer_date_fixed(self, util): ] def test_table_dataframe_infer_time(self, util): - data = util.make_dataframe(freq="H") + data = util.make_dataframe(freq="h") tbl = Table(data) assert tbl.size() == 10 @@ -196,7 +196,7 @@ def test_table_dataframe_infer_time(self, util): @mark.skip(reason="pyarrow dataframe does not support date inference") def test_table_dataframe_year_start_index(self, util): - data = util.make_dataframe(freq="AS") + data = util.make_dataframe(freq="YS") tbl = Table(data) assert tbl.size() == 10 @@ -222,7 +222,7 @@ def test_table_dataframe_year_start_index(self, util): ] def test_table_dataframe_year_start_index_fixed(self, util): - data = util.make_dataframe(freq="AS") + data = util.make_dataframe(freq="YS") tbl = Table(data) assert tbl.size() == 10 @@ -249,7 +249,7 @@ def test_table_dataframe_year_start_index_fixed(self, util): @mark.skip(reason="pyarrow dataframe does not support date inference") def test_table_dataframe_quarter_index(self, util): - data = util.make_dataframe(size=4, freq="Q") + data = util.make_dataframe(size=4, freq="QE") tbl = Table(data) assert tbl.size() == 4 @@ -269,7 +269,7 @@ def test_table_dataframe_quarter_index(self, util): ] def test_table_dataframe_quarter_index_fixed(self, util): - data = util.make_dataframe(size=4, freq="Q") + data = util.make_dataframe(size=4, freq="QE") tbl = Table(data) assert tbl.size() == 4 diff --git a/rust/perspective-python/perspective/viewer/viewer_traitlets.py b/rust/perspective-python/perspective/viewer/viewer_traitlets.py index 61b01c23aa..1a2e0a1017 100644 --- a/rust/perspective-python/perspective/viewer/viewer_traitlets.py +++ b/rust/perspective-python/perspective/viewer/viewer_traitlets.py @@ -52,7 +52,7 @@ class PerspectiveTraitlets(HasTraits): table_name = Unicode(None, allow_none=True).tag(sync=True) server = Bool(False).tag(sync=True) - binding_mode = Enum(("server", "client-server"), default="server").tag(sync=True) + binding_mode = Enum(("server", "client-server")).tag(default="server", sync=True) title = Unicode(None, allow_none=True).tag(sync=True) version = Unicode(__version__).tag(sync=True) diff --git a/rust/perspective-python/src/client/client_sync.rs b/rust/perspective-python/src/client/client_sync.rs index abb51ef11c..1548d1203b 100644 --- a/rust/perspective-python/src/client/client_sync.rs +++ b/rust/perspective-python/src/client/client_sync.rs @@ -84,8 +84,8 @@ pub struct Client(PyClient); #[pymethods] impl Client { #[new] - pub fn new(callback: Py) -> PyResult { - let client = PyClient::new(callback); + pub fn new(handle_request: Py, close_cb: Py) -> PyResult { + let client = PyClient::new(handle_request, close_cb); Ok(Client(client)) } @@ -124,6 +124,11 @@ impl Client { pub fn set_loop_callback(&self, loop_cb: Py) -> PyResult<()> { self.0.set_loop_cb(loop_cb).block_on() } + + #[doc = crate::inherit_docs!("client/terminate.md")] + pub fn terminate(&self, py: Python<'_>) -> PyResult> { + self.0.terminate(py).block_on() + } } #[doc = crate::inherit_docs!("table.md")] @@ -140,8 +145,8 @@ impl Table { } #[doc = crate::inherit_docs!("table/get_client.md")] - pub fn get_client(&self, loop_cb: Option>) -> Client { - Client(self.0.get_client(loop_cb).block_on()) + pub fn get_client(&self) -> Client { + Client(self.0.get_client().block_on()) } #[doc = crate::inherit_docs!("table/get_client.md")] diff --git a/rust/perspective-python/src/client/python.rs b/rust/perspective-python/src/client/python.rs index be80acc9ed..63f64a7bf4 100644 --- a/rust/perspective-python/src/client/python.rs +++ b/rust/perspective-python/src/client/python.rs @@ -32,6 +32,7 @@ use pythonize::depythonize_bound; pub struct PyClient { pub(crate) client: Client, loop_cb: Arc>>>, + close_cb: Py, } #[extend::ext] @@ -263,7 +264,7 @@ fn pandas_to_arrow_bytes<'py>( } impl PyClient { - pub fn new(handle_request: Py) -> Self { + pub fn new(handle_request: Py, handle_close: Py) -> Self { let client = Client::new_with_callback({ move |msg| { clone!(handle_request); @@ -280,6 +281,7 @@ impl PyClient { PyClient { client, loop_cb: Arc::default(), + close_cb: handle_close, } } @@ -359,6 +361,10 @@ impl PyClient { *self.loop_cb.write().await = Some(loop_cb); Ok(()) } + + pub async fn terminate(&self, py: Python<'_>) -> PyResult> { + self.close_cb.call0(py) + } } #[derive(Clone)] @@ -374,10 +380,11 @@ impl PyTable { self.table.get_index() } - pub async fn get_client(&self, loop_cb: Option>) -> PyClient { + pub async fn get_client(&self) -> PyClient { PyClient { client: self.table.get_client(), - loop_cb: Arc::new(RwLock::new(loop_cb)), + loop_cb: self.client.loop_cb.clone(), + close_cb: self.client.close_cb.clone(), } } diff --git a/rust/perspective/src/lib.rs b/rust/perspective/src/lib.rs index e411a5af6b..1edb79982f 100644 --- a/rust/perspective/src/lib.rs +++ b/rust/perspective/src/lib.rs @@ -33,10 +33,23 @@ //! .iter() //! .map(|x| Some(x.to_string())) //! .collect(); - +//! //! let view = table.view(Some(view_config)).await?; //! let arrow = view.to_arrow(ViewWindow::default()).await?; //! ``` +//! +//! # See also +//! +//! - [`perspective-js`](https://docs.rs/perspective-js/latest/) for the +//! JavaScript API. +//! - [`perspective-python`](https://docs.rs/perspective-python/latest/) for the +//! Python API. +//! - [`perspective-server`](https://docs.rs/perspective-python/latest/) for +//! Data Binding details. +//! - [`perspective-client`](https://docs.rs/perspective-python/latest/) for the +//! Rust Client API +//! - [`perspective-viewer`](https://docs.rs/perspective-viewer/latest/) for the +//! WebAssembly `` Custom Element API. #[cfg(feature = "axum-ws")] pub mod axum;