1use 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 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}