polars-df 0.9.0 → 0.11.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Cargo.lock +144 -57
  4. data/README.md +7 -6
  5. data/ext/polars/Cargo.toml +10 -6
  6. data/ext/polars/src/batched_csv.rs +53 -50
  7. data/ext/polars/src/conversion/anyvalue.rs +3 -2
  8. data/ext/polars/src/conversion/mod.rs +31 -67
  9. data/ext/polars/src/dataframe/construction.rs +186 -0
  10. data/ext/polars/src/dataframe/export.rs +48 -0
  11. data/ext/polars/src/dataframe/general.rs +607 -0
  12. data/ext/polars/src/dataframe/io.rs +463 -0
  13. data/ext/polars/src/dataframe/mod.rs +26 -0
  14. data/ext/polars/src/expr/array.rs +6 -2
  15. data/ext/polars/src/expr/datetime.rs +13 -4
  16. data/ext/polars/src/expr/general.rs +50 -9
  17. data/ext/polars/src/expr/list.rs +6 -2
  18. data/ext/polars/src/expr/rolling.rs +185 -69
  19. data/ext/polars/src/expr/string.rs +12 -33
  20. data/ext/polars/src/file.rs +158 -11
  21. data/ext/polars/src/functions/lazy.rs +20 -3
  22. data/ext/polars/src/functions/range.rs +74 -0
  23. data/ext/polars/src/functions/whenthen.rs +47 -17
  24. data/ext/polars/src/interop/mod.rs +1 -0
  25. data/ext/polars/src/interop/numo/mod.rs +2 -0
  26. data/ext/polars/src/interop/numo/to_numo_df.rs +23 -0
  27. data/ext/polars/src/interop/numo/to_numo_series.rs +60 -0
  28. data/ext/polars/src/lazyframe/mod.rs +111 -56
  29. data/ext/polars/src/lib.rs +68 -34
  30. data/ext/polars/src/map/dataframe.rs +17 -9
  31. data/ext/polars/src/map/lazy.rs +5 -25
  32. data/ext/polars/src/map/series.rs +7 -1
  33. data/ext/polars/src/series/aggregation.rs +47 -30
  34. data/ext/polars/src/series/export.rs +131 -49
  35. data/ext/polars/src/series/mod.rs +13 -133
  36. data/lib/polars/array_expr.rb +6 -2
  37. data/lib/polars/batched_csv_reader.rb +11 -3
  38. data/lib/polars/convert.rb +6 -1
  39. data/lib/polars/data_frame.rb +225 -370
  40. data/lib/polars/date_time_expr.rb +11 -4
  41. data/lib/polars/date_time_name_space.rb +14 -4
  42. data/lib/polars/dynamic_group_by.rb +2 -2
  43. data/lib/polars/exceptions.rb +4 -0
  44. data/lib/polars/expr.rb +1171 -54
  45. data/lib/polars/functions/lazy.rb +3 -3
  46. data/lib/polars/functions/range/date_range.rb +92 -0
  47. data/lib/polars/functions/range/datetime_range.rb +149 -0
  48. data/lib/polars/functions/range/time_range.rb +141 -0
  49. data/lib/polars/functions/whenthen.rb +74 -5
  50. data/lib/polars/group_by.rb +88 -23
  51. data/lib/polars/io/avro.rb +24 -0
  52. data/lib/polars/{io.rb → io/csv.rb} +307 -489
  53. data/lib/polars/io/database.rb +73 -0
  54. data/lib/polars/io/ipc.rb +247 -0
  55. data/lib/polars/io/json.rb +18 -0
  56. data/lib/polars/io/ndjson.rb +69 -0
  57. data/lib/polars/io/parquet.rb +226 -0
  58. data/lib/polars/lazy_frame.rb +55 -195
  59. data/lib/polars/lazy_group_by.rb +100 -3
  60. data/lib/polars/list_expr.rb +6 -2
  61. data/lib/polars/rolling_group_by.rb +2 -2
  62. data/lib/polars/series.rb +14 -12
  63. data/lib/polars/string_expr.rb +38 -36
  64. data/lib/polars/utils.rb +89 -1
  65. data/lib/polars/version.rb +1 -1
  66. data/lib/polars/whenthen.rb +83 -0
  67. data/lib/polars.rb +10 -3
  68. metadata +23 -8
  69. data/ext/polars/src/dataframe.rs +0 -1182
  70. data/lib/polars/when.rb +0 -16
  71. data/lib/polars/when_then.rb +0 -19
@@ -7,187 +7,281 @@ use crate::RbExpr;
7
7
  impl RbExpr {
8
8
  pub fn rolling_sum(
9
9
  &self,
10
- window_size: String,
10
+ window_size: usize,
11
11
  weights: Option<Vec<f64>>,
12
12
  min_periods: usize,
13
13
  center: bool,
14
- by: Option<String>,
15
- closed: Option<Wrap<ClosedWindow>>,
16
14
  ) -> Self {
17
- let options = RollingOptions {
18
- window_size: Duration::parse(&window_size),
15
+ let options = RollingOptionsFixedWindow {
16
+ window_size,
19
17
  weights,
20
18
  min_periods,
21
19
  center,
22
- by,
23
- closed_window: closed.map(|c| c.0),
24
20
  ..Default::default()
25
21
  };
26
22
  self.inner.clone().rolling_sum(options).into()
27
23
  }
28
24
 
29
- pub fn rolling_min(
25
+ pub fn rolling_sum_by(
30
26
  &self,
27
+ by: &RbExpr,
31
28
  window_size: String,
29
+ min_periods: usize,
30
+ closed: Wrap<ClosedWindow>,
31
+ ) -> Self {
32
+ let options = RollingOptionsDynamicWindow {
33
+ window_size: Duration::parse(&window_size),
34
+ min_periods,
35
+ closed_window: closed.0,
36
+ fn_params: None,
37
+ };
38
+ self.inner
39
+ .clone()
40
+ .rolling_sum_by(by.inner.clone(), options)
41
+ .into()
42
+ }
43
+
44
+ pub fn rolling_min(
45
+ &self,
46
+ window_size: usize,
32
47
  weights: Option<Vec<f64>>,
33
48
  min_periods: usize,
34
49
  center: bool,
35
- by: Option<String>,
36
- closed: Option<Wrap<ClosedWindow>>,
37
50
  ) -> Self {
38
- let options = RollingOptions {
39
- window_size: Duration::parse(&window_size),
51
+ let options = RollingOptionsFixedWindow {
52
+ window_size,
40
53
  weights,
41
54
  min_periods,
42
55
  center,
43
- by,
44
- closed_window: closed.map(|c| c.0),
45
56
  ..Default::default()
46
57
  };
47
58
  self.inner.clone().rolling_min(options).into()
48
59
  }
49
60
 
50
- pub fn rolling_max(
61
+ pub fn rolling_min_by(
51
62
  &self,
63
+ by: &RbExpr,
52
64
  window_size: String,
65
+ min_periods: usize,
66
+ closed: Wrap<ClosedWindow>,
67
+ ) -> Self {
68
+ let options = RollingOptionsDynamicWindow {
69
+ window_size: Duration::parse(&window_size),
70
+ min_periods,
71
+ closed_window: closed.0,
72
+ fn_params: None,
73
+ };
74
+ self.inner
75
+ .clone()
76
+ .rolling_min_by(by.inner.clone(), options)
77
+ .into()
78
+ }
79
+
80
+ pub fn rolling_max(
81
+ &self,
82
+ window_size: usize,
53
83
  weights: Option<Vec<f64>>,
54
84
  min_periods: usize,
55
85
  center: bool,
56
- by: Option<String>,
57
- closed: Option<Wrap<ClosedWindow>>,
58
86
  ) -> Self {
59
- let options = RollingOptions {
60
- window_size: Duration::parse(&window_size),
87
+ let options = RollingOptionsFixedWindow {
88
+ window_size: window_size,
61
89
  weights,
62
90
  min_periods,
63
91
  center,
64
- by,
65
- closed_window: closed.map(|c| c.0),
66
92
  ..Default::default()
67
93
  };
68
94
  self.inner.clone().rolling_max(options).into()
69
95
  }
70
96
 
71
- pub fn rolling_mean(
97
+ pub fn rolling_max_by(
72
98
  &self,
99
+ by: &RbExpr,
73
100
  window_size: String,
101
+ min_periods: usize,
102
+ closed: Wrap<ClosedWindow>,
103
+ ) -> Self {
104
+ let options = RollingOptionsDynamicWindow {
105
+ window_size: Duration::parse(&window_size),
106
+ min_periods,
107
+ closed_window: closed.0,
108
+ fn_params: None,
109
+ };
110
+ self.inner
111
+ .clone()
112
+ .rolling_max_by(by.inner.clone(), options)
113
+ .into()
114
+ }
115
+
116
+ pub fn rolling_mean(
117
+ &self,
118
+ window_size: usize,
74
119
  weights: Option<Vec<f64>>,
75
120
  min_periods: usize,
76
121
  center: bool,
77
- by: Option<String>,
78
- closed: Option<Wrap<ClosedWindow>>,
79
122
  ) -> Self {
80
- let options = RollingOptions {
81
- window_size: Duration::parse(&window_size),
123
+ let options = RollingOptionsFixedWindow {
124
+ window_size,
82
125
  weights,
83
126
  min_periods,
84
127
  center,
85
- by,
86
- closed_window: closed.map(|c| c.0),
87
128
  ..Default::default()
88
129
  };
89
130
 
90
131
  self.inner.clone().rolling_mean(options).into()
91
132
  }
92
133
 
93
- #[allow(clippy::too_many_arguments)]
94
- pub fn rolling_std(
134
+ pub fn rolling_mean_by(
95
135
  &self,
136
+ by: &RbExpr,
96
137
  window_size: String,
138
+ min_periods: usize,
139
+ closed: Wrap<ClosedWindow>,
140
+ ) -> Self {
141
+ let options = RollingOptionsDynamicWindow {
142
+ window_size: Duration::parse(&window_size),
143
+ min_periods,
144
+ closed_window: closed.0,
145
+ fn_params: None,
146
+ };
147
+
148
+ self.inner
149
+ .clone()
150
+ .rolling_mean_by(by.inner.clone(), options)
151
+ .into()
152
+ }
153
+
154
+ pub fn rolling_std(
155
+ &self,
156
+ window_size: usize,
97
157
  weights: Option<Vec<f64>>,
98
158
  min_periods: usize,
99
159
  center: bool,
100
- by: Option<String>,
101
- closed: Option<Wrap<ClosedWindow>>,
102
160
  ddof: u8,
103
- warn_if_unsorted: bool,
104
161
  ) -> Self {
105
- let options = RollingOptions {
106
- window_size: Duration::parse(&window_size),
162
+ let options = RollingOptionsFixedWindow {
163
+ window_size,
107
164
  weights,
108
165
  min_periods,
109
166
  center,
110
- by,
111
- closed_window: closed.map(|c| c.0),
112
167
  fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
113
- warn_if_unsorted,
114
168
  };
115
169
 
116
170
  self.inner.clone().rolling_std(options).into()
117
171
  }
118
172
 
119
- #[allow(clippy::too_many_arguments)]
120
- pub fn rolling_var(
173
+ pub fn rolling_std_by(
121
174
  &self,
175
+ by: &RbExpr,
122
176
  window_size: String,
177
+ min_periods: usize,
178
+ closed: Wrap<ClosedWindow>,
179
+ ddof: u8,
180
+ ) -> Self {
181
+ let options = RollingOptionsDynamicWindow {
182
+ window_size: Duration::parse(&window_size),
183
+ min_periods,
184
+ closed_window: closed.0,
185
+ fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
186
+ };
187
+
188
+ self.inner
189
+ .clone()
190
+ .rolling_std_by(by.inner.clone(), options)
191
+ .into()
192
+ }
193
+
194
+ pub fn rolling_var(
195
+ &self,
196
+ window_size: usize,
123
197
  weights: Option<Vec<f64>>,
124
198
  min_periods: usize,
125
199
  center: bool,
126
- by: Option<String>,
127
- closed: Option<Wrap<ClosedWindow>>,
128
200
  ddof: u8,
129
- warn_if_unsorted: bool,
130
201
  ) -> Self {
131
- let options = RollingOptions {
132
- window_size: Duration::parse(&window_size),
202
+ let options = RollingOptionsFixedWindow {
203
+ window_size,
133
204
  weights,
134
205
  min_periods,
135
206
  center,
136
- by,
137
- closed_window: closed.map(|c| c.0),
138
207
  fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
139
- warn_if_unsorted,
140
208
  };
141
209
 
142
210
  self.inner.clone().rolling_var(options).into()
143
211
  }
144
212
 
145
- #[allow(clippy::too_many_arguments)]
146
- pub fn rolling_median(
213
+ pub fn rolling_var_by(
147
214
  &self,
215
+ by: &RbExpr,
148
216
  window_size: String,
217
+ min_periods: usize,
218
+ closed: Wrap<ClosedWindow>,
219
+ ddof: u8,
220
+ ) -> Self {
221
+ let options = RollingOptionsDynamicWindow {
222
+ window_size: Duration::parse(&window_size),
223
+ min_periods,
224
+ closed_window: closed.0,
225
+ fn_params: Some(Arc::new(RollingVarParams { ddof }) as Arc<dyn Any + Send + Sync>),
226
+ };
227
+
228
+ self.inner
229
+ .clone()
230
+ .rolling_var_by(by.inner.clone(), options)
231
+ .into()
232
+ }
233
+
234
+ pub fn rolling_median(
235
+ &self,
236
+ window_size: usize,
149
237
  weights: Option<Vec<f64>>,
150
238
  min_periods: usize,
151
239
  center: bool,
152
- by: Option<String>,
153
- closed: Option<Wrap<ClosedWindow>>,
154
- warn_if_unsorted: bool,
155
240
  ) -> Self {
156
- let options = RollingOptions {
157
- window_size: Duration::parse(&window_size),
158
- weights,
241
+ let options = RollingOptionsFixedWindow {
242
+ window_size,
159
243
  min_periods,
244
+ weights,
160
245
  center,
161
- by,
162
- closed_window: closed.map(|c| c.0),
163
246
  fn_params: None,
164
- warn_if_unsorted,
165
247
  };
166
248
  self.inner.clone().rolling_median(options).into()
167
249
  }
168
250
 
169
- #[allow(clippy::too_many_arguments)]
251
+ pub fn rolling_median_by(
252
+ &self,
253
+ by: &RbExpr,
254
+ window_size: String,
255
+ min_periods: usize,
256
+ closed: Wrap<ClosedWindow>,
257
+ ) -> Self {
258
+ let options = RollingOptionsDynamicWindow {
259
+ window_size: Duration::parse(&window_size),
260
+ min_periods,
261
+ closed_window: closed.0,
262
+ fn_params: None,
263
+ };
264
+ self.inner
265
+ .clone()
266
+ .rolling_median_by(by.inner.clone(), options)
267
+ .into()
268
+ }
269
+
170
270
  pub fn rolling_quantile(
171
271
  &self,
172
272
  quantile: f64,
173
273
  interpolation: Wrap<QuantileInterpolOptions>,
174
- window_size: String,
274
+ window_size: usize,
175
275
  weights: Option<Vec<f64>>,
176
276
  min_periods: usize,
177
277
  center: bool,
178
- by: Option<String>,
179
- closed: Option<Wrap<ClosedWindow>>,
180
- warn_if_unsorted: bool,
181
278
  ) -> Self {
182
- let options = RollingOptions {
183
- window_size: Duration::parse(&window_size),
279
+ let options = RollingOptionsFixedWindow {
280
+ window_size,
184
281
  weights,
185
282
  min_periods,
186
283
  center,
187
- by,
188
- closed_window: closed.map(|c| c.0),
189
284
  fn_params: None,
190
- warn_if_unsorted,
191
285
  };
192
286
 
193
287
  self.inner
@@ -196,6 +290,28 @@ impl RbExpr {
196
290
  .into()
197
291
  }
198
292
 
293
+ pub fn rolling_quantile_by(
294
+ &self,
295
+ by: &RbExpr,
296
+ quantile: f64,
297
+ interpolation: Wrap<QuantileInterpolOptions>,
298
+ window_size: String,
299
+ min_periods: usize,
300
+ closed: Wrap<ClosedWindow>,
301
+ ) -> Self {
302
+ let options = RollingOptionsDynamicWindow {
303
+ window_size: Duration::parse(&window_size),
304
+ min_periods,
305
+ closed_window: closed.0,
306
+ fn_params: None,
307
+ };
308
+
309
+ self.inner
310
+ .clone()
311
+ .rolling_quantile_by(by.inner.clone(), interpolation.0, quantile, options)
312
+ .into()
313
+ }
314
+
199
315
  pub fn rolling_skew(&self, window_size: usize, bias: bool) -> Self {
200
316
  self.inner.clone().rolling_skew(window_size, bias).into()
201
317
  }
@@ -244,12 +244,12 @@ impl RbExpr {
244
244
  .into()
245
245
  }
246
246
 
247
- pub fn str_to_integer(&self, base: u32, strict: bool) -> Self {
247
+ pub fn str_to_integer(&self, base: &Self, strict: bool) -> Self {
248
248
  self.inner
249
249
  .clone()
250
250
  .str()
251
- .to_integer(base, strict)
252
- .with_fmt("str.parse_int")
251
+ .to_integer(base.inner.clone(), strict)
252
+ .with_fmt("str.to_integer")
253
253
  .into()
254
254
  }
255
255
 
@@ -259,39 +259,18 @@ impl RbExpr {
259
259
  infer_schema_len: Option<usize>,
260
260
  ) -> Self {
261
261
  let dtype = dtype.map(|wrap| wrap.0);
262
-
263
- let output_type = match dtype.clone() {
264
- Some(dtype) => GetOutput::from_type(dtype),
265
- None => GetOutput::from_type(DataType::Unknown),
266
- };
267
-
268
- let function = move |s: Series| {
269
- let ca = s.str()?;
270
- match ca.json_decode(dtype.clone(), infer_schema_len) {
271
- Ok(ca) => Ok(Some(ca.into_series())),
272
- Err(e) => Err(PolarsError::ComputeError(format!("{e:?}").into())),
273
- }
274
- };
275
-
276
- self.clone()
277
- .inner
278
- .map(function, output_type)
279
- .with_fmt("str.json_decode")
262
+ self.inner
263
+ .clone()
264
+ .str()
265
+ .json_decode(dtype, infer_schema_len)
280
266
  .into()
281
267
  }
282
268
 
283
- pub fn str_json_path_match(&self, pat: String) -> Self {
284
- let function = move |s: Series| {
285
- let ca = s.str()?;
286
- match ca.json_path_match(&pat) {
287
- Ok(ca) => Ok(Some(ca.into_series())),
288
- Err(e) => Err(PolarsError::ComputeError(format!("{:?}", e).into())),
289
- }
290
- };
291
- self.clone()
292
- .inner
293
- .map(function, GetOutput::from_type(DataType::String))
294
- .with_fmt("str.json_path_match")
269
+ pub fn str_json_path_match(&self, pat: &Self) -> Self {
270
+ self.inner
271
+ .clone()
272
+ .str()
273
+ .json_path_match(pat.inner.clone())
295
274
  .into()
296
275
  }
297
276
 
@@ -1,20 +1,167 @@
1
- use magnus::{exception, prelude::*, Error, RString, Value};
2
- use polars::io::mmap::MmapBytesReader;
3
1
  use std::fs::File;
4
- use std::io::Cursor;
2
+ use std::io;
3
+ use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
5
4
  use std::path::PathBuf;
6
5
 
6
+ use magnus::{exception, prelude::*, Error, RString, Value};
7
+ use polars::io::mmap::MmapBytesReader;
8
+
9
+ use crate::error::RbPolarsErr;
10
+ use crate::prelude::resolve_homedir;
7
11
  use crate::RbResult;
8
12
 
9
- pub fn get_file_like(f: Value, truncate: bool) -> RbResult<File> {
10
- let str_slice = PathBuf::try_convert(f)?;
11
- let f = if truncate {
12
- File::create(str_slice)
13
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?
13
+ #[derive(Clone)]
14
+ pub struct RbFileLikeObject {
15
+ inner: Value,
16
+ }
17
+
18
+ /// Wraps a `Value`, and implements read, seek, and write for it.
19
+ impl RbFileLikeObject {
20
+ /// Creates an instance of a `RbFileLikeObject` from a `Value`.
21
+ /// To assert the object has the required methods methods,
22
+ /// instantiate it with `RbFileLikeObject::require`
23
+ pub fn new(object: Value) -> Self {
24
+ RbFileLikeObject { inner: object }
25
+ }
26
+
27
+ pub fn as_buffer(&self) -> std::io::Cursor<Vec<u8>> {
28
+ let data = self.as_file_buffer().into_inner();
29
+ std::io::Cursor::new(data)
30
+ }
31
+
32
+ pub fn as_file_buffer(&self) -> Cursor<Vec<u8>> {
33
+ let bytes = self
34
+ .inner
35
+ .funcall::<_, _, RString>("read", ())
36
+ .expect("no read method found");
37
+
38
+ let buf = unsafe { bytes.as_slice() }.to_vec();
39
+
40
+ Cursor::new(buf)
41
+ }
42
+
43
+ /// Same as `RbFileLikeObject::new`, but validates that the underlying
44
+ /// ruby object has a `read`, `write`, and `seek` methods in respect to parameters.
45
+ /// Will return a `TypeError` if object does not have `read`, `seek`, and `write` methods.
46
+ pub fn with_requirements(object: Value, read: bool, write: bool, seek: bool) -> RbResult<Self> {
47
+ if read && !object.respond_to("read", false)? {
48
+ return Err(Error::new(
49
+ exception::type_error(),
50
+ "Object does not have a .read() method.",
51
+ ));
52
+ }
53
+
54
+ if seek && !object.respond_to("seek", false)? {
55
+ return Err(Error::new(
56
+ exception::type_error(),
57
+ "Object does not have a .seek() method.",
58
+ ));
59
+ }
60
+
61
+ if write && !object.respond_to("write", false)? {
62
+ return Err(Error::new(
63
+ exception::type_error(),
64
+ "Object does not have a .write() method.",
65
+ ));
66
+ }
67
+
68
+ Ok(RbFileLikeObject::new(object))
69
+ }
70
+ }
71
+
72
+ /// Extracts a string repr from, and returns an IO error to send back to rust.
73
+ fn rberr_to_io_err(e: Error) -> io::Error {
74
+ io::Error::new(io::ErrorKind::Other, e.to_string())
75
+ }
76
+
77
+ impl Read for RbFileLikeObject {
78
+ fn read(&mut self, mut buf: &mut [u8]) -> Result<usize, io::Error> {
79
+ let bytes = self
80
+ .inner
81
+ .funcall::<_, _, RString>("read", (buf.len(),))
82
+ .map_err(rberr_to_io_err)?;
83
+
84
+ buf.write_all(unsafe { bytes.as_slice() })?;
85
+
86
+ Ok(bytes.len())
87
+ }
88
+ }
89
+
90
+ impl Write for RbFileLikeObject {
91
+ fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
92
+ let rbbytes = RString::from_slice(buf);
93
+
94
+ let number_bytes_written = self
95
+ .inner
96
+ .funcall::<_, _, usize>("write", (rbbytes,))
97
+ .map_err(rberr_to_io_err)?;
98
+
99
+ Ok(number_bytes_written)
100
+ }
101
+
102
+ fn flush(&mut self) -> Result<(), io::Error> {
103
+ self.inner
104
+ .funcall::<_, _, Value>("flush", ())
105
+ .map_err(rberr_to_io_err)?;
106
+
107
+ Ok(())
108
+ }
109
+ }
110
+
111
+ impl Seek for RbFileLikeObject {
112
+ fn seek(&mut self, pos: SeekFrom) -> Result<u64, io::Error> {
113
+ let (whence, offset) = match pos {
114
+ SeekFrom::Start(i) => (0, i as i64),
115
+ SeekFrom::Current(i) => (1, i),
116
+ SeekFrom::End(i) => (2, i),
117
+ };
118
+
119
+ let new_position = self
120
+ .inner
121
+ .funcall("seek", (offset, whence))
122
+ .map_err(rberr_to_io_err)?;
123
+
124
+ Ok(new_position)
125
+ }
126
+ }
127
+
128
+ pub trait FileLike: Read + Write + Seek {}
129
+
130
+ impl FileLike for File {}
131
+ impl FileLike for RbFileLikeObject {}
132
+
133
+ pub enum EitherRustRubyFile {
134
+ Rb(RbFileLikeObject),
135
+ Rust(BufReader<File>),
136
+ }
137
+
138
+ ///
139
+ /// # Arguments
140
+ /// * `truncate` - open or create a new file.
141
+ pub fn get_either_file(rb_f: Value, truncate: bool) -> RbResult<EitherRustRubyFile> {
142
+ if let Ok(rstring) = RString::try_convert(rb_f) {
143
+ let s = unsafe { rstring.as_str() }?;
144
+ let file_path = std::path::Path::new(&s);
145
+ let file_path = resolve_homedir(file_path);
146
+ let f = if truncate {
147
+ File::create(file_path).map_err(RbPolarsErr::io)?
148
+ } else {
149
+ polars_utils::open_file(&file_path).map_err(RbPolarsErr::from)?
150
+ };
151
+ let reader = BufReader::new(f);
152
+ Ok(EitherRustRubyFile::Rust(reader))
14
153
  } else {
15
- File::open(str_slice).map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?
16
- };
17
- Ok(f)
154
+ let f = RbFileLikeObject::with_requirements(rb_f, !truncate, truncate, !truncate)?;
155
+ Ok(EitherRustRubyFile::Rb(f))
156
+ }
157
+ }
158
+
159
+ pub fn get_file_like(f: Value, truncate: bool) -> RbResult<Box<dyn FileLike>> {
160
+ use EitherRustRubyFile::*;
161
+ match get_either_file(f, truncate)? {
162
+ Rb(f) => Ok(Box::new(f)),
163
+ Rust(f) => Ok(Box::new(f.into_inner())),
164
+ }
18
165
  }
19
166
 
20
167
  pub fn get_mmap_bytes_reader(rb_f: Value) -> RbResult<Box<dyn MmapBytesReader>> {
@@ -55,9 +55,24 @@ pub fn rolling_cov(
55
55
  .into()
56
56
  }
57
57
 
58
- pub fn arg_sort_by(by: RArray, descending: Vec<bool>) -> RbResult<RbExpr> {
58
+ pub fn arg_sort_by(
59
+ by: RArray,
60
+ descending: Vec<bool>,
61
+ nulls_last: bool,
62
+ multithreaded: bool,
63
+ maintain_order: bool,
64
+ ) -> RbResult<RbExpr> {
59
65
  let by = rb_exprs_to_exprs(by)?;
60
- Ok(dsl::arg_sort_by(by, &descending).into())
66
+ Ok(dsl::arg_sort_by(
67
+ by,
68
+ SortMultipleOptions {
69
+ descending,
70
+ nulls_last,
71
+ multithreaded,
72
+ maintain_order,
73
+ },
74
+ )
75
+ .into())
61
76
  }
62
77
 
63
78
  pub fn arg_where(condition: &RbExpr) -> RbExpr {
@@ -115,6 +130,7 @@ pub fn concat_lf(
115
130
  rechunk,
116
131
  parallel,
117
132
  to_supertypes,
133
+ ..Default::default()
118
134
  },
119
135
  )
120
136
  .map_err(RbPolarsErr::from)?;
@@ -183,6 +199,7 @@ pub fn concat_lf_diagonal(
183
199
  rechunk,
184
200
  parallel,
185
201
  to_supertypes,
202
+ ..Default::default()
186
203
  },
187
204
  )
188
205
  .map_err(RbPolarsErr::from)?;
@@ -324,6 +341,6 @@ pub fn spearman_rank_corr(a: &RbExpr, b: &RbExpr, ddof: u8, propagate_nans: bool
324
341
  }
325
342
 
326
343
  pub fn sql_expr(sql: String) -> RbResult<RbExpr> {
327
- let expr = polars::sql::sql_expr(&sql).map_err(RbPolarsErr::from)?;
344
+ let expr = polars::sql::sql_expr(sql).map_err(RbPolarsErr::from)?;
328
345
  Ok(expr.into())
329
346
  }