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