1use std::collections::HashMap;
2
3use crate::{DataFrame, DataValue, JoinRelation, Key};
4use data_value::Extract as _;
5use ndarray::Array1;
6use numpy::PyArray2;
7use pyo3::{exceptions::PyTypeError, prelude::*, types::PyList};
8use tracing::trace;
9
10impl DataFrame {
11 fn select_data(
12 &self,
13 keys: Option<Vec<String>>,
14 transposed: Option<bool>,
15 ) -> Result<ndarray::Array2<DataValue>, crate::error::Error> {
16 let keys = keys
17 .unwrap_or(self.keys())
18 .into_iter()
19 .map(Key::from)
20 .collect::<Vec<Key>>();
21 if transposed.unwrap_or(false) {
22 self.select(Some(keys.as_slice()))
23 } else {
24 self.select_transposed(Some(keys.as_slice()))
25 }
26 }
27}
28
29enum DfOrDict {
30 DataFrame(DataFrame),
31 Dict(HashMap<String, DataValue>),
32}
33
34impl DfOrDict {
35 pub fn new(object: Bound<'_, PyAny>) -> Result<DfOrDict, PyErr> {
36 if let Ok(df) = object.extract::<DataFrame>() {
37 Ok(DfOrDict::DataFrame(df))
38 } else {
39 let dict: HashMap<String, DataValue> = object.extract()?;
40 Ok(DfOrDict::Dict(dict))
41 }
42 }
43}
44
45#[pymethods]
46impl DataFrame {
47 #[new]
48 pub fn init() -> Self {
49 Self::default()
50 }
51
52 #[cfg(feature = "polars-df")]
53 #[staticmethod]
54 pub fn from_polars(df: pyo3_polars::PyDataFrame) -> Self {
55 df.0.into()
56 }
57
58 #[staticmethod]
59 pub fn from_dict_np(df: HashMap<String, Vec<DataValue>>) -> Self {
60 let mut result_df: Vec<(Key, Vec<DataValue>)> = Vec::new();
61 for (key, value) in df.into_iter() {
62 result_df.push((key.as_str().into(), value));
63 }
64 result_df.into()
65 }
66
67 pub fn keys(&self) -> Vec<String> {
68 self.dataframe
69 .keys()
70 .iter()
71 .map(|x| x.name().to_string())
72 .collect()
73 }
74
75 #[cfg(feature = "polars-df")]
76 #[pyo3(name = "as_polars")]
77 pub fn py_as_polars(&self) -> PyResult<pyo3_polars::PyDataFrame> {
78 let df = self
79 .as_polars()
80 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot prepare polars DF: {e}")))?;
81 Ok(pyo3_polars::PyDataFrame(df))
82 }
83
84 pub fn apply(&mut self, function: Bound<'_, PyAny>) -> Result<(), PyErr> {
85 let df: DataFrame = pyo3::Python::with_gil(|py| {
86 let self_ = self
87 .clone()
88 .into_pyobject(py)
89 .expect("BUG: cannot convert to PyObject");
90 let result = function.call1((self_,)).expect("BUG: cannot call function");
91 result
92 .extract::<Bound<DataFrame>>()
93 .expect("BUG: cannot extract data frame")
94 .unbind()
95 .extract(py)
96 .expect("BUG: cannot extract data frame")
97 });
98 self.dataframe = df.dataframe;
99 Ok(())
100 }
101
102 #[pyo3(signature = (keys=None, transposed=None))]
103 pub fn as_numpy_u32<'py>(
104 &self,
105 keys: Option<Vec<String>>,
106 transposed: Option<bool>,
107 py: Python<'py>,
108 ) -> PyResult<Bound<'py, numpy::PyArray2<u32>>> {
109 let data = self
110 .select_data(keys, transposed)
111 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
112 Ok(PyArray2::from_array(py, &data.mapv(|x| u32::extract(&x))))
113 }
114
115 #[pyo3(signature = (keys=None, transposed=None))]
116 pub fn as_numpy_u64<'py>(
117 &self,
118 keys: Option<Vec<String>>,
119 transposed: Option<bool>,
120 py: Python<'py>,
121 ) -> PyResult<Bound<'py, numpy::PyArray2<u64>>> {
122 let data = self
123 .select_data(keys, transposed)
124 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
125 Ok(PyArray2::from_array(py, &data.mapv(|x| u64::extract(&x))))
126 }
127
128 #[pyo3(signature = (keys=None, transposed=None))]
129 pub fn as_numpy_i32<'py>(
130 &self,
131 keys: Option<Vec<String>>,
132 transposed: Option<bool>,
133 py: Python<'py>,
134 ) -> PyResult<Bound<'py, numpy::PyArray2<i32>>> {
135 let data = self
136 .select_data(keys, transposed)
137 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
138 Ok(PyArray2::from_array(py, &data.mapv(|x| i32::extract(&x))))
139 }
140
141 #[pyo3(signature = (keys=None, transposed=None))]
142 pub fn as_numpy_i64<'py>(
143 &self,
144 keys: Option<Vec<String>>,
145 transposed: Option<bool>,
146 py: Python<'py>,
147 ) -> PyResult<Bound<'py, numpy::PyArray2<i64>>> {
148 let data = self
149 .select_data(keys, transposed)
150 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
151 Ok(PyArray2::from_array(py, &data.mapv(|x| i64::extract(&x))))
152 }
153
154 #[pyo3(signature = (keys=None, transposed=None))]
155 pub fn as_numpy_f32<'py>(
156 &self,
157 keys: Option<Vec<String>>,
158 transposed: Option<bool>,
159 py: Python<'py>,
160 ) -> PyResult<Bound<'py, numpy::PyArray2<f32>>> {
161 let data = self
162 .select_data(keys, transposed)
163 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
164 Ok(PyArray2::from_array(py, &data.mapv(|x| f32::extract(&x))))
165 }
166
167 #[pyo3(signature = (keys=None, transposed=None))]
168 pub fn as_numpy_f64<'py>(
169 &self,
170 keys: Option<Vec<String>>,
171 transposed: Option<bool>,
172 py: Python<'py>,
173 ) -> PyResult<Bound<'py, numpy::PyArray2<f64>>> {
174 let data = self
175 .select_data(keys, transposed)
176 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
177 Ok(PyArray2::from_array(py, &data.mapv(|x| f64::extract(&x))))
178 }
179
180 #[pyo3(name = "shrink")]
181 pub fn py_shrink(&mut self) {
182 self.dataframe.shrink();
183 }
184
185 #[pyo3(name = "add_metadata")]
186 pub fn py_add_metadata(&mut self, key: String, value: DataValue) {
187 self.metadata.insert(key, value);
188 }
189
190 #[pyo3(name = "get_metadata")]
191 pub fn py_get_metadata(&self, key: &str) -> Option<DataValue> {
192 self.metadata.get(key).cloned()
193 }
194
195 #[pyo3(name = "rename_key")]
196 pub fn py_rename_key(&mut self, key: &str, new_name: &str) -> Result<(), PyErr> {
197 self.dataframe
199 .rename_key(key, new_name.into())
200 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("{e}")))
201 }
202
203 #[pyo3(name = "add_alias")]
204 pub fn py_add_alias(&mut self, key: &str, new_name: &str) -> Result<(), PyErr> {
205 self.dataframe
206 .add_alias(key, new_name)
207 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("{e}")))
208 }
209
210 #[pyo3(name = "select", signature = (keys=None))]
211 pub fn py_select<'py>(
212 &self,
213 py: Python<'py>,
214 keys: Option<Vec<String>>,
215 ) -> Result<Bound<'py, PyList>, PyErr> {
216 let keys = keys
217 .unwrap_or(self.keys())
218 .into_iter()
219 .map(Key::from)
220 .collect::<Vec<Key>>();
221 let selected = self
222 .select(Some(keys.as_slice()))
223 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
224
225 let list = PyList::empty(py);
226 for rows in selected.rows() {
227 let row = PyList::empty(py);
228 for value in rows.iter() {
229 row.append(value.clone())
230 .expect("BUG: cannot append to list");
231 }
232 list.append(row).expect("BUG: cannot append to list");
233 }
234 Ok(list)
235 }
236
237 #[pyo3(name = "select_transposed", signature = (keys=None))]
238 pub fn py_select_transposed<'py>(
239 &self,
240 py: Python<'py>,
241 keys: Option<Vec<String>>,
242 ) -> Result<Bound<'py, PyList>, PyErr> {
243 let keys = keys
244 .unwrap_or(self.keys())
245 .into_iter()
246 .map(Key::from)
247 .collect::<Vec<Key>>();
248 let selected = self
249 .select_transposed(Some(keys.as_slice()))
250 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot select data: {e}")))?;
251
252 let list = PyList::empty(py);
253 for rows in selected.rows() {
254 let row = PyList::empty(py);
255 for value in rows.iter() {
256 row.append(value.clone())?;
257 }
258 list.append(row)?;
259 }
260 Ok(list)
261 }
262
263 #[pyo3(name = "select_column")]
264 pub fn py_select_column<'py>(
265 &self,
266 py: Python<'py>,
267 key: String,
268 ) -> Result<Bound<'py, PyList>, PyErr> {
269 let selected = self
270 .select_column(Key::from(key))
271 .ok_or_else(|| PyErr::new::<PyTypeError, _>("Cannot select column"))?;
272
273 let list = PyList::empty(py);
274 for x in selected.to_vec().into_iter() {
275 list.append(x)?;
276 }
277
278 Ok(list)
279 }
280
281 #[pyo3(name = "join")]
282 pub fn py_join(&mut self, other: DataFrame, join_type: JoinRelation) -> Result<(), PyErr> {
283 self.dataframe
284 .join(other.dataframe, &join_type)
285 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot join data: {e}")))?;
286
287 Ok(())
288 }
289
290 #[pyo3(name = "push")]
291 pub fn py_push(&mut self, data: HashMap<Key, DataValue>) -> Result<(), PyErr> {
292 self.dataframe
293 .push(data)
294 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot join data: {e}")))?;
295 Ok(())
296 }
297
298 #[pyo3(name = "add_column")]
299 pub fn py_add_column(&mut self, key: Key, data: Vec<DataValue>) -> Result<(), PyErr> {
300 self.dataframe
301 .add_single_column(key, Array1::from_vec(data))
302 .map_err(|e| PyErr::new::<PyTypeError, _>(format!("Cannot join data: {e}")))?;
303 Ok(())
304 }
305
306 pub fn add_constant(&mut self, key: Key, feature: DataValue) -> Result<(), PyErr> {
307 self.constants.insert(key, feature);
308 Ok(())
309 }
310
311 fn __repr__(&self) -> String {
312 self.to_string()
313 }
314
315 fn __str__(&self) -> String {
316 self.to_string()
317 }
318
319 pub fn __iadd__(&mut self, object: Bound<'_, PyAny>) -> Result<(), PyErr> {
320 trace!("{object:?}");
321 let df_or_dict = DfOrDict::new(object)?;
322 match df_or_dict {
323 DfOrDict::DataFrame(df) => {
324 self.dataframe += df.dataframe;
325 }
326 DfOrDict::Dict(dict) => {
327 self.dataframe += dict;
328 }
329 }
330 Ok(())
331 }
332
333 pub fn __isub__(&mut self, object: Bound<'_, PyAny>) -> Result<(), PyErr> {
334 trace!("{object:?}");
335
336 let df_or_dict = DfOrDict::new(object)?;
337 match df_or_dict {
338 DfOrDict::DataFrame(df) => {
339 self.dataframe -= df.dataframe;
340 }
341 DfOrDict::Dict(dict) => {
342 self.dataframe -= dict;
343 }
344 }
345 Ok(())
346 }
347
348 pub fn __imul__(&mut self, object: Bound<'_, PyAny>) -> Result<(), PyErr> {
349 trace!("{object:?}");
350 let df_or_dict = DfOrDict::new(object)?;
351 match df_or_dict {
352 DfOrDict::DataFrame(df) => {
353 self.dataframe *= df.dataframe;
354 }
355 DfOrDict::Dict(dict) => {
356 self.dataframe *= dict;
357 }
358 }
359 Ok(())
360 }
361
362 pub fn __itruediv__(&mut self, object: Bound<'_, PyAny>) -> Result<(), PyErr> {
363 trace!("{object:?}");
364 let df_or_dict = DfOrDict::new(object)?;
365 match df_or_dict {
366 DfOrDict::DataFrame(df) => {
367 self.dataframe /= df.dataframe;
368 }
369 DfOrDict::Dict(dict) => {
370 self.dataframe /= dict;
371 }
372 }
373 Ok(())
374 }
375
376 pub fn __len__(&mut self) -> Result<usize, PyErr> {
377 Ok(self.dataframe.len())
378 }
379}
380
381#[cfg(test)]
382mod test {
383
384 use super::*;
385 use data_value::{stdhashmap, DataValue};
386 use halfbrown::hashmap;
387 use pyo3::ffi::c_str;
388 use rstest::*;
389 use tracing_test::traced_test;
390
391 #[fixture]
392 fn df() -> DataFrame {
393 let mut df = DataFrame::init();
394 assert!(df
395 .push(hashmap! {
396 Key::from("key1") => DataValue::U32(1),
397 Key::from("key2") => DataValue::U32(2),
398 })
399 .is_ok());
400 assert!(df
401 .push(hashmap! {
402 Key::from("key1") => DataValue::U32(11),
403 Key::from("key2") => DataValue::U32(21),
404 })
405 .is_ok());
406 df
407 }
408
409 #[fixture]
410 fn hm() -> HashMap<String, DataValue> {
411 stdhashmap!(
412 "key1".to_string() => DataValue::U32(2),
413 "key2".to_string() => DataValue::U32(3),
414 )
415 }
416
417 #[rstest]
418 fn test_select_data(df: DataFrame) {
419 let data = df.select_data(Some(vec!["key1".into(), "key2".into()]), Some(false));
420 assert!(data.is_ok());
421 assert_eq!(
422 data.unwrap(),
423 ndarray::array![[1u32.into(), 11u32.into()], [2u32.into(), 21u32.into()]]
424 );
425
426 let data = df.select_data(Some(vec!["key1".into(), "key2".into()]), Some(true));
427 assert!(data.is_ok());
428 assert_eq!(
429 data.unwrap(),
430 ndarray::array![[1u32.into(), 2u32.into()], [11u32.into(), 21u32.into()]]
431 );
432 }
433
434 #[cfg(feature = "python")]
435 #[rstest]
436 fn test_from_create() {
437 pyo3::Python::with_gil(|_py| {
438 let mut hm: HashMap<String, Vec<DataValue>> = Default::default();
439 let value: Vec<DataValue> = vec![1i32.into(), 22i32.into()];
440 hm.insert("a".into(), value);
441
442 let df = DataFrame::from_dict_np(hm);
443 assert_eq!(
444 df.select(Some(&["a".into()])),
445 Ok(ndarray::array![
446 [DataValue::from(1i32)],
447 [DataValue::from(22i32)]
448 ]),
449 );
450 });
451 #[cfg(feature = "polars-df")]
452 {
453 let pdf = polars::df!(
454 "a" => [1u64, 2u64, 3u64],
455 "b" => [4f64, 5f64, 6f64],
456 "c" => [7i64, 8i64, 9i64]
457 )
458 .expect("BUG: should be ok");
459 let df = DataFrame::from_polars(pyo3_polars::PyDataFrame(pdf));
460 assert_eq!(
461 df.select(Some(&["a".into(), "b".into(), "c".into()])),
462 crate::df! {
463 "a" => [1u64, 2u64, 3u64],
464 "b" => [4f64, 5f64, 6f64],
465 "c" => [7i64, 8i64, 9i64]
466 }
467 .select(Some(&["a".into(), "b".into(), "c".into()])),
468 );
469 }
470 }
471
472 #[rstest]
473 #[traced_test]
474 fn basic_ops_add(mut df: DataFrame, hm: HashMap<String, DataValue>) {
475 let mut df_expect = df.clone();
476 let df2 = df.clone();
477 let exec = Python::with_gil(|py| -> PyResult<()> {
478 df.__iadd__(df.clone().into_pyobject(py)?.into_any())?;
479 df_expect.dataframe += df2.dataframe;
480 tracing::trace!("{} vs {}", df, df_expect);
481 assert_eq!(df.dataframe, df_expect.dataframe);
482
483 df.__iadd__(hm.clone().into_pyobject(py)?.into_any())?;
484 df_expect.dataframe += hm;
485 tracing::trace!("{} vs {}", df, df_expect);
486 assert_eq!(df.dataframe, df_expect.dataframe);
487
488 Ok(())
489 });
490
491 assert!(exec.is_ok(), "{:?}", exec);
492 }
493
494 #[rstest]
495 #[traced_test]
496 fn basic_ops_sub(mut df: DataFrame, hm: HashMap<String, DataValue>) {
497 let mut df_expect = df.clone();
498 let df2 = df.clone();
499 let exec = Python::with_gil(|py| -> PyResult<()> {
500 df.__isub__(df.clone().into_pyobject(py)?.into_any())?;
501 df_expect.dataframe -= df2.dataframe;
502 tracing::trace!("{} vs {}", df, df_expect);
503 assert_eq!(df.dataframe, df_expect.dataframe);
504
505 df.__isub__(hm.clone().into_pyobject(py)?.into_any())?;
506 df_expect.dataframe -= hm;
507 tracing::trace!("{} vs {}", df, df_expect);
508 assert_eq!(df.dataframe, df_expect.dataframe);
509
510 Ok(())
511 });
512
513 assert!(exec.is_ok(), "{:?}", exec);
514 }
515
516 #[rstest]
517 #[traced_test]
518 fn basic_ops_mul(mut df: DataFrame, hm: HashMap<String, DataValue>) {
519 let mut df_expect = df.clone();
520 let df2 = df.clone();
521 let exec = Python::with_gil(|py| -> PyResult<()> {
522 df.__imul__(df.clone().into_pyobject(py)?.into_any())?;
523 df_expect.dataframe *= df2.dataframe;
524 tracing::trace!("{} vs {}", df, df_expect);
525 assert_eq!(df.dataframe, df_expect.dataframe);
526
527 df.__imul__(hm.clone().into_pyobject(py)?.into_any())?;
528 df_expect.dataframe *= hm;
529 tracing::trace!("{} vs {}", df, df_expect);
530 assert_eq!(df.dataframe, df_expect.dataframe);
531 Ok(())
532 });
533
534 assert!(exec.is_ok(), "{:?}", exec);
535 }
536
537 #[rstest]
538 #[traced_test]
539 fn basic_ops_div(mut df: DataFrame, hm: HashMap<String, DataValue>) {
540 let mut df_expect = df.clone();
541 let df2 = df.clone();
542 let exec = Python::with_gil(|py| -> PyResult<()> {
543 df.__itruediv__(df.clone().into_pyobject(py)?.into_any())?;
544 df_expect.dataframe /= df2.dataframe;
545 tracing::trace!("{} vs {}", df, df_expect);
546 assert_eq!(df.dataframe, df_expect.dataframe);
547
548 df.__itruediv__(hm.clone().into_pyobject(py)?.into_any())?;
549 df_expect.dataframe /= hm;
550 tracing::trace!("{} vs {}", df, df_expect);
551 assert_eq!(df.dataframe, df_expect.dataframe);
552 Ok(())
553 });
554
555 assert!(exec.is_ok(), "{:?}", exec);
556 }
557
558 #[rstest]
559 #[traced_test]
560 #[rstest]
561 fn test_numpy(mut df: DataFrame) {
562 let exec = Python::with_gil(|py| -> PyResult<()> {
563 let code = c_str!(
564 r#"
565def example(df):
566 import numpy as np
567 a_np = df.as_numpy_f32(['key1', 'key2'])
568 print(a_np)
569 b_np = df.as_numpy_u32(['key1', 'key'])
570 print(b_np)
571 b_np = df.as_numpy_i32(['key1', 'key'])
572 print(b_np)
573 b_np = df.as_numpy_i64(['key1', 'key'])
574 print(b_np)
575 b_np = df.as_numpy_u64(['key1', 'key'])
576 print(b_np)
577 b_np = df.as_numpy_f64(['key1', 'key'])
578 print(b_np)
579 b_np = df.as_numpy_f64(['key1', 'key'], transposed=True)
580 print(b_np)
581 return df
582 "#
583 );
584 let fun: Py<PyAny> = PyModule::from_code(py, code, c_str!(""), c_str!(""))?
585 .getattr("example")?
586 .into();
587 let result = fun.call1(py, (df.clone(),));
588 assert!(df.py_join(df.clone(), JoinRelation::default()).is_ok());
589 if py.import("numpy").is_ok() {
592 assert!(result.is_ok(), "{:?}", result);
593 } else {
594 assert!(result.is_err(), "{:?}", result);
595 }
596 Ok(())
597 });
598 assert!(exec.is_ok(), "{:?}", exec);
599 }
600
601 #[rstest]
602 #[traced_test]
603 #[rstest]
604 fn test_fill_from_python(df: DataFrame) {
605 let exec = Python::with_gil(|_py| -> PyResult<()> {
606 let hm = stdhashmap!(
607 Key::from("key1") => DataValue::U32(1),
608 Key::from("key2") => DataValue::U32(2),
609 );
610 let mut df2 = DataFrame::init();
611 assert!(df2.py_push(hm).is_ok());
612 assert!(df2
613 .py_push(stdhashmap!(
614 Key::from("key1") => DataValue::U32(11),
615 Key::from("key2") => DataValue::U32(21),
616 ))
617 .is_ok());
618
619 assert_eq!(df, df2);
620
621 let mut df2 = DataFrame::init();
622 assert!(df2
623 .py_add_column(
624 Key::from("key1"),
625 vec![DataValue::U32(1), DataValue::U32(11)]
626 )
627 .is_ok());
628 assert!(df2
629 .py_add_column(
630 Key::from("key2"),
631 vec![DataValue::U32(2), DataValue::U32(21)]
632 )
633 .is_ok());
634
635 assert_eq!(df, df2);
636 Ok(())
637 });
638 assert!(exec.is_ok(), "{:?}", exec);
639 }
640
641 #[rstest]
642 fn basic_python_dataframe(mut df: DataFrame) {
643 let exec = Python::with_gil(|py| -> PyResult<()> {
644 let fun: Py<PyAny> = PyModule::from_code(
645 py,
646 c_str!(
647 "
648def example(df):
649 print(df)
650 df.shrink()
651 assert len(df) == 2
652 df.add_alias('key1', 'key1-alias')
653 a = df.select(['key1', 'key2'])
654 print(a)
655 b = df.select(['key1-alias', 'key2'])
656 print(b)
657 df.rename_key('key1', 'key1new')
658 df.rename_key('key1new', 'key1')
659 assert a == [[1, 2], [11, 21]]
660 assert a == b
661 df.add_metadata('test', 1)
662 m = df.get_metadata('test')
663 assert m == 1
664 b = df.select_transposed(['key1', 'key2'])
665 print(b)
666 assert b == [[1, 11], [2, 21]]
667 c = df.select_column('key1')
668 print(c)
669 assert c == [1, 11]
670
671 a += b
672 print(a)
673 assert a == [[2, 13], [4, 23]]
674 a -= b
675 print(a)
676 assert e == a
677 f = e * b
678 print(f)
679 assert f == [[1, 22], [44, 441]]
680 g = f / b
681 print(g)
682 assert g == e
683
684 "
685 ),
686 c_str!(""),
687 c_str!(""),
688 )?
689 .getattr("example")?
690 .into();
691 let _ = fun.call1(py, (df.clone(),));
692 assert!(df.py_join(df.clone(), JoinRelation::default()).is_ok());
693 Ok(())
694 });
695 assert!(exec.is_ok(), "{:?}", exec);
696 }
697
698 #[rstest]
699 fn dummy_test_apply(mut df: DataFrame) {
700 let exec = Python::with_gil(|py| -> PyResult<()> {
701 let fun: Py<PyAny> = PyModule::from_code(
702 py,
703 c_str!(
704 r#"
705def multiply_by_ten(x):
706 print(x)
707 x *= {"key1": 10}
708 print(x)
709 return x
710
711def example(df):
712 print(df)
713 df.apply(multiply_by_ten)
714 "#
715 ),
716 c_str!(""),
717 c_str!(""),
718 )?
719 .getattr("example")?
720 .into();
721 let _ = fun.call1(py, (df.clone(),));
722 assert!(df.py_join(df.clone(), JoinRelation::default()).is_ok());
723 Ok(())
724 });
725 assert!(exec.is_ok(), "{:?}", exec);
726 }
727}