perspective_python/client/
client_sync.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::collections::HashMap;
14use std::future::Future;
15
16use macro_rules_attribute::apply;
17#[cfg(doc)]
18use perspective_client::{Schema, TableInitOptions, UpdateOptions, config::ViewConfigUpdate};
19use perspective_client::{Session, assert_table_api, assert_view_api};
20use pyo3::exceptions::PyTypeError;
21use pyo3::marker::Ungil;
22use pyo3::prelude::*;
23use pyo3::types::*;
24
25use super::client_async::*;
26use crate::inherit_doc;
27use crate::py_err::ResultTClientErrorExt;
28use crate::server::PySyncServer;
29
30#[pyclass(module = "perspective")]
31#[derive(Clone)]
32pub struct ProxySession(perspective_client::ProxySession);
33
34#[pymethods]
35impl ProxySession {
36    #[new]
37    pub fn new(py: Python<'_>, client: Py<Client>, handle_request: Py<PyAny>) -> PyResult<Self> {
38        let callback = {
39            move |msg: &[u8]| {
40                let msg = msg.to_vec();
41                Python::with_gil(|py| {
42                    let bytes = PyBytes::new(py, &msg);
43                    handle_request.call1(py, (bytes,))?;
44                    Ok(())
45                })
46            }
47        };
48
49        Ok(ProxySession(perspective_client::ProxySession::new(
50            client.borrow(py).0.client.clone(),
51            callback,
52        )))
53    }
54
55    pub fn handle_request(&self, py: Python<'_>, data: Vec<u8>) -> PyResult<()> {
56        self.0.handle_request(&data).py_block_on(py).into_pyerr()?;
57        Ok(())
58    }
59
60    pub fn poll(&self, py: Python<'_>) -> PyResult<()> {
61        self.0.poll().py_block_on(py).into_pyerr()?;
62        Ok(())
63    }
64
65    pub fn close(&self, py: Python<'_>) -> PyResult<()> {
66        self.0.clone().close().py_block_on(py);
67        Ok(())
68    }
69}
70
71trait PyFutureExt: Future {
72    fn py_block_on(self, py: Python<'_>) -> Self::Output
73    where
74        Self: Sized + Send,
75        Self::Output: Ungil,
76    {
77        use pollster::FutureExt;
78        py.allow_threads(move || self.block_on())
79    }
80}
81
82impl<F: Future> PyFutureExt for F {}
83
84#[apply(inherit_doc)]
85#[inherit_doc = "client.md"]
86#[pyclass(subclass, module = "perspective")]
87pub struct Client(pub(crate) AsyncClient);
88
89#[pymethods]
90impl Client {
91    #[new]
92    #[pyo3(signature = (handle_request, close_cb=None))]
93    pub fn new(handle_request: Py<PyAny>, close_cb: Option<Py<PyAny>>) -> PyResult<Self> {
94        let client = AsyncClient::new(handle_request, close_cb);
95        Ok(Client(client))
96    }
97
98    #[staticmethod]
99    #[pyo3(signature = (server, loop_callback=None))]
100    pub fn from_server(
101        py: Python<'_>,
102        server: Py<PySyncServer>,
103        loop_callback: Option<Py<PyAny>>,
104    ) -> PyResult<Self> {
105        server.borrow(py).new_local_client(py, loop_callback)
106    }
107
108    pub fn handle_response(&self, py: Python<'_>, response: Py<PyBytes>) -> PyResult<bool> {
109        self.0.handle_response(response).py_block_on(py)
110    }
111
112    #[apply(inherit_doc)]
113    #[inherit_doc = "client/table.md"]
114    #[pyo3(signature = (input, limit=None, index=None, name=None, format=None))]
115    pub fn table(
116        &self,
117        py: Python<'_>,
118        input: Py<PyAny>,
119        limit: Option<u32>,
120        index: Option<Py<PyString>>,
121        name: Option<Py<PyString>>,
122        format: Option<Py<PyString>>,
123    ) -> PyResult<Table> {
124        Ok(Table(
125            self.0
126                .table(input, limit, index, name, format)
127                .py_block_on(py)?,
128        ))
129    }
130
131    #[apply(inherit_doc)]
132    #[inherit_doc = "client/open_table.md"]
133    pub fn open_table(&self, py: Python<'_>, name: String) -> PyResult<Table> {
134        let client = self.0.clone();
135        let table = client.open_table(name).py_block_on(py)?;
136        Ok(Table(table))
137    }
138
139    #[apply(inherit_doc)]
140    #[inherit_doc = "client/get_hosted_table_names.md"]
141    pub fn get_hosted_table_names(&self, py: Python<'_>) -> PyResult<Vec<String>> {
142        self.0.get_hosted_table_names().py_block_on(py)
143    }
144
145    #[apply(inherit_doc)]
146    #[inherit_doc = "client/on_hosted_tables_update.md"]
147    pub fn on_hosted_tables_update(&self, py: Python<'_>, callback: Py<PyAny>) -> PyResult<u32> {
148        self.0.on_hosted_tables_update(callback).py_block_on(py)
149    }
150
151    #[apply(inherit_doc)]
152    #[inherit_doc = "client/remove_hosted_tables_update.md"]
153    pub fn remove_hosted_tables_update(&self, py: Python<'_>, callback_id: u32) -> PyResult<()> {
154        self.0
155            .remove_hosted_tables_update(callback_id)
156            .py_block_on(py)
157    }
158
159    #[apply(inherit_doc)]
160    #[inherit_doc = "client/set_loop_callback.md"]
161    pub fn set_loop_callback(&self, py: Python<'_>, loop_cb: Py<PyAny>) -> PyResult<()> {
162        self.0.set_loop_callback(loop_cb).py_block_on(py)
163    }
164
165    #[apply(inherit_doc)]
166    #[inherit_doc = "client/terminate.md"]
167    pub fn terminate(&self, py: Python<'_>) -> PyResult<()> {
168        self.0.terminate(py)
169    }
170}
171
172#[pyclass(subclass, name = "Table", module = "perspective")]
173pub struct Table(AsyncTable);
174
175assert_table_api!(Table);
176
177#[pymethods]
178impl Table {
179    #[new]
180    fn new() -> PyResult<Self> {
181        Err(PyTypeError::new_err(
182            "Do not call Table's constructor directly, construct from a Client instance.",
183        ))
184    }
185
186    #[apply(inherit_doc)]
187    #[inherit_doc = "table/get_index.md"]
188    pub fn get_index(&self) -> Option<String> {
189        self.0.get_index()
190    }
191
192    #[apply(inherit_doc)]
193    #[inherit_doc = "table/get_client.md"]
194    pub fn get_client(&self, py: Python<'_>) -> Client {
195        Client(self.0.get_client().py_block_on(py))
196    }
197
198    #[apply(inherit_doc)]
199    #[inherit_doc = "table/get_client.md"]
200    pub fn get_limit(&self) -> Option<u32> {
201        self.0.get_limit()
202    }
203
204    pub fn get_name(&self) -> String {
205        self.0.get_name()
206    }
207
208    #[apply(inherit_doc)]
209    #[inherit_doc = "table/clear.md"]
210    pub fn clear(&self, py: Python<'_>) -> PyResult<()> {
211        self.0.clear().py_block_on(py)
212    }
213
214    #[apply(inherit_doc)]
215    #[inherit_doc = "table/columns.md"]
216    pub fn columns(&self, py: Python<'_>) -> PyResult<Vec<String>> {
217        self.0.columns().py_block_on(py)
218    }
219
220    #[apply(inherit_doc)]
221    #[inherit_doc = "table/delete.md"]
222    pub fn delete(&self, py: Python<'_>) -> PyResult<()> {
223        self.0.delete().py_block_on(py)
224    }
225
226    #[apply(inherit_doc)]
227    #[inherit_doc = "table/make_port.md"]
228    pub fn make_port(&self, py: Python<'_>) -> PyResult<i32> {
229        let table = self.0.clone();
230        table.make_port().py_block_on(py)
231    }
232
233    #[apply(inherit_doc)]
234    #[inherit_doc = "table/on_delete.md"]
235    pub fn on_delete(&self, py: Python<'_>, callback: Py<PyAny>) -> PyResult<u32> {
236        let table = self.0.clone();
237        table.on_delete(callback).py_block_on(py)
238    }
239
240    #[apply(inherit_doc)]
241    #[inherit_doc = "table/remove.md"]
242    #[pyo3(signature = (input, format=None))]
243    pub fn remove(&self, py: Python<'_>, input: Py<PyAny>, format: Option<String>) -> PyResult<()> {
244        let table = self.0.clone();
245        table.remove(input, format).py_block_on(py)
246    }
247
248    #[apply(inherit_doc)]
249    #[inherit_doc = "table/remove_delete.md"]
250    pub fn remove_delete(&self, py: Python<'_>, callback_id: u32) -> PyResult<()> {
251        let table = self.0.clone();
252        table.remove_delete(callback_id).py_block_on(py)
253    }
254
255    #[apply(inherit_doc)]
256    #[inherit_doc = "table/schema.md"]
257    pub fn schema(&self, py: Python<'_>) -> PyResult<HashMap<String, String>> {
258        let table = self.0.clone();
259        table.schema().py_block_on(py)
260    }
261
262    #[apply(inherit_doc)]
263    #[inherit_doc = "table/validate_expressions.md"]
264    pub fn validate_expressions(
265        &self,
266        py: Python<'_>,
267        expression: Py<PyAny>,
268    ) -> PyResult<Py<PyAny>> {
269        let table = self.0.clone();
270        table.validate_expressions(expression).py_block_on(py)
271    }
272
273    #[apply(inherit_doc)]
274    #[inherit_doc = "table/view.md"]
275    #[pyo3(signature = (**config))]
276    pub fn view(&self, py: Python<'_>, config: Option<Py<PyDict>>) -> PyResult<View> {
277        Ok(View(self.0.view(config).py_block_on(py)?))
278    }
279
280    #[apply(inherit_doc)]
281    #[inherit_doc = "table/size.md"]
282    pub fn size(&self, py: Python<'_>) -> PyResult<usize> {
283        self.0.size().py_block_on(py)
284    }
285
286    #[apply(inherit_doc)]
287    #[inherit_doc = "table/update.md"]
288    #[pyo3(signature = (input, format=None))]
289    pub fn replace(
290        &self,
291        py: Python<'_>,
292        input: Py<PyAny>,
293        format: Option<String>,
294    ) -> PyResult<()> {
295        self.0.replace(input, format).py_block_on(py)
296    }
297
298    #[apply(inherit_doc)]
299    #[inherit_doc = "table/update.md"]
300    #[pyo3(signature = (input, port_id=None, format=None))]
301    pub fn update(
302        &self,
303        py: Python<'_>,
304        input: Py<PyAny>,
305        port_id: Option<u32>,
306        format: Option<String>,
307    ) -> PyResult<()> {
308        self.0.update(input, port_id, format).py_block_on(py)
309    }
310}
311
312#[apply(inherit_doc)]
313#[inherit_doc = "view.md"]
314#[pyclass(subclass, name = "View", module = "perspective")]
315pub struct View(AsyncView);
316
317assert_view_api!(View);
318
319#[pymethods]
320impl View {
321    #[new]
322    fn new() -> PyResult<Self> {
323        Err(PyTypeError::new_err(
324            "Do not call View's constructor directly, construct from a Table instance.",
325        ))
326    }
327
328    #[apply(inherit_doc)]
329    #[inherit_doc = "view/column_paths.md"]
330    pub fn column_paths(&self, py: Python<'_>) -> PyResult<Vec<String>> {
331        self.0.column_paths().py_block_on(py)
332    }
333
334    #[apply(inherit_doc)]
335    #[inherit_doc = "view/to_columns_string.md"]
336    #[pyo3(signature = (**window))]
337    pub fn to_columns_string(
338        &self,
339        py: Python<'_>,
340        window: Option<Py<PyDict>>,
341    ) -> PyResult<String> {
342        self.0.to_columns_string(window).py_block_on(py)
343    }
344
345    #[apply(inherit_doc)]
346    #[inherit_doc = "view/to_json_string.md"]
347    #[pyo3(signature = (**window))]
348    pub fn to_json_string(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<String> {
349        self.0.to_json_string(window).py_block_on(py)
350    }
351
352    #[apply(inherit_doc)]
353    #[inherit_doc = "view/to_ndjson.md"]
354    #[pyo3(signature = (**window))]
355    pub fn to_ndjson(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<String> {
356        self.0.to_ndjson(window).py_block_on(py)
357    }
358
359    #[pyo3(signature = (**window))]
360    pub fn to_records<'a>(
361        &self,
362        py: Python<'a>,
363        window: Option<Py<PyDict>>,
364    ) -> PyResult<Bound<'a, PyAny>> {
365        let json = self.0.to_json_string(window).py_block_on(py)?;
366        let json_module = PyModule::import(py, "json")?;
367        json_module.call_method1("loads", (json,))
368    }
369
370    #[apply(inherit_doc)]
371    #[inherit_doc = "view/to_json.md"]
372    #[pyo3(signature = (**window))]
373    pub fn to_json<'a>(
374        &self,
375        py: Python<'a>,
376        window: Option<Py<PyDict>>,
377    ) -> PyResult<Bound<'a, PyAny>> {
378        self.to_records(py, window)
379    }
380
381    #[apply(inherit_doc)]
382    #[inherit_doc = "view/to_columns.md"]
383    #[pyo3(signature = (**window))]
384    pub fn to_columns<'a>(
385        &self,
386        py: Python<'a>,
387        window: Option<Py<PyDict>>,
388    ) -> PyResult<Bound<'a, PyAny>> {
389        let json = self.0.to_columns_string(window).py_block_on(py)?;
390        let json_module = PyModule::import(py, "json")?;
391        json_module.call_method1("loads", (json,))
392    }
393
394    #[apply(inherit_doc)]
395    #[inherit_doc = "view/to_csv.md"]
396    #[pyo3(signature = (**window))]
397    pub fn to_csv(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<String> {
398        self.0.to_csv(window).py_block_on(py)
399    }
400
401    #[doc = include_str!("../../docs/client/to_pandas.md")]
402    #[pyo3(signature = (**window))]
403    // #[deprecated(since="3.2.0", note="Please use `View::to_pandas`")]
404    pub fn to_dataframe(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<Py<PyAny>> {
405        self.0.to_dataframe(window).py_block_on(py)
406    }
407
408    #[doc = include_str!("../../docs/client/to_pandas.md")]
409    #[pyo3(signature = (**window))]
410    pub fn to_pandas(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<Py<PyAny>> {
411        self.0.to_dataframe(window).py_block_on(py)
412    }
413
414    #[doc = include_str!("../../docs/client/to_polars.md")]
415    #[pyo3(signature = (**window))]
416    pub fn to_polars(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<Py<PyAny>> {
417        self.0.to_polars(window).py_block_on(py)
418    }
419
420    #[apply(inherit_doc)]
421    #[inherit_doc = "view/to_arrow.md"]
422    #[pyo3(signature = (**window))]
423    pub fn to_arrow(&self, py: Python<'_>, window: Option<Py<PyDict>>) -> PyResult<Py<PyBytes>> {
424        self.0.to_arrow(window).py_block_on(py)
425    }
426
427    #[apply(inherit_doc)]
428    #[inherit_doc = "view/delete.md"]
429    pub fn delete(&self, py: Python<'_>) -> PyResult<()> {
430        self.0.delete().py_block_on(py)
431    }
432
433    #[apply(inherit_doc)]
434    #[inherit_doc = "view/expand.md"]
435    pub fn expand(&self, py: Python<'_>, index: u32) -> PyResult<u32> {
436        self.0.expand(index).py_block_on(py)
437    }
438
439    #[apply(inherit_doc)]
440    #[inherit_doc = "view/collapse.md"]
441    pub fn collapse(&self, py: Python<'_>, index: u32) -> PyResult<u32> {
442        self.0.collapse(index).py_block_on(py)
443    }
444
445    #[apply(inherit_doc)]
446    #[inherit_doc = "view/dimensions.md"]
447    pub fn dimensions(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
448        self.0.dimensions().py_block_on(py)
449    }
450
451    #[apply(inherit_doc)]
452    #[inherit_doc = "view/expression_schema.md"]
453    pub fn expression_schema(&self, py: Python<'_>) -> PyResult<HashMap<String, String>> {
454        self.0.expression_schema().py_block_on(py)
455    }
456
457    #[apply(inherit_doc)]
458    #[inherit_doc = "view/get_config.md"]
459    pub fn get_config(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
460        self.0.get_config().py_block_on(py)
461    }
462
463    #[apply(inherit_doc)]
464    #[inherit_doc = "view/get_min_max.md"]
465    pub fn get_min_max(&self, py: Python<'_>, column_name: String) -> PyResult<(String, String)> {
466        self.0.get_min_max(column_name).py_block_on(py)
467    }
468
469    #[apply(inherit_doc)]
470    #[inherit_doc = "view/num_rows.md"]
471    pub fn num_rows(&self, py: Python<'_>) -> PyResult<u32> {
472        self.0.num_rows().py_block_on(py)
473    }
474
475    #[apply(inherit_doc)]
476    #[inherit_doc = "view/schema.md"]
477    pub fn schema(&self, py: Python<'_>) -> PyResult<HashMap<String, String>> {
478        self.0.schema().py_block_on(py)
479    }
480
481    #[apply(inherit_doc)]
482    #[inherit_doc = "view/on_delete.md"]
483    pub fn on_delete(&self, py: Python<'_>, callback: Py<PyAny>) -> PyResult<u32> {
484        self.0.on_delete(callback).py_block_on(py)
485    }
486
487    #[apply(inherit_doc)]
488    #[inherit_doc = "view/remove_delete.md"]
489    pub fn remove_delete(&self, py: Python<'_>, callback_id: u32) -> PyResult<()> {
490        self.0.remove_delete(callback_id).py_block_on(py)
491    }
492
493    #[apply(inherit_doc)]
494    #[inherit_doc = "view/on_update.md"]
495    #[pyo3(signature = (callback, mode=None))]
496    pub fn on_update(
497        &self,
498        py: Python<'_>,
499        callback: Py<PyAny>,
500        mode: Option<String>,
501    ) -> PyResult<u32> {
502        self.0.on_update(callback, mode).py_block_on(py)
503    }
504
505    #[apply(inherit_doc)]
506    #[inherit_doc = "view/remove_update.md"]
507    pub fn remove_update(&self, py: Python<'_>, callback_id: u32) -> PyResult<()> {
508        self.0.remove_update(callback_id).py_block_on(py)
509    }
510}