polars-df 0.26.1 → 0.27.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.
@@ -6,9 +6,8 @@ use std::path::PathBuf;
6
6
  use magnus::{Error, RString, Ruby, Value, prelude::*, value::Opaque};
7
7
  use polars::io::mmap::MmapBytesReader;
8
8
  use polars::prelude::PlRefPath;
9
- use polars::prelude::file::{Writeable, WriteableTrait};
9
+ use polars::prelude::file::{Writable, WritableTrait};
10
10
  use polars_buffer::Buffer;
11
- use polars_utils::create_file;
12
11
 
13
12
  use crate::error::RbPolarsErr;
14
13
  use crate::prelude::resolve_homedir;
@@ -23,7 +22,7 @@ pub struct RbFileLikeObject {
23
22
  has_flush: bool,
24
23
  }
25
24
 
26
- impl WriteableTrait for RbFileLikeObject {
25
+ impl WritableTrait for RbFileLikeObject {
27
26
  fn close(&mut self) -> io::Result<()> {
28
27
  Ok(())
29
28
  }
@@ -192,7 +191,7 @@ impl Seek for RbFileLikeObject {
192
191
  }
193
192
  }
194
193
 
195
- pub trait FileLike: Read + Write + Seek {}
194
+ pub trait FileLike: Read + Write + Seek + Sync + Send {}
196
195
 
197
196
  impl FileLike for File {}
198
197
  impl FileLike for RbFileLikeObject {}
@@ -217,10 +216,10 @@ impl EitherRustRubyFile {
217
216
  }
218
217
  }
219
218
 
220
- pub(crate) fn into_writeable(self) -> Writeable {
219
+ pub(crate) fn into_writable(self) -> Writable {
221
220
  match self {
222
- Self::Rb(f) => Writeable::Dyn(Box::new(f)),
223
- Self::Rust(f) => Writeable::Local(f),
221
+ Self::Rb(f) => Writable::Dyn(Box::new(f)),
222
+ Self::Rust(f) => Writable::Local(f),
224
223
  }
225
224
  }
226
225
  }
@@ -272,9 +271,9 @@ pub fn get_either_buffer_or_path(
272
271
  let file_path = std::path::Path::new(&s);
273
272
  let file_path = resolve_homedir(&file_path);
274
273
  let f = if write {
275
- create_file(&file_path).map_err(RbPolarsErr::from)?
274
+ polars_utils::io::create_file(&file_path).map_err(RbPolarsErr::from)?
276
275
  } else {
277
- polars_utils::open_file(&file_path).map_err(RbPolarsErr::from)?
276
+ polars_utils::io::open_file(&file_path).map_err(RbPolarsErr::from)?
278
277
  };
279
278
  Ok((EitherRustRubyFile::Rust(f), Some(file_path.into_owned())))
280
279
  } else {
@@ -62,7 +62,8 @@ pub fn read_parquet_metadata(
62
62
  })
63
63
  .map_err(RbPolarsErr::from)?
64
64
  } else {
65
- let file = polars_utils::open_file(p.as_std_path()).map_err(RbPolarsErr::from)?;
65
+ let file =
66
+ polars_utils::io::open_file(p.as_std_path()).map_err(RbPolarsErr::from)?;
66
67
  read_metadata(&mut BufReader::new(file)).map_err(RbPolarsErr::from)?
67
68
  }
68
69
  }
@@ -1,28 +1,15 @@
1
1
  use arrow::datatypes::ArrowDataType;
2
2
  use arrow::ffi;
3
- use magnus::{IntoValue, Ruby, Value};
3
+ use magnus::{Ruby, Value};
4
4
  use polars::datatypes::CompatLevel;
5
5
  use polars::frame::DataFrame;
6
6
  use polars::prelude::{ArrayRef, ArrowField, PlSmallStr, PolarsResult, SchemaExt};
7
7
  use polars::series::Series;
8
8
  use polars_core::utils::arrow;
9
- use std::ffi::CString;
10
9
 
11
10
  use crate::RbResult;
12
11
  use crate::ruby::capsule::RbCapsule;
13
12
 
14
- // TODO switch to RbCapsule in 0.27.0
15
- #[magnus::wrap(class = "Polars::ArrowSchema")]
16
- pub struct RbArrowSchema {
17
- pub(crate) schema: ffi::ArrowSchema,
18
- }
19
-
20
- impl RbArrowSchema {
21
- pub fn to_i(&self) -> usize {
22
- (&self.schema as *const _) as usize
23
- }
24
- }
25
-
26
13
  pub(crate) fn series_to_stream(series: &Series, ruby: &Ruby) -> RbResult<Value> {
27
14
  let field = series.field().to_arrow(CompatLevel::newest());
28
15
  let series = series.clone();
@@ -31,16 +18,14 @@ pub(crate) fn series_to_stream(series: &Series, ruby: &Ruby) -> RbResult<Value>
31
18
  ) as _;
32
19
 
33
20
  let stream = ffi::export_iterator(iter, field);
34
- let stream_capsule_name = CString::new("arrow_array_stream").unwrap();
35
- Ok(RbCapsule::new(stream, Some(stream_capsule_name)).into_value_with(ruby))
21
+ RbCapsule::new_with_value(ruby, stream, c"arrow_array_stream")
36
22
  }
37
23
 
38
24
  pub(crate) fn dataframe_to_stream(df: &DataFrame, ruby: &Ruby) -> RbResult<Value> {
39
25
  let iter = Box::new(DataFrameStreamIterator::new(df));
40
26
  let field = iter.field();
41
27
  let stream = ffi::export_iterator(iter, field);
42
- let stream_capsule_name = CString::new("arrow_array_stream").unwrap();
43
- Ok(RbCapsule::new(stream, Some(stream_capsule_name)).into_value_with(ruby))
28
+ RbCapsule::new_with_value(ruby, stream, c"arrow_array_stream")
44
29
  }
45
30
 
46
31
  pub(crate) fn polars_schema_to_rbcapsule(
@@ -59,7 +44,7 @@ pub(crate) fn polars_schema_to_rbcapsule(
59
44
  false,
60
45
  ));
61
46
 
62
- Ok(RbArrowSchema { schema }.into_value_with(ruby))
47
+ RbCapsule::new_with_value(ruby, schema, c"arrow_schema")
63
48
  }
64
49
 
65
50
  pub struct DataFrameStreamIterator {
@@ -1224,7 +1224,12 @@ impl RbLazyFrame {
1224
1224
  ldf.count().into()
1225
1225
  }
1226
1226
 
1227
- pub fn merge_sorted(&self, other: &Self, key: String, maintain_order: bool) -> RbResult<Self> {
1227
+ pub fn merge_sorted(
1228
+ &self,
1229
+ other: &Self,
1230
+ key: Vec<String>,
1231
+ maintain_order: bool,
1232
+ ) -> RbResult<Self> {
1228
1233
  let out = self
1229
1234
  .ldf
1230
1235
  .read()
@@ -18,7 +18,7 @@ impl TryConvert for Wrap<polars_plan::dsl::SinkTarget> {
18
18
  RbResult::Ok(
19
19
  crate::file::try_get_rbfile(rb, rb_f, true)?
20
20
  .0
21
- .into_writeable(),
21
+ .into_writable(),
22
22
  )
23
23
  })?;
24
24
 
@@ -37,7 +37,6 @@ use expr::RbExpr;
37
37
  use expr::datatype::RbDataTypeExpr;
38
38
  use expr::selector::RbSelector;
39
39
  use functions::whenthen::{RbChainedThen, RbChainedWhen, RbThen, RbWhen};
40
- use interop::arrow::to_rb::RbArrowSchema;
41
40
  use lazyframe::{RbCollectBatches, RbInProcessQuery, RbLazyFrame, RbOptFlags};
42
41
  use lazygroupby::RbLazyGroupBy;
43
42
  use magnus::{Ruby, function, method, prelude::*};
@@ -138,6 +137,7 @@ fn init(ruby: &Ruby) -> RbResult<()> {
138
137
  class.define_method("tail", method!(RbDataFrame::tail, 1))?;
139
138
  class.define_method("is_unique", method!(RbDataFrame::is_unique, 0))?;
140
139
  class.define_method("is_duplicated", method!(RbDataFrame::is_duplicated, 0))?;
140
+ class.define_method("is_sorted", method!(RbDataFrame::is_sorted, 3))?;
141
141
  class.define_method("equals", method!(RbDataFrame::equals, 2))?;
142
142
  class.define_method("with_row_index", method!(RbDataFrame::with_row_index, 2))?;
143
143
  class.define_method(
@@ -252,6 +252,7 @@ fn init(ruby: &Ruby) -> RbResult<()> {
252
252
  class.define_method("is_unique", method!(RbExpr::is_unique, 0))?;
253
253
  class.define_method("is_between", method!(RbExpr::is_between, 3))?;
254
254
  class.define_method("is_close", method!(RbExpr::is_close, 4))?;
255
+ class.define_method("is_sorted", method!(RbExpr::is_sorted, 2))?;
255
256
  class.define_method("approx_n_unique", method!(RbExpr::approx_n_unique, 0))?;
256
257
  class.define_method("is_first_distinct", method!(RbExpr::is_first_distinct, 0))?;
257
258
  class.define_method("is_last_distinct", method!(RbExpr::is_last_distinct, 0))?;
@@ -1347,10 +1348,6 @@ fn init(ruby: &Ruby) -> RbResult<()> {
1347
1348
  class.define_method("to_i", method!(RbCapsule::to_i, 0))?;
1348
1349
  class.define_method("name", method!(RbCapsule::name, 0))?;
1349
1350
 
1350
- // arrow schema
1351
- let class = module.define_class("ArrowSchema", ruby.class_object())?;
1352
- class.define_method("to_i", method!(RbArrowSchema::to_i, 0))?;
1353
-
1354
1351
  // catalog
1355
1352
  let class = module.define_class("RbCatalogClient", ruby.class_object())?;
1356
1353
  class.define_singleton_method("new", function!(RbCatalogClient::new, 2))?;
@@ -12,7 +12,7 @@ use polars_core::chunked_array::object::registry;
12
12
  use polars_core::chunked_array::object::registry::AnonymousObjectBuilder;
13
13
  use polars_core::prelude::AnyValue;
14
14
  use polars_error::PolarsWarning;
15
- use polars_error::signals::register_polars_keyboard_interrupt_hook;
15
+ use polars_error::abort::register_polars_abort_mechanism;
16
16
 
17
17
  use crate::Wrap;
18
18
  use crate::dataframe::RbDataFrame;
@@ -209,7 +209,7 @@ pub unsafe fn register_startup_deps(catch_keyboard_interrupt: bool) {
209
209
  polars_error::set_warning_function(warning_function);
210
210
 
211
211
  if catch_keyboard_interrupt {
212
- register_polars_keyboard_interrupt_hook();
212
+ register_polars_abort_mechanism();
213
213
  }
214
214
  });
215
215
  }
@@ -1,5 +1,9 @@
1
1
  use std::any::Any;
2
- use std::ffi::CString;
2
+ use std::ffi::{CStr, CString};
3
+
4
+ use magnus::{IntoValue, Ruby, Value};
5
+
6
+ use crate::RbResult;
3
7
 
4
8
  #[magnus::wrap(class = "Polars::Capsule")]
5
9
  pub struct RbCapsule {
@@ -8,6 +12,14 @@ pub struct RbCapsule {
8
12
  }
9
13
 
10
14
  impl RbCapsule {
15
+ pub fn new_with_value<T: 'static + Send>(
16
+ ruby: &Ruby,
17
+ value: T,
18
+ name: &CStr,
19
+ ) -> RbResult<Value> {
20
+ Ok(Self::new(value, Some(name.into())).into_value_with(ruby))
21
+ }
22
+
11
23
  pub fn new<T: 'static + Send>(value: T, name: Option<CString>) -> Self {
12
24
  Self {
13
25
  value: Box::new(value),
@@ -4,7 +4,7 @@ use magnus::Ruby;
4
4
  use polars::frame::DataFrame;
5
5
  use polars::series::IntoSeries;
6
6
  use polars_error::PolarsResult;
7
- use polars_error::signals::{KeyboardInterrupt, catch_keyboard_interrupt};
7
+ use polars_error::abort::{QueryAborted, catch_polars_abort};
8
8
 
9
9
  use crate::ruby::exceptions::RbKeyboardInterrupt;
10
10
  use crate::ruby::gvl::GvlExt;
@@ -114,12 +114,16 @@ impl EnterPolarsExt for &Ruby {
114
114
  E: Send + Into<RbPolarsErr>,
115
115
  {
116
116
  let timeout = schedule_polars_timeout();
117
- let ret = self.detach(|| catch_keyboard_interrupt(AssertUnwindSafe(f)));
117
+ let ret = self.detach(|| catch_polars_abort(AssertUnwindSafe(f)));
118
118
  cancel_polars_timeout(timeout);
119
119
  match ret {
120
120
  Ok(Ok(ret)) => Ok(ret),
121
121
  Ok(Err(err)) => Err(RbErr::from(err.into())),
122
- Err(KeyboardInterrupt) => Err(RbKeyboardInterrupt::new_err("")),
122
+ Err(QueryAborted::KeyboardInterrupt) => Err(RbKeyboardInterrupt::new_err("")),
123
+ Err(QueryAborted::OocOutOfDisk) => Err(RbPolarsErr::Other(
124
+ "query aborted, raise POLARS_OOC_DISK_BUDGET_MB".to_string(),
125
+ )
126
+ .into()),
123
127
  }
124
128
  }
125
129
  }
@@ -776,7 +776,7 @@ module Polars
776
776
  # df = Polars::DataFrame.new(
777
777
  # {"a" => [[1, 2, 3], [4, 5, 6]]}, schema: {"a" => Polars::Array.new(Polars::Int64, 3)}
778
778
  # )
779
- # df.select(Polars.col("a").arr.explode)
779
+ # df.select(Polars.col("a").arr.explode(empty_as_null: false))
780
780
  # # =>
781
781
  # # shape: (6, 1)
782
782
  # # ┌─────┐
@@ -791,8 +791,16 @@ module Polars
791
791
  # # │ 5 │
792
792
  # # │ 6 │
793
793
  # # └─────┘
794
- def explode(empty_as_null: true, keep_nulls: true)
795
- Utils.wrap_expr(_rbexpr.explode(empty_as_null, keep_nulls))
794
+ def explode(empty_as_null: OMITTED, keep_nulls: true)
795
+ if empty_as_null == OMITTED
796
+ Utils.issue_deprecation_warning(
797
+ "The default behavior for `empty_as_null` will change to `false`. " +
798
+ "To keep the current behavior, explicitly set `empty_as_null: true`."
799
+ )
800
+ empty_as_null = true
801
+ end
802
+
803
+ Utils.wrap_expr(_rbexpr.arr_explode(empty_as_null, keep_nulls))
796
804
  end
797
805
 
798
806
  # Check if sub-arrays contain the given item.
@@ -614,7 +614,7 @@ module Polars
614
614
  #
615
615
  # @example
616
616
  # s = Polars::Series.new("a", [[1, 2, 3], [4, 5, 6]], dtype: Polars::Array.new(Polars::Int64, 3))
617
- # s.arr.explode
617
+ # s.arr.explode(empty_as_null: false)
618
618
  # # =>
619
619
  # # shape: (6,)
620
620
  # # Series: 'a' [i64]
@@ -626,7 +626,7 @@ module Polars
626
626
  # # 5
627
627
  # # 6
628
628
  # # ]
629
- def explode(empty_as_null: true, keep_nulls: true)
629
+ def explode(empty_as_null: nil, keep_nulls: true)
630
630
  super
631
631
  end
632
632
 
@@ -4535,7 +4535,7 @@ module Polars
4535
4535
  # "numbers" => [[1], [2, 3], [4, 5], [6, 7, 8]]
4536
4536
  # }
4537
4537
  # )
4538
- # df.explode("numbers")
4538
+ # df.explode("numbers", empty_as_null: false)
4539
4539
  # # =>
4540
4540
  # # shape: (8, 2)
4541
4541
  # # ┌─────────┬─────────┐
@@ -4555,7 +4555,7 @@ module Polars
4555
4555
  def explode(
4556
4556
  columns,
4557
4557
  *more_columns,
4558
- empty_as_null: true,
4558
+ empty_as_null: OMITTED,
4559
4559
  keep_nulls: true
4560
4560
  )
4561
4561
  lazy
@@ -4970,6 +4970,51 @@ module Polars
4970
4970
  lazy.shift(n, fill_value: fill_value).collect(optimizations: QueryOptFlags._eager)
4971
4971
  end
4972
4972
 
4973
+ # Check whether the DataFrame is sorted by the given columns.
4974
+ #
4975
+ # @param by [Object]
4976
+ # Column name(s) to check.
4977
+ # @param more_by [Array]
4978
+ # Additional column names.
4979
+ # @param descending [Boolean]
4980
+ # Sort in descending order. When sorting by multiple columns, can be
4981
+ # specified per column by passing a sequence of booleans.
4982
+ # @param nulls_last [Boolean]
4983
+ # Place null values last. When sorting by multiple columns, can be
4984
+ # specified per column by passing a sequence of booleans.
4985
+ #
4986
+ # @return [Boolean]
4987
+ #
4988
+ # @example
4989
+ # df = Polars::DataFrame.new({"a" => [1, 2, 3], "b" => [5, 4, 3]})
4990
+ # df.is_sorted("a")
4991
+ # # => true
4992
+ #
4993
+ # @example
4994
+ # df.is_sorted("b", descending: true)
4995
+ # # => true
4996
+ #
4997
+ # @example
4998
+ # df.is_sorted("a", "b")
4999
+ # # => true
5000
+ def is_sorted(
5001
+ by,
5002
+ *more_by,
5003
+ descending: false,
5004
+ nulls_last: false
5005
+ )
5006
+ by = Array(by)
5007
+ by.concat(more_by)
5008
+ n = by.length
5009
+ if Utils.bool?(descending)
5010
+ descending = [descending] * n
5011
+ end
5012
+ if Utils.bool?(nulls_last)
5013
+ nulls_last = [nulls_last] * n
5014
+ end
5015
+ _df.is_sorted(by, descending, nulls_last)
5016
+ end
5017
+
4973
5018
  # Get a mask of all duplicated rows in this DataFrame.
4974
5019
  #
4975
5020
  # @return [Series]
data/lib/polars/expr.rb CHANGED
@@ -1576,7 +1576,7 @@ module Polars
1576
1576
  # @example Get the top 2 rows by column `a` in each group.
1577
1577
  # df.group_by("c", maintain_order: true)
1578
1578
  # .agg(Polars.all.top_k_by("a", k: 2))
1579
- # .explode(Polars.all.exclude("c"))
1579
+ # .explode(Polars.all.exclude("c"), empty_as_null: false)
1580
1580
  # # =>
1581
1581
  # # shape: (5, 3)
1582
1582
  # # ┌────────┬─────┬─────┐
@@ -1721,7 +1721,7 @@ module Polars
1721
1721
  # @example Get the bottom 2 rows by column `a` in each group.
1722
1722
  # df.group_by("c", maintain_order: true)
1723
1723
  # .agg(Polars.all.bottom_k_by("a", k: 2))
1724
- # .explode(Polars.all.exclude("c"))
1724
+ # .explode(Polars.all.exclude("c"), empty_as_null: false)
1725
1725
  # # =>
1726
1726
  # # shape: (5, 3)
1727
1727
  # # ┌────────┬─────┬─────┐
@@ -3677,7 +3677,7 @@ module Polars
3677
3677
  #
3678
3678
  # @example
3679
3679
  # df = Polars::DataFrame.new({"b" => [[1, 2, 3], [4, 5, 6]]})
3680
- # df.select(Polars.col("b").explode)
3680
+ # df.select(Polars.col("b").explode(empty_as_null: false))
3681
3681
  # # =>
3682
3682
  # # shape: (6, 1)
3683
3683
  # # ┌─────┐
@@ -3692,7 +3692,15 @@ module Polars
3692
3692
  # # │ 5 │
3693
3693
  # # │ 6 │
3694
3694
  # # └─────┘
3695
- def explode(empty_as_null: true, keep_nulls: true)
3695
+ def explode(empty_as_null: OMITTED, keep_nulls: true)
3696
+ if empty_as_null == OMITTED
3697
+ Utils.issue_deprecation_warning(
3698
+ "The default behavior for `empty_as_null` will change to `false`. " +
3699
+ "To keep the current behavior, explicitly set `empty_as_null: true`."
3700
+ )
3701
+ empty_as_null = true
3702
+ end
3703
+
3696
3704
  wrap_expr(_rbexpr.explode(empty_as_null, keep_nulls))
3697
3705
  end
3698
3706
 
@@ -4604,6 +4612,66 @@ module Polars
4604
4612
  wrap_expr(_rbexpr.is_close(other, abs_tol, rel_tol, nans_equal))
4605
4613
  end
4606
4614
 
4615
+ # Checks if an expression is sorted.
4616
+ #
4617
+ # If `descending` and/or `nulls_last` are nil, it will check `true` and `false`
4618
+ # for the unspecified option(s), and return `true` if the expression is sorted
4619
+ # under any combination of those settings.
4620
+ #
4621
+ # @param descending [Boolean]
4622
+ # Checks if the expression is sorted in descending order.
4623
+ # Defaults to false.
4624
+ # @param nulls_last [Boolean]
4625
+ # Consider null values as being ordered last when checking sortedness.
4626
+ # Defaults to false.
4627
+ #
4628
+ # @return [Expr]
4629
+ #
4630
+ # @example Check if a column is sorted in ascending order.
4631
+ # df = Polars::DataFrame.new({"a" => [1, 2, 3, 4]})
4632
+ # df.select(Polars.col("a").is_sorted)
4633
+ # # =>
4634
+ # # shape: (1, 1)
4635
+ # # ┌──────┐
4636
+ # # │ a │
4637
+ # # │ --- │
4638
+ # # │ bool │
4639
+ # # ╞══════╡
4640
+ # # │ true │
4641
+ # # └──────┘
4642
+ #
4643
+ # @example Check if a column is sorted in descending order.
4644
+ # df = Polars::DataFrame.new({"a" => [4, 3, 2, 1]})
4645
+ # df.select(Polars.col("a").is_sorted(descending: true))
4646
+ # # =>
4647
+ # # shape: (1, 1)
4648
+ # # ┌──────┐
4649
+ # # │ a │
4650
+ # # │ --- │
4651
+ # # │ bool │
4652
+ # # ╞══════╡
4653
+ # # │ true │
4654
+ # # └──────┘
4655
+ #
4656
+ # @example Check if a column is sorted in either direction.
4657
+ # df = Polars::DataFrame.new({"a" => [4, 3, 2, 1]})
4658
+ # df.select(Polars.col("a").is_sorted(descending: nil))
4659
+ # # =>
4660
+ # # shape: (1, 1)
4661
+ # # ┌──────┐
4662
+ # # │ a │
4663
+ # # │ --- │
4664
+ # # │ bool │
4665
+ # # ╞══════╡
4666
+ # # │ true │
4667
+ # # └──────┘
4668
+ def is_sorted(
4669
+ descending: false,
4670
+ nulls_last: false
4671
+ )
4672
+ wrap_expr(_rbexpr.is_sorted(descending, nulls_last))
4673
+ end
4674
+
4607
4675
  # Hash the elements in the selection.
4608
4676
  #
4609
4677
  # The hash value is of type `UInt64`.
@@ -7583,7 +7651,7 @@ module Polars
7583
7651
  # # │ --- │
7584
7652
  # # │ f64 │
7585
7653
  # # ╞══════════╡
7586
- # # │ 0.0 │
7654
+ # # │ null │
7587
7655
  # # │ 0.707107 │
7588
7656
  # # │ 0.963624 │
7589
7657
  # # └──────────┘
@@ -7615,7 +7683,7 @@ module Polars
7615
7683
  # # │ --- │
7616
7684
  # # │ f64 │
7617
7685
  # # ╞══════════╡
7618
- # # │ 0.0 │
7686
+ # # │ null │
7619
7687
  # # │ 0.5 │
7620
7688
  # # │ 0.928571 │
7621
7689
  # # └──────────┘
@@ -95,7 +95,7 @@ module Polars
95
95
  # # │ 2 ┆ 4 ┆ 5 ┆ null │
96
96
  # # │ 3 ┆ null ┆ 6 ┆ 8 │
97
97
  # # └─────┴──────┴──────┴──────┘
98
- def concat(items, rechunk: false, how: "vertical", parallel: true, strict: false)
98
+ def concat(items, rechunk: false, how: "vertical", parallel: true, strict: nil)
99
99
  elems = items.to_a
100
100
 
101
101
  if elems.empty?
@@ -303,7 +303,7 @@ module Polars
303
303
  def union(
304
304
  items,
305
305
  how: "vertical",
306
- strict: false
306
+ strict: nil
307
307
  )
308
308
  elems = items.to_a
309
309