polars-df 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/Cargo.lock +268 -95
  4. data/LICENSE.txt +1 -1
  5. data/README.md +1 -3
  6. data/ext/polars/Cargo.toml +18 -18
  7. data/ext/polars/src/catalog/unity.rs +15 -20
  8. data/ext/polars/src/conversion/any_value.rs +25 -24
  9. data/ext/polars/src/conversion/chunked_array.rs +58 -56
  10. data/ext/polars/src/conversion/datetime.rs +58 -7
  11. data/ext/polars/src/conversion/mod.rs +155 -141
  12. data/ext/polars/src/dataframe/export.rs +15 -12
  13. data/ext/polars/src/dataframe/general.rs +5 -4
  14. data/ext/polars/src/dataframe/map.rs +6 -4
  15. data/ext/polars/src/error.rs +1 -1
  16. data/ext/polars/src/expr/array.rs +0 -24
  17. data/ext/polars/src/expr/datatype.rs +3 -2
  18. data/ext/polars/src/expr/datetime.rs +4 -4
  19. data/ext/polars/src/expr/general.rs +27 -15
  20. data/ext/polars/src/expr/list.rs +0 -26
  21. data/ext/polars/src/functions/business.rs +2 -2
  22. data/ext/polars/src/functions/io.rs +4 -3
  23. data/ext/polars/src/functions/lazy.rs +58 -46
  24. data/ext/polars/src/functions/meta.rs +6 -5
  25. data/ext/polars/src/functions/mod.rs +0 -1
  26. data/ext/polars/src/functions/utils.rs +4 -2
  27. data/ext/polars/src/interop/arrow/mod.rs +4 -2
  28. data/ext/polars/src/interop/numo/to_numo_series.rs +26 -25
  29. data/ext/polars/src/io/scan_options.rs +6 -3
  30. data/ext/polars/src/io/sink_options.rs +2 -0
  31. data/ext/polars/src/lazyframe/general.rs +28 -13
  32. data/ext/polars/src/lazyframe/optflags.rs +2 -1
  33. data/ext/polars/src/lib.rs +14 -33
  34. data/ext/polars/src/map/lazy.rs +5 -2
  35. data/ext/polars/src/map/series.rs +19 -18
  36. data/ext/polars/src/on_startup.rs +16 -7
  37. data/ext/polars/src/ruby/numo.rs +3 -4
  38. data/ext/polars/src/ruby/rb_modules.rs +2 -4
  39. data/ext/polars/src/ruby/ruby_udf.rs +7 -9
  40. data/ext/polars/src/ruby/utils.rs +12 -1
  41. data/ext/polars/src/series/aggregation.rs +13 -1
  42. data/ext/polars/src/series/export.rs +33 -38
  43. data/ext/polars/src/series/general.rs +4 -3
  44. data/ext/polars/src/series/map.rs +3 -2
  45. data/ext/polars/src/series/scatter.rs +4 -4
  46. data/ext/polars/src/utils.rs +31 -7
  47. data/lib/polars/array_expr.rb +23 -7
  48. data/lib/polars/array_name_space.rb +16 -2
  49. data/lib/polars/binary_name_space.rb +32 -0
  50. data/lib/polars/data_frame.rb +73 -10
  51. data/lib/polars/date_time_expr.rb +91 -3
  52. data/lib/polars/date_time_name_space.rb +7 -1
  53. data/lib/polars/expr.rb +122 -44
  54. data/lib/polars/functions/business.rb +2 -2
  55. data/lib/polars/functions/eager.rb +80 -7
  56. data/lib/polars/functions/lazy.rb +5 -2
  57. data/lib/polars/io/csv.rb +27 -5
  58. data/lib/polars/io/ipc.rb +1 -1
  59. data/lib/polars/io/lines.rb +4 -4
  60. data/lib/polars/io/sink_options.rb +4 -2
  61. data/lib/polars/lazy_frame.rb +97 -14
  62. data/lib/polars/list_expr.rb +21 -7
  63. data/lib/polars/list_name_space.rb +16 -2
  64. data/lib/polars/query_opt_flags.rb +22 -5
  65. data/lib/polars/selectors.rb +1 -1
  66. data/lib/polars/series.rb +88 -19
  67. data/lib/polars/sql_context.rb +2 -2
  68. data/lib/polars/string_cache.rb +19 -72
  69. data/lib/polars/string_expr.rb +1 -7
  70. data/lib/polars/string_name_space.rb +1 -7
  71. data/lib/polars/utils/construction/series.rb +8 -3
  72. data/lib/polars/utils/convert.rb +16 -6
  73. data/lib/polars/utils/parse.rb +7 -0
  74. data/lib/polars/utils/reduce_balanced.rb +43 -0
  75. data/lib/polars/utils/various.rb +5 -0
  76. data/lib/polars/version.rb +1 -1
  77. data/lib/polars.rb +1 -1
  78. metadata +3 -17
  79. data/ext/polars/src/functions/string_cache.rs +0 -24
@@ -16,6 +16,7 @@ use magnus::{
16
16
  use polars::chunked_array::object::PolarsObjectSafe;
17
17
  use polars::chunked_array::ops::{FillNullLimit, FillNullStrategy};
18
18
  use polars::datatypes::AnyValue;
19
+ use polars::frame::PivotColumnNaming;
19
20
  use polars::frame::row::Row;
20
21
  use polars::io::avro::AvroCompression;
21
22
  use polars::prelude::default_values::{
@@ -36,6 +37,8 @@ use polars_utils::total_ord::{TotalEq, TotalHash};
36
37
  use crate::file::{RubyScanSourceInput, get_ruby_scan_source_input};
37
38
  use crate::object::OBJECT_NAME;
38
39
  use crate::rb_modules::pl_series;
40
+ use crate::ruby::gvl::GvlExt;
41
+ use crate::ruby::utils::TryIntoValue;
39
42
  use crate::utils::to_rb_err;
40
43
  use crate::{
41
44
  RbDataFrame, RbExpr, RbLazyFrame, RbPolarsErr, RbResult, RbSeries, RbTypeError, RbValueError,
@@ -80,11 +83,9 @@ pub(crate) fn get_series(obj: Value) -> RbResult<Series> {
80
83
  Ok(rbs.series.read().clone())
81
84
  }
82
85
 
83
- pub(crate) fn to_series(rb: &Ruby, s: RbSeries) -> Value {
86
+ pub(crate) fn to_series(rb: &Ruby, s: RbSeries) -> RbResult<Value> {
84
87
  let series = pl_series(rb);
85
- series
86
- .funcall::<_, _, Value>("_from_rbseries", (s,))
87
- .unwrap()
88
+ series.funcall::<_, _, Value>("_from_rbseries", (s,))
88
89
  }
89
90
 
90
91
  impl TryConvert for Wrap<PlSmallStr> {
@@ -116,140 +117,135 @@ impl TryConvert for Wrap<NullValues> {
116
117
  }
117
118
  }
118
119
 
119
- fn struct_dict<'a>(ruby: &Ruby, vals: impl Iterator<Item = AnyValue<'a>>, flds: &[Field]) -> Value {
120
+ fn struct_dict<'a>(
121
+ ruby: &Ruby,
122
+ vals: impl Iterator<Item = AnyValue<'a>>,
123
+ flds: &[Field],
124
+ ) -> RbResult<Value> {
120
125
  let dict = ruby.hash_new();
121
126
  for (fld, val) in flds.iter().zip(vals) {
122
- dict.aset(fld.name().as_str(), Wrap(val)).unwrap()
127
+ dict.aset(fld.name().as_str(), Wrap(val).try_into_value_with(ruby)?)?;
123
128
  }
124
- dict.as_value()
129
+ Ok(dict.as_value())
125
130
  }
126
131
 
127
- impl IntoValue for Wrap<Series> {
128
- fn into_value_with(self, ruby: &Ruby) -> Value {
132
+ impl TryIntoValue for Wrap<Series> {
133
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
129
134
  to_series(ruby, RbSeries::new(self.0))
130
135
  }
131
136
  }
132
137
 
133
- impl IntoValue for Wrap<DataType> {
134
- fn into_value_with(self, ruby: &Ruby) -> Value {
138
+ impl TryIntoValue for Wrap<DataType> {
139
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
135
140
  let pl = crate::rb_modules::polars(ruby);
136
141
 
137
142
  match self.0 {
138
143
  DataType::Int8 => {
139
- let class = pl.const_get::<_, Value>("Int8").unwrap();
140
- class.funcall("new", ()).unwrap()
144
+ let class = pl.const_get::<_, Value>("Int8")?;
145
+ class.funcall("new", ())
141
146
  }
142
147
  DataType::Int16 => {
143
- let class = pl.const_get::<_, Value>("Int16").unwrap();
144
- class.funcall("new", ()).unwrap()
148
+ let class = pl.const_get::<_, Value>("Int16")?;
149
+ class.funcall("new", ())
145
150
  }
146
151
  DataType::Int32 => {
147
- let class = pl.const_get::<_, Value>("Int32").unwrap();
148
- class.funcall("new", ()).unwrap()
152
+ let class = pl.const_get::<_, Value>("Int32")?;
153
+ class.funcall("new", ())
149
154
  }
150
155
  DataType::Int64 => {
151
- let class = pl.const_get::<_, Value>("Int64").unwrap();
152
- class.funcall("new", ()).unwrap()
156
+ let class = pl.const_get::<_, Value>("Int64")?;
157
+ class.funcall("new", ())
153
158
  }
154
159
  DataType::Int128 => {
155
- let class = pl.const_get::<_, Value>("Int128").unwrap();
156
- class.funcall("new", ()).unwrap()
160
+ let class = pl.const_get::<_, Value>("Int128")?;
161
+ class.funcall("new", ())
157
162
  }
158
163
  DataType::UInt8 => {
159
- let class = pl.const_get::<_, Value>("UInt8").unwrap();
160
- class.funcall("new", ()).unwrap()
164
+ let class = pl.const_get::<_, Value>("UInt8")?;
165
+ class.funcall("new", ())
161
166
  }
162
167
  DataType::UInt16 => {
163
- let class = pl.const_get::<_, Value>("UInt16").unwrap();
164
- class.funcall("new", ()).unwrap()
168
+ let class = pl.const_get::<_, Value>("UInt16")?;
169
+ class.funcall("new", ())
165
170
  }
166
171
  DataType::UInt32 => {
167
- let class = pl.const_get::<_, Value>("UInt32").unwrap();
168
- class.funcall("new", ()).unwrap()
172
+ let class = pl.const_get::<_, Value>("UInt32")?;
173
+ class.funcall("new", ())
169
174
  }
170
175
  DataType::UInt64 => {
171
- let class = pl.const_get::<_, Value>("UInt64").unwrap();
172
- class.funcall("new", ()).unwrap()
176
+ let class = pl.const_get::<_, Value>("UInt64")?;
177
+ class.funcall("new", ())
173
178
  }
174
179
  DataType::UInt128 => {
175
- let class = pl.const_get::<_, Value>("UInt128").unwrap();
176
- class.funcall("new", ()).unwrap()
180
+ let class = pl.const_get::<_, Value>("UInt128")?;
181
+ class.funcall("new", ())
177
182
  }
178
183
  DataType::Float16 => {
179
- let class = pl.const_get::<_, Value>("Float16").unwrap();
180
- class.funcall("new", ()).unwrap()
184
+ let class = pl.const_get::<_, Value>("Float16")?;
185
+ class.funcall("new", ())
181
186
  }
182
187
  DataType::Float32 => {
183
- let class = pl.const_get::<_, Value>("Float32").unwrap();
184
- class.funcall("new", ()).unwrap()
188
+ let class = pl.const_get::<_, Value>("Float32")?;
189
+ class.funcall("new", ())
185
190
  }
186
191
  DataType::Float64 | DataType::Unknown(UnknownKind::Float) => {
187
- let class = pl.const_get::<_, Value>("Float64").unwrap();
188
- class.funcall("new", ()).unwrap()
192
+ let class = pl.const_get::<_, Value>("Float64")?;
193
+ class.funcall("new", ())
189
194
  }
190
195
  DataType::Decimal(precision, scale) => {
191
- let class = pl.const_get::<_, Value>("Decimal").unwrap();
192
- class
193
- .funcall::<_, _, Value>("new", (precision, scale))
194
- .unwrap()
196
+ let class = pl.const_get::<_, Value>("Decimal")?;
197
+ class.funcall::<_, _, Value>("new", (precision, scale))
195
198
  }
196
199
  DataType::Boolean => {
197
- let class = pl.const_get::<_, Value>("Boolean").unwrap();
198
- class.funcall("new", ()).unwrap()
200
+ let class = pl.const_get::<_, Value>("Boolean")?;
201
+ class.funcall("new", ())
199
202
  }
200
203
  DataType::String | DataType::Unknown(UnknownKind::Str) => {
201
- let class = pl.const_get::<_, Value>("String").unwrap();
202
- class.funcall("new", ()).unwrap()
204
+ let class = pl.const_get::<_, Value>("String")?;
205
+ class.funcall("new", ())
203
206
  }
204
207
  DataType::Binary => {
205
- let class = pl.const_get::<_, Value>("Binary").unwrap();
206
- class.funcall("new", ()).unwrap()
208
+ let class = pl.const_get::<_, Value>("Binary")?;
209
+ class.funcall("new", ())
207
210
  }
208
211
  DataType::Array(inner, size) => {
209
- let class = pl.const_get::<_, Value>("Array").unwrap();
210
- let inner = Wrap(*inner);
212
+ let class = pl.const_get::<_, Value>("Array")?;
213
+ let inner = Wrap(*inner).try_into_value_with(ruby)?;
211
214
  let args = (inner, size);
212
- class.funcall::<_, _, Value>("new", args).unwrap()
215
+ class.funcall::<_, _, Value>("new", args)
213
216
  }
214
217
  DataType::List(inner) => {
215
- let class = pl.const_get::<_, Value>("List").unwrap();
216
- let inner = Wrap(*inner);
217
- class.funcall::<_, _, Value>("new", (inner,)).unwrap()
218
+ let class = pl.const_get::<_, Value>("List")?;
219
+ let inner = Wrap(*inner).try_into_value_with(ruby)?;
220
+ class.funcall::<_, _, Value>("new", (inner,))
218
221
  }
219
222
  DataType::Date => {
220
- let class = pl.const_get::<_, Value>("Date").unwrap();
221
- class.funcall("new", ()).unwrap()
223
+ let class = pl.const_get::<_, Value>("Date")?;
224
+ class.funcall("new", ())
222
225
  }
223
226
  DataType::Datetime(tu, tz) => {
224
- let datetime_class = pl.const_get::<_, Value>("Datetime").unwrap();
225
- datetime_class
226
- .funcall::<_, _, Value>(
227
- "new",
228
- (tu.to_ascii(), tz.as_deref().map(|x| x.as_str())),
229
- )
230
- .unwrap()
227
+ let datetime_class = pl.const_get::<_, Value>("Datetime")?;
228
+ datetime_class.funcall::<_, _, Value>(
229
+ "new",
230
+ (tu.to_ascii(), tz.as_deref().map(|x| x.as_str())),
231
+ )
231
232
  }
232
233
  DataType::Duration(tu) => {
233
- let duration_class = pl.const_get::<_, Value>("Duration").unwrap();
234
- duration_class
235
- .funcall::<_, _, Value>("new", (tu.to_ascii(),))
236
- .unwrap()
234
+ let duration_class = pl.const_get::<_, Value>("Duration")?;
235
+ duration_class.funcall::<_, _, Value>("new", (tu.to_ascii(),))
237
236
  }
238
237
  DataType::Object(_) => {
239
- let class = pl.const_get::<_, Value>("Object").unwrap();
240
- class.funcall("new", ()).unwrap()
238
+ let class = pl.const_get::<_, Value>("Object")?;
239
+ class.funcall("new", ())
241
240
  }
242
241
  DataType::Categorical(cats, _) => {
243
- let categories_class = pl.const_get::<_, Value>("Categories").unwrap();
244
- let categorical_class = pl.const_get::<_, Value>("Categorical").unwrap();
242
+ let categories_class = pl.const_get::<_, Value>("Categories")?;
243
+ let categorical_class = pl.const_get::<_, Value>("Categorical")?;
245
244
  let categories: Value = categories_class
246
- .funcall("_from_rb_categories", (RbCategories::from(cats.clone()),))
247
- .unwrap();
245
+ .funcall("_from_rb_categories", (RbCategories::from(cats.clone()),))?;
248
246
  let kwargs = ruby.hash_new();
249
- kwargs
250
- .aset(ruby.to_symbol("categories"), categories)
251
- .unwrap();
252
- categorical_class.funcall("new", (kwargs,)).unwrap()
247
+ kwargs.aset(ruby.to_symbol("categories"), categories)?;
248
+ categorical_class.funcall("new", (kwargs,))
253
249
  }
254
250
  DataType::Enum(_, mapping) => {
255
251
  let categories = unsafe {
@@ -258,42 +254,38 @@ impl IntoValue for Wrap<DataType> {
258
254
  vec![mapping.to_arrow(true)],
259
255
  )
260
256
  };
261
- let class = pl.const_get::<_, Value>("Enum").unwrap();
262
- let series = to_series(ruby, categories.into_series().into());
263
- class.funcall::<_, _, Value>("new", (series,)).unwrap()
257
+ let class = pl.const_get::<_, Value>("Enum")?;
258
+ let series = to_series(ruby, categories.into_series().into())?;
259
+ class.funcall::<_, _, Value>("new", (series,))
264
260
  }
265
261
  DataType::Time => {
266
- let class = pl.const_get::<_, Value>("Time").unwrap();
267
- class.funcall("new", ()).unwrap()
262
+ let class = pl.const_get::<_, Value>("Time")?;
263
+ class.funcall("new", ())
268
264
  }
269
265
  DataType::Struct(fields) => {
270
- let field_class = pl.const_get::<_, Value>("Field").unwrap();
266
+ let field_class = pl.const_get::<_, Value>("Field")?;
271
267
  let iter = fields.iter().map(|fld| {
272
268
  let name = fld.name().as_str();
273
- let dtype = Wrap(fld.dtype().clone());
274
- field_class
275
- .funcall::<_, _, Value>("new", (name, dtype))
276
- .unwrap()
269
+ let dtype = Wrap(fld.dtype().clone()).try_into_value_with(ruby);
270
+ dtype.and_then(|dt| field_class.funcall::<_, _, Value>("new", (name, dt)))
277
271
  });
278
- let fields = ruby.ary_from_iter(iter);
279
- let struct_class = pl.const_get::<_, Value>("Struct").unwrap();
280
- struct_class
281
- .funcall::<_, _, Value>("new", (fields,))
282
- .unwrap()
272
+ let fields = ruby.ary_try_from_iter(iter)?;
273
+ let struct_class = pl.const_get::<_, Value>("Struct")?;
274
+ struct_class.funcall::<_, _, Value>("new", (fields,))
283
275
  }
284
276
  DataType::Null => {
285
- let class = pl.const_get::<_, Value>("Null").unwrap();
286
- class.funcall("new", ()).unwrap()
277
+ let class = pl.const_get::<_, Value>("Null")?;
278
+ class.funcall("new", ())
287
279
  }
288
280
  DataType::Extension(_typ, _storage) => {
289
281
  todo!();
290
282
  }
291
283
  DataType::Unknown(UnknownKind::Int(v)) => {
292
- Wrap(materialize_dyn_int(v).dtype()).into_value_with(ruby)
284
+ Wrap(materialize_dyn_int(v).dtype()).try_into_value_with(ruby)
293
285
  }
294
286
  DataType::Unknown(_) => {
295
- let class = pl.const_get::<_, Value>("Unknown").unwrap();
296
- class.funcall("new", ()).unwrap()
287
+ let class = pl.const_get::<_, Value>("Unknown")?;
288
+ class.funcall("new", ())
297
289
  }
298
290
  DataType::BinaryOffset => {
299
291
  unimplemented!()
@@ -399,13 +391,12 @@ impl TryConvert for Wrap<DataType> {
399
391
  "Polars::String" => DataType::String,
400
392
  "Polars::Binary" => DataType::Binary,
401
393
  "Polars::Categorical" => {
402
- let categories: Value = ob.funcall("categories", ()).unwrap();
403
- let rb_categories: &RbCategories =
404
- categories.funcall("_categories", ()).unwrap();
394
+ let categories: Value = ob.funcall("categories", ())?;
395
+ let rb_categories: &RbCategories = categories.funcall("_categories", ())?;
405
396
  DataType::from_categories(rb_categories.categories().clone())
406
397
  }
407
398
  "Polars::Enum" => {
408
- let categories: Value = ob.funcall("categories", ()).unwrap();
399
+ let categories: Value = ob.funcall("categories", ())?;
409
400
  let s = get_series(categories)?;
410
401
  let ca = s.str().map_err(RbPolarsErr::from)?;
411
402
  let categories = ca.downcast_iter().next().unwrap().clone();
@@ -417,7 +408,7 @@ impl TryConvert for Wrap<DataType> {
417
408
  "Polars::Date" => DataType::Date,
418
409
  "Polars::Time" => DataType::Time,
419
410
  "Polars::Datetime" => {
420
- let time_unit: Value = ob.funcall("time_unit", ()).unwrap();
411
+ let time_unit: Value = ob.funcall("time_unit", ())?;
421
412
  let time_unit = Wrap::<TimeUnit>::try_convert(time_unit)?.0;
422
413
  let time_zone: Option<String> = ob.funcall("time_zone", ())?;
423
414
  DataType::Datetime(
@@ -426,7 +417,7 @@ impl TryConvert for Wrap<DataType> {
426
417
  )
427
418
  }
428
419
  "Polars::Duration" => {
429
- let time_unit: Value = ob.funcall("time_unit", ()).unwrap();
420
+ let time_unit: Value = ob.funcall("time_unit", ())?;
430
421
  let time_unit = Wrap::<TimeUnit>::try_convert(time_unit)?.0;
431
422
  DataType::Duration(time_unit)
432
423
  }
@@ -437,13 +428,13 @@ impl TryConvert for Wrap<DataType> {
437
428
  DataType::Decimal(precision, scale)
438
429
  }
439
430
  "Polars::List" => {
440
- let inner: Value = ob.funcall("inner", ()).unwrap();
431
+ let inner: Value = ob.funcall("inner", ())?;
441
432
  let inner = Wrap::<DataType>::try_convert(inner)?;
442
433
  DataType::List(Box::new(inner.0))
443
434
  }
444
435
  "Polars::Array" => {
445
- let inner: Value = ob.funcall("inner", ()).unwrap();
446
- let size: Value = ob.funcall("size", ()).unwrap();
436
+ let inner: Value = ob.funcall("inner", ())?;
437
+ let size: Value = ob.funcall("size", ())?;
447
438
  let inner = Wrap::<DataType>::try_convert(inner)?;
448
439
  let size = usize::try_convert(size)?;
449
440
  DataType::Array(Box::new(inner.0), size)
@@ -638,13 +629,13 @@ impl TryConvert for Wrap<ScanSources> {
638
629
  }
639
630
  }
640
631
 
641
- impl IntoValue for Wrap<Schema> {
642
- fn into_value_with(self, ruby: &Ruby) -> Value {
632
+ impl TryIntoValue for Wrap<Schema> {
633
+ fn try_into_value_with(self, ruby: &Ruby) -> RbResult<Value> {
643
634
  let dict = ruby.hash_new();
644
635
  for (k, v) in self.0.iter() {
645
- dict.aset(k.as_str(), Wrap(v.clone())).unwrap();
636
+ dict.aset(k.as_str(), Wrap(v.clone()).try_into_value_with(ruby)?)?;
646
637
  }
647
- dict.as_value()
638
+ Ok(dict.as_value())
648
639
  }
649
640
  }
650
641
 
@@ -655,18 +646,17 @@ pub struct ObjectValue {
655
646
 
656
647
  impl Debug for ObjectValue {
657
648
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
658
- f.debug_struct("ObjectValue")
659
- .field("inner", &self.to_value())
660
- .finish()
649
+ write!(f, "{}", self)
661
650
  }
662
651
  }
663
652
 
664
653
  impl Hash for ObjectValue {
665
654
  fn hash<H: Hasher>(&self, state: &mut H) {
666
- let h = self
667
- .to_value()
668
- .funcall::<_, _, isize>("hash", ())
669
- .expect("should be hashable");
655
+ let h = Ruby::attach(|rb| {
656
+ rb.get_inner(self.inner)
657
+ .funcall::<_, _, isize>("hash", ())
658
+ .expect("should be hashable")
659
+ });
670
660
  state.write_isize(h)
671
661
  }
672
662
  }
@@ -675,7 +665,11 @@ impl Eq for ObjectValue {}
675
665
 
676
666
  impl PartialEq for ObjectValue {
677
667
  fn eq(&self, other: &Self) -> bool {
678
- self.to_value().eql(other.to_value()).unwrap_or(false)
668
+ Ruby::attach(|ruby| {
669
+ ruby.get_inner(self.inner)
670
+ .eql(ruby.get_inner(other.inner))
671
+ .unwrap_or(false)
672
+ })
679
673
  }
680
674
  }
681
675
 
@@ -696,7 +690,10 @@ impl TotalHash for ObjectValue {
696
690
 
697
691
  impl Display for ObjectValue {
698
692
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
699
- write!(f, "{}", self.to_value())
693
+ Ruby::attach(|rb| {
694
+ let v = rb.get_inner(self.inner);
695
+ write!(f, "{}", v)
696
+ })
700
697
  }
701
698
  }
702
699
 
@@ -724,12 +721,6 @@ impl From<&dyn PolarsObjectSafe> for &ObjectValue {
724
721
  }
725
722
  }
726
723
 
727
- impl ObjectValue {
728
- pub fn to_value(&self) -> Value {
729
- self.clone().into_value_with(&Ruby::get().unwrap())
730
- }
731
- }
732
-
733
724
  impl IntoValue for ObjectValue {
734
725
  fn into_value_with(self, ruby: &Ruby) -> Value {
735
726
  ruby.get_inner(self.inner)
@@ -738,9 +729,9 @@ impl IntoValue for ObjectValue {
738
729
 
739
730
  impl Default for ObjectValue {
740
731
  fn default() -> Self {
741
- ObjectValue {
742
- inner: Ruby::get().unwrap().qnil().as_value().into(),
743
- }
732
+ Ruby::attach(|rb| ObjectValue {
733
+ inner: rb.qnil().as_value().into(),
734
+ })
744
735
  }
745
736
  }
746
737
 
@@ -1125,6 +1116,21 @@ impl TryConvert for Wrap<SearchSortedSide> {
1125
1116
  }
1126
1117
  }
1127
1118
 
1119
+ impl TryConvert for Wrap<PivotColumnNaming> {
1120
+ fn try_convert(ob: Value) -> RbResult<Self> {
1121
+ let parsed = match String::try_convert(ob)?.as_str() {
1122
+ "auto" => PivotColumnNaming::Auto,
1123
+ "combine" => PivotColumnNaming::Combine,
1124
+ v => {
1125
+ return Err(RbValueError::new_err(format!(
1126
+ "`column_naming` must be one of {{'auto', 'combine'}}, got {v}",
1127
+ )));
1128
+ }
1129
+ };
1130
+ Ok(Wrap(parsed))
1131
+ }
1132
+ }
1133
+
1128
1134
  impl TryConvert for Wrap<ClosedInterval> {
1129
1135
  fn try_convert(ob: Value) -> RbResult<Self> {
1130
1136
  let parsed = match String::try_convert(ob)?.as_str() {
@@ -1234,15 +1240,25 @@ impl TryConvert for Wrap<CastColumnsPolicy> {
1234
1240
  return Ok(out);
1235
1241
  }
1236
1242
 
1237
- let integer_upcast = match &*ob.funcall::<_, _, String>("integer_cast", ())? {
1238
- "upcast" => true,
1239
- "forbid" => false,
1240
- v => {
1241
- return Err(RbValueError::new_err(format!(
1242
- "unknown option for integer_cast: {v}"
1243
- )));
1243
+ let mut integer_upcast = false;
1244
+ let mut integer_to_float_cast = false;
1245
+
1246
+ let integer_cast_object: Value = ob.funcall("integer_cast", ())?;
1247
+
1248
+ parse_multiple_options("integer_cast", integer_cast_object, |v| {
1249
+ match v {
1250
+ "upcast" => integer_upcast = true,
1251
+ "allow-float" => integer_to_float_cast = true,
1252
+ "forbid" => {}
1253
+ v => {
1254
+ return Err(RbValueError::new_err(format!(
1255
+ "unknown option for integer_cast: {v}"
1256
+ )));
1257
+ }
1244
1258
  }
1245
- };
1259
+
1260
+ Ok(())
1261
+ })?;
1246
1262
 
1247
1263
  let mut float_upcast = false;
1248
1264
  let mut float_downcast = false;
@@ -1318,6 +1334,7 @@ impl TryConvert for Wrap<CastColumnsPolicy> {
1318
1334
 
1319
1335
  return Ok(Wrap(CastColumnsPolicy {
1320
1336
  integer_upcast,
1337
+ integer_to_float_cast,
1321
1338
  float_upcast,
1322
1339
  float_downcast,
1323
1340
  datetime_nanoseconds_downcast,
@@ -1364,12 +1381,9 @@ pub fn parse_fill_null_strategy(
1364
1381
  "zero" => FillNullStrategy::Zero,
1365
1382
  "one" => FillNullStrategy::One,
1366
1383
  e => {
1367
- return Err(magnus::Error::new(
1368
- Ruby::get().unwrap().exception_runtime_error(),
1369
- format!(
1370
- "strategy must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1371
- ),
1372
- ));
1384
+ return Err(RbValueError::new_err(format!(
1385
+ "`strategy` must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1386
+ )));
1373
1387
  }
1374
1388
  };
1375
1389
  Ok(parsed)
@@ -1,39 +1,42 @@
1
- use magnus::{IntoValue, Ruby, Value, prelude::*};
1
+ use magnus::{Ruby, Value, prelude::*};
2
2
 
3
3
  use super::*;
4
4
  use crate::RbResult;
5
5
  use crate::conversion::{ObjectValue, Wrap};
6
6
  use crate::interop::arrow::to_rb::dataframe_to_stream;
7
+ use crate::ruby::utils::TryIntoValue;
7
8
 
8
9
  impl RbDataFrame {
9
- pub fn row_tuple(ruby: &Ruby, self_: &Self, idx: i64) -> Value {
10
+ pub fn row_tuple(ruby: &Ruby, self_: &Self, idx: i64) -> RbResult<Value> {
10
11
  let idx = if idx < 0 {
11
12
  (self_.df.read().height() as i64 + idx) as usize
12
13
  } else {
13
14
  idx as usize
14
15
  };
15
- ruby.ary_from_iter(self_.df.read().columns().iter().map(|s| match s.dtype() {
16
+ ruby.ary_try_from_iter(self_.df.read().columns().iter().map(|s| match s.dtype() {
16
17
  DataType::Object(_) => {
17
18
  let obj: Option<&ObjectValue> = s.get_object(idx).map(|any| any.into());
18
- obj.unwrap().to_value()
19
+ // TODO remove unwrap and clone
20
+ obj.unwrap().clone().try_into_value_with(ruby)
19
21
  }
20
- _ => Wrap(s.get(idx).unwrap()).into_value_with(ruby),
22
+ _ => Wrap(s.get(idx).unwrap()).try_into_value_with(ruby),
21
23
  }))
22
- .as_value()
24
+ .map(|v| v.as_value())
23
25
  }
24
26
 
25
- pub fn row_tuples(ruby: &Ruby, self_: &Self) -> Value {
27
+ pub fn row_tuples(ruby: &Ruby, self_: &Self) -> RbResult<Value> {
26
28
  let df = &self_.df;
27
- ruby.ary_from_iter((0..df.read().height()).map(|idx| {
28
- ruby.ary_from_iter(self_.df.read().columns().iter().map(|s| match s.dtype() {
29
+ ruby.ary_try_from_iter((0..df.read().height()).map(|idx| {
30
+ ruby.ary_try_from_iter(self_.df.read().columns().iter().map(|s| match s.dtype() {
29
31
  DataType::Object(_) => {
30
32
  let obj: Option<&ObjectValue> = s.get_object(idx).map(|any| any.into());
31
- obj.unwrap().to_value()
33
+ // TODO remove unwrap and clone
34
+ obj.unwrap().clone().try_into_value_with(ruby)
32
35
  }
33
- _ => Wrap(s.get(idx).unwrap()).into_value_with(ruby),
36
+ _ => Wrap(s.get(idx).unwrap()).try_into_value_with(ruby),
34
37
  }))
35
38
  }))
36
- .as_value()
39
+ .map(|v| v.as_value())
37
40
  }
38
41
 
39
42
  pub fn __arrow_c_stream__(ruby: &Ruby, self_: &Self) -> RbResult<Value> {
@@ -2,7 +2,7 @@ use std::hash::BuildHasher;
2
2
 
3
3
  use arrow::bitmap::MutableBitmap;
4
4
  use either::Either;
5
- use magnus::{IntoValue, RArray, Ruby, Value, prelude::*, value::Opaque};
5
+ use magnus::{RArray, Ruby, Value, prelude::*, value::Opaque};
6
6
  use polars::prelude::*;
7
7
 
8
8
  use crate::conversion::*;
@@ -10,6 +10,7 @@ use crate::prelude::strings_to_pl_smallstr;
10
10
  use crate::rb_modules::pl_utils;
11
11
  use crate::ruby::exceptions::RbIndexError;
12
12
  use crate::ruby::gvl::GvlExt;
13
+ use crate::ruby::utils::TryIntoValue;
13
14
  use crate::series::ToRbSeries;
14
15
  use crate::series::to_series;
15
16
  use crate::utils::EnterPolarsExt;
@@ -144,13 +145,13 @@ impl RbDataFrame {
144
145
  Ok(())
145
146
  }
146
147
 
147
- pub fn dtypes(ruby: &Ruby, self_: &Self) -> RArray {
148
+ pub fn dtypes(ruby: &Ruby, self_: &Self) -> RbResult<RArray> {
148
149
  let df = self_.df.read();
149
150
  let iter = df
150
151
  .columns()
151
152
  .iter()
152
- .map(|s| Wrap(s.dtype().clone()).into_value_with(ruby));
153
- ruby.ary_from_iter(iter)
153
+ .map(|s| Wrap(s.dtype().clone()).try_into_value_with(ruby));
154
+ ruby.ary_try_from_iter(iter)
154
155
  }
155
156
 
156
157
  pub fn n_chunks(&self) -> usize {
@@ -5,7 +5,7 @@ use polars_core::utils::CustomIterTools;
5
5
  use super::*;
6
6
  use crate::error::RbPolarsErr;
7
7
  use crate::prelude::*;
8
- use crate::ruby::utils::to_pl_err;
8
+ use crate::ruby::utils::{TryIntoValue, to_pl_err};
9
9
  use crate::series::construction::series_from_objects;
10
10
  use crate::{RbResult, RbSeries, raise_err};
11
11
 
@@ -28,9 +28,11 @@ impl RbDataFrame {
28
28
  drop(df); // Release lock before calling lambda.
29
29
 
30
30
  let lambda_result_iter = (0..height).map(move |_| {
31
- let iter = iters.iter_mut().map(|it| Wrap(it.next().unwrap()));
32
- let tpl = rb.ary_from_iter(iter);
33
- lambda.funcall::<_, _, Value>("call", (tpl,))
31
+ let iter = iters
32
+ .iter_mut()
33
+ .map(|it| Wrap(it.next().unwrap()).try_into_value_with(rb));
34
+ rb.ary_try_from_iter(iter)
35
+ .and_then(|tpl| lambda.funcall::<_, _, Value>("call", (tpl,)))
34
36
  });
35
37
 
36
38
  // Simple case: return type set.
@@ -63,7 +63,7 @@ impl From<RbPolarsErr> for Error {
63
63
  PolarsError::StructFieldNotFound(name) => {
64
64
  StructFieldNotFoundError::new_err(name.to_string())
65
65
  }
66
- PolarsError::Context { .. } => {
66
+ PolarsError::Context { .. } | PolarsError::ExprContext { .. } => {
67
67
  let tmp = RbPolarsErr::Polars(err.context_trace());
68
68
  RbErr::from(tmp)
69
69
  }