Repository: apache/arrow-rs
Pull request #6745 (2024-11-17 15:24:43)

update to pyo3 0.23.0
# Which issue does this PR close?

Closes #.

# Rationale for this change
 
Updates pyo3 to 0.23.0

# What changes are included in this PR?

Dependency update

# Are there any user-facing changes?

Yes, IntoPy trait is replaced with IntoPyObject. 



Patch:
diff --git a/arrow-arith/LICENSE.txt b/arrow-arith/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-arith/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-arith/NOTICE.txt b/arrow-arith/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-arith/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-array/LICENSE.txt b/arrow-array/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-array/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-array/NOTICE.txt b/arrow-array/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-array/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-array/src/array/dictionary_array.rs b/arrow-array/src/array/dictionary_array.rs
index 1187e1676..55ecd56f9 100644
--- a/arrow-array/src/array/dictionary_array.rs
+++ b/arrow-array/src/array/dictionary_array.rs
@@ -729,7 +729,7 @@ impl<T: ArrowDictionaryKeyType> Array for DictionaryArray<T> {
     }
 
     fn logical_nulls(&self) -> Option<NullBuffer> {
-        match self.values.nulls() {
+        match self.values.logical_nulls() {
             None => self.nulls().cloned(),
             Some(value_nulls) => {
                 let mut builder = BooleanBufferBuilder::new(self.len());
@@ -749,6 +749,26 @@ impl<T: ArrowDictionaryKeyType> Array for DictionaryArray<T> {
         }
     }
 
+    fn logical_null_count(&self) -> usize {
+        match (self.keys.nulls(), self.values.logical_nulls()) {
+            (None, None) => 0,
+            (Some(key_nulls), None) => key_nulls.null_count(),
+            (None, Some(value_nulls)) => self
+                .keys
+                .values()
+                .iter()
+                .filter(|k| value_nulls.is_null(k.as_usize()))
+                .count(),
+            (Some(key_nulls), Some(value_nulls)) => self
+                .keys
+                .values()
+                .iter()
+                .enumerate()
+                .filter(|(idx, k)| key_nulls.is_null(*idx) || value_nulls.is_null(k.as_usize()))
+                .count(),
+        }
+    }
+
     fn is_nullable(&self) -> bool {
         !self.is_empty() && (self.nulls().is_some() || self.values.is_nullable())
     }
@@ -1020,7 +1040,7 @@ impl<K: ArrowDictionaryKeyType> AnyDictionaryArray for DictionaryArray<K> {
 mod tests {
     use super::*;
     use crate::cast::as_dictionary_array;
-    use crate::{Int16Array, Int32Array, Int8Array};
+    use crate::{Int16Array, Int32Array, Int8Array, RunArray};
     use arrow_buffer::{Buffer, ToByteSlice};
 
     #[test]
@@ -1445,6 +1465,54 @@ mod tests {
         assert_eq!(values, &[Some(50), None, None, Some(2)])
     }
 
+    #[test]
+    fn test_logical_nulls() -> Result<(), ArrowError> {
+        let values = Arc::new(RunArray::try_new(
+            &Int32Array::from(vec![1, 3, 7]),
+            &Int32Array::from(vec![Some(1), None, Some(3)]),
+        )?) as ArrayRef;
+
+        // For this test to be meaningful, the values array need to have different nulls and logical nulls
+        assert_eq!(values.null_count(), 0);
+        assert_eq!(values.logical_null_count(), 2);
+
+        // Construct a trivial dictionary with 1-1 mapping to underlying array
+        let dictionary = DictionaryArray::<Int8Type>::try_new(
+            Int8Array::from((0..values.len()).map(|i| i as i8).collect::<Vec<_>>()),
+            Arc::clone(&values),
+        )?;
+
+        // No keys are null
+        assert_eq!(dictionary.null_count(), 0);
+        // Dictionary array values are logically nullable
+        assert_eq!(dictionary.logical_null_count(), values.logical_null_count());
+        assert_eq!(dictionary.logical_nulls(), values.logical_nulls());
+        assert!(dictionary.is_nullable());
+
+        // Construct a trivial dictionary with 1-1 mapping to underlying array except that key 0 is nulled out
+        let dictionary = DictionaryArray::<Int8Type>::try_new(
+            Int8Array::from(
+                (0..values.len())
+                    .map(|i| i as i8)
+                    .map(|i| if i == 0 { None } else { Some(i) })
+                    .collect::<Vec<_>>(),
+            ),
+            Arc::clone(&values),
+        )?;
+
+        // One key is null
+        assert_eq!(dictionary.null_count(), 1);
+
+        // Dictionary array values are logically nullable
+        assert_eq!(
+            dictionary.logical_null_count(),
+            values.logical_null_count() + 1
+        );
+        assert!(dictionary.is_nullable());
+
+        Ok(())
+    }
+
     #[test]
     fn test_normalized_keys() {
         let values = vec![132, 0, 1].into();
diff --git a/arrow-array/src/array/mod.rs b/arrow-array/src/array/mod.rs
index 04d9883f5..87577166e 100644
--- a/arrow-array/src/array/mod.rs
+++ b/arrow-array/src/array/mod.rs
@@ -317,8 +317,7 @@ pub trait Array: std::fmt::Debug + Send + Sync {
     /// even if the nulls present in [`DictionaryArray::values`] are not referenced by any key,
     /// and therefore would not appear in [`Array::logical_nulls`].
     fn is_nullable(&self) -> bool {
-        // TODO this is not necessarily perfect default implementation, since null_count() and logical_null_count() are not always equivalent
-        self.null_count() != 0
+        self.logical_null_count() != 0
     }
 
     /// Returns the total number of bytes of memory pointed to by this array.
diff --git a/arrow-array/src/temporal_conversions.rs b/arrow-array/src/temporal_conversions.rs
index 8d238b3a1..23f950d55 100644
--- a/arrow-array/src/temporal_conversions.rs
+++ b/arrow-array/src/temporal_conversions.rs
@@ -37,8 +37,18 @@ pub const MILLISECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MILLISECONDS;
 pub const MICROSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MICROSECONDS;
 /// Number of nanoseconds in a day
 pub const NANOSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * NANOSECONDS;
-/// Number of days between 0001-01-01 and 1970-01-01
-pub const EPOCH_DAYS_FROM_CE: i32 = 719_163;
+
+/// Constant from chrono crate
+///
+/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0.
+/// 4 full leap year cycles until December 31, 1600     4 * 146097 = 584388
+/// 1 day until January 1, 1601                                           1
+/// 369 years until Januari 1, 1970                      369 * 365 = 134685
+/// of which floor(369 / 4) are leap years          floor(369 / 4) =     92
+/// except for 1700, 1800 and 1900                                       -3 +
+///                                                                  --------
+///                                                                  719163
+pub const UNIX_EPOCH_DAY: i64 = 719_163;
 
 /// converts a `i32` representing a `date32` to [`NaiveDateTime`]
 #[inline]
@@ -134,6 +144,31 @@ pub fn timestamp_s_to_datetime(v: i64) -> Option<NaiveDateTime> {
     Some(DateTime::from_timestamp(v, 0)?.naive_utc())
 }
 
+/// Similar to timestamp_s_to_datetime but only compute `date`
+#[inline]
+pub fn timestamp_s_to_date(secs: i64) -> Option<NaiveDateTime> {
+    let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY;
+    if days < i32::MIN as i64 || days > i32::MAX as i64 {
+        return None;
+    }
+    let date = NaiveDate::from_num_days_from_ce_opt(days as i32)?;
+    Some(date.and_time(NaiveTime::default()).and_utc().naive_utc())
+}
+
+/// Similar to timestamp_s_to_datetime but only compute `time`
+#[inline]
+pub fn timestamp_s_to_time(secs: i64) -> Option<NaiveDateTime> {
+    let secs = secs.rem_euclid(86_400);
+    let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, 0)?;
+    Some(
+        DateTime::<Utc>::from_naive_utc_and_offset(
+            NaiveDateTime::new(NaiveDate::default(), time),
+            Utc,
+        )
+        .naive_utc(),
+    )
+}
+
 /// converts a `i64` representing a `timestamp(ms)` to [`NaiveDateTime`]
 #[inline]
 pub fn timestamp_ms_to_datetime(v: i64) -> Option<NaiveDateTime> {
@@ -274,10 +309,28 @@ pub fn as_duration<T: ArrowPrimitiveType>(v: i64) -> Option<Duration> {
 mod tests {
     use crate::temporal_conversions::{
         date64_to_datetime, split_second, timestamp_ms_to_datetime, timestamp_ns_to_datetime,
+        timestamp_s_to_date, timestamp_s_to_datetime, timestamp_s_to_time,
         timestamp_us_to_datetime, NANOSECONDS,
     };
     use chrono::DateTime;
 
+    #[test]
+    fn test_timestamp_func() {
+        let timestamp = 1234;
+        let datetime = timestamp_s_to_datetime(timestamp).unwrap();
+        let expected_date = datetime.date();
+        let expected_time = datetime.time();
+
+        assert_eq!(
+            timestamp_s_to_date(timestamp).unwrap().date(),
+            expected_date
+        );
+        assert_eq!(
+            timestamp_s_to_time(timestamp).unwrap().time(),
+            expected_time
+        );
+    }
+
     #[test]
     fn negative_input_timestamp_ns_to_datetime() {
         assert_eq!(
diff --git a/arrow-avro/LICENSE.txt b/arrow-avro/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-avro/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-avro/NOTICE.txt b/arrow-avro/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-avro/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-buffer/LICENSE.txt b/arrow-buffer/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-buffer/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-buffer/NOTICE.txt b/arrow-buffer/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-buffer/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-cast/LICENSE.txt b/arrow-cast/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-cast/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-cast/NOTICE.txt b/arrow-cast/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-cast/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-csv/LICENSE.txt b/arrow-csv/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-csv/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-csv/NOTICE.txt b/arrow-csv/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-csv/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-data/LICENSE.txt b/arrow-data/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-data/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-data/NOTICE.txt b/arrow-data/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-data/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-flight/LICENSE.txt b/arrow-flight/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-flight/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-flight/NOTICE.txt b/arrow-flight/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-flight/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-ipc/LICENSE.txt b/arrow-ipc/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-ipc/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-ipc/NOTICE.txt b/arrow-ipc/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-ipc/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-json/LICENSE.txt b/arrow-json/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-json/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-json/NOTICE.txt b/arrow-json/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-json/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-ord/LICENSE.txt b/arrow-ord/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-ord/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-ord/NOTICE.txt b/arrow-ord/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-ord/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-row/LICENSE.txt b/arrow-row/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-row/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-row/NOTICE.txt b/arrow-row/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-row/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-schema/LICENSE.txt b/arrow-schema/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-schema/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-schema/NOTICE.txt b/arrow-schema/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-schema/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-select/LICENSE.txt b/arrow-select/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-select/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-select/NOTICE.txt b/arrow-select/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-select/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-string/LICENSE.txt b/arrow-string/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-string/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-string/NOTICE.txt b/arrow-string/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-string/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow/Cargo.toml b/arrow/Cargo.toml
index a0fd96415..732819cf0 100644
--- a/arrow/Cargo.toml
+++ b/arrow/Cargo.toml
@@ -54,7 +54,7 @@ arrow-select = { workspace = true }
 arrow-string = { workspace = true }
 
 rand = { version = "0.8", default-features = false, features = ["std", "std_rng"], optional = true }
-pyo3 = { version = "0.22.2", default-features = false, optional = true }
+pyo3 = { version = "0.23", default-features = false, optional = true }
 
 chrono = { workspace = true, optional = true }
 
diff --git a/arrow/LICENSE.txt b/arrow/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow/NOTICE.txt b/arrow/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow/src/pyarrow.rs b/arrow/src/pyarrow.rs
index 6effe1c03..4ccbd0541 100644
--- a/arrow/src/pyarrow.rs
+++ b/arrow/src/pyarrow.rs
@@ -111,7 +111,7 @@ impl<T: ToPyArrow> IntoPyArrow for T {
 }
 
 fn validate_class(expected: &str, value: &Bound<PyAny>) -> PyResult<()> {
-    let pyarrow = PyModule::import_bound(value.py(), "pyarrow")?;
+    let pyarrow = PyModule::import(value.py(), "pyarrow")?;
     let class = pyarrow.getattr(expected)?;
     if !value.is_instance(&class)? {
         let expected_module = class.getattr("__module__")?.extract::<PyBackedStr>()?;
@@ -177,7 +177,7 @@ impl ToPyArrow for DataType {
     fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
         let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
         let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
-        let module = py.import_bound("pyarrow")?;
+        let module = py.import("pyarrow")?;
         let class = module.getattr("DataType")?;
         let dtype = class.call_method1("_import_from_c", (c_schema_ptr as Py_uintptr_t,))?;
         Ok(dtype.into())
@@ -213,7 +213,7 @@ impl ToPyArrow for Field {
     fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
         let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
         let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
-        let module = py.import_bound("pyarrow")?;
+        let module = py.import("pyarrow")?;
         let class = module.getattr("Field")?;
         let dtype = class.call_method1("_import_from_c", (c_schema_ptr as Py_uintptr_t,))?;
         Ok(dtype.into())
@@ -249,7 +249,7 @@ impl ToPyArrow for Schema {
     fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
         let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
         let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
-        let module = py.import_bound("pyarrow")?;
+        let module = py.import("pyarrow")?;
         let class = module.getattr("Schema")?;
         let schema = class.call_method1("_import_from_c", (c_schema_ptr as Py_uintptr_t,))?;
         Ok(schema.into())
@@ -309,7 +309,7 @@ impl ToPyArrow for ArrayData {
         let array = FFI_ArrowArray::new(self);
         let schema = FFI_ArrowSchema::try_from(self.data_type()).map_err(to_py_err)?;
 
-        let module = py.import_bound("pyarrow")?;
+        let module = py.import("pyarrow")?;
         let class = module.getattr("Array")?;
         let array = class.call_method1(
             "_import_from_c",
@@ -318,7 +318,7 @@ impl ToPyArrow for ArrayData {
                 addr_of!(schema) as Py_uintptr_t,
             ),
         )?;
-        Ok(array.to_object(py))
+        Ok(array.unbind())
     }
 }
 
@@ -335,7 +335,7 @@ impl<T: ToPyArrow> ToPyArrow for Vec<T> {
             .iter()
             .map(|v| v.to_pyarrow(py))
             .collect::<PyResult<Vec<_>>>()?;
-        Ok(values.to_object(py))
+        Ok(PyList::new(py, values)?.unbind().into())
     }
 }
 
@@ -451,7 +451,7 @@ impl FromPyArrow for ArrowArrayStreamReader {
         // make the conversion through PyArrow's private API
         // this changes the pointer's memory and is thus unsafe.
         // In particular, `_export_to_c` can go out of bounds
-        let args = PyTuple::new_bound(value.py(), [stream_ptr as Py_uintptr_t]);
+        let args = PyTuple::new(value.py(), [stream_ptr as Py_uintptr_t])?;
         value.call_method1("_export_to_c", args)?;
 
         let stream_reader = ArrowArrayStreamReader::try_new(stream)
@@ -469,9 +469,9 @@ impl IntoPyArrow for Box<dyn RecordBatchReader + Send> {
         let mut stream = FFI_ArrowArrayStream::new(self);
 
         let stream_ptr = (&mut stream) as *mut FFI_ArrowArrayStream;
-        let module = py.import_bound("pyarrow")?;
+        let module = py.import("pyarrow")?;
         let class = module.getattr("RecordBatchReader")?;
-        let args = PyTuple::new_bound(py, [stream_ptr as Py_uintptr_t]);
+        let args = PyTuple::new(py, [stream_ptr as Py_uintptr_t])?;
         let reader = class.call_method1("_import_from_c", args)?;
 
         Ok(PyObject::from(reader))
@@ -500,11 +500,17 @@ impl<'source, T: FromPyArrow> FromPyObject<'source> for PyArrowType<T> {
     }
 }
 
-impl<T: IntoPyArrow> IntoPy<PyObject> for PyArrowType<T> {
-    fn into_py(self, py: Python) -> PyObject {
+impl<'py, T: IntoPyArrow> IntoPyObject<'py> for PyArrowType<T> {
+    type Target = PyAny;
+
+    type Output = Bound<'py, Self::Target>;
+
+    type Error = PyErr;
+
+    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, PyErr> {
         match self.0.into_pyarrow(py) {
-            Ok(obj) => obj,
-            Err(err) => err.to_object(py),
+            Ok(obj) => Result::Ok(obj.into_bound(py)),
+            Err(err) => Result::Err(err),
         }
     }
 }
diff --git a/parquet/LICENSE.txt b/parquet/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/parquet/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/parquet/NOTICE.txt b/parquet/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/parquet/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/parquet/src/arrow/async_reader/mod.rs b/parquet/src/arrow/async_reader/mod.rs
index 8b315cc9f..cee5f11dd 100644
--- a/parquet/src/arrow/async_reader/mod.rs
+++ b/parquet/src/arrow/async_reader/mod.rs
@@ -158,7 +158,8 @@ pub trait AsyncFileReader: Send {
     fn get_metadata(&mut self) -> BoxFuture<'_, Result<Arc<ParquetMetaData>>>;
 }
 
-impl AsyncFileReader for Box<dyn AsyncFileReader> {
+/// This allows Box<dyn AsyncFileReader + '_> to be used as an AsyncFileReader,
+impl<'reader> AsyncFileReader for Box<dyn AsyncFileReader + 'reader> {
     fn get_bytes(&mut self, range: Range<usize>) -> BoxFuture<'_, Result<Bytes>> {
         self.as_mut().get_bytes(range)
     }
diff --git a/parquet/src/arrow/async_writer/mod.rs b/parquet/src/arrow/async_writer/mod.rs
index 8155b57d9..5695fd216 100644
--- a/parquet/src/arrow/async_writer/mod.rs
+++ b/parquet/src/arrow/async_writer/mod.rs
@@ -89,7 +89,7 @@ pub trait AsyncFileWriter: Send {
     fn complete(&mut self) -> BoxFuture<'_, Result<()>>;
 }
 
-impl AsyncFileWriter for Box<dyn AsyncFileWriter> {
+impl<'writer> AsyncFileWriter for Box<dyn AsyncFileWriter + 'writer> {
     fn write(&mut self, bs: Bytes) -> BoxFuture<'_, Result<()>> {
         self.as_mut().write(bs)
     }
diff --git a/parquet/src/record/api.rs b/parquet/src/record/api.rs
index c95ce3f92..1b0d81c7d 100644
--- a/parquet/src/record/api.rs
+++ b/parquet/src/record/api.rs
@@ -52,6 +52,11 @@ pub struct Row {
 
 #[allow(clippy::len_without_is_empty)]
 impl Row {
+    /// Constructs a `Row` from the list of `fields` and returns it.
+    pub fn new(fields: Vec<(String, Field)>) -> Row {
+        Row { fields }
+    }
+
     /// Get the number of fields in this row.
     pub fn len(&self) -> usize {
         self.fields.len()
@@ -283,12 +288,6 @@ impl RowAccessor for Row {
     row_complex_accessor!(get_map, MapInternal, Map);
 }
 
-/// Constructs a `Row` from the list of `fields` and returns it.
-#[inline]
-pub fn make_row(fields: Vec<(String, Field)>) -> Row {
-    Row { fields }
-}
-
 impl fmt::Display for Row {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "{{")?;
@@ -1386,7 +1385,7 @@ mod tests {
             ("z".to_string(), Field::Float(3.1)),
             ("a".to_string(), Field::Str("abc".to_string())),
         ];
-        let row = Field::Group(make_row(fields));
+        let row = Field::Group(Row::new(fields));
         assert_eq!(format!("{row}"), "{x: null, Y: 2, z: 3.1, a: \"abc\"}");
 
         let row = Field::ListInternal(make_list(vec![
@@ -1431,7 +1430,7 @@ mod tests {
         assert!(Field::Decimal(Decimal::from_i32(4, 8, 2)).is_primitive());
 
         // complex types
-        assert!(!Field::Group(make_row(vec![
+        assert!(!Field::Group(Row::new(vec![
             ("x".to_string(), Field::Null),
             ("Y".to_string(), Field::Int(2)),
             ("z".to_string(), Field::Float(3.1)),
@@ -1458,7 +1457,7 @@ mod tests {
     #[test]
     fn test_row_primitive_field_fmt() {
         // Primitives types
-        let row = make_row(vec![
+        let row = Row::new(vec![
             ("00".to_string(), Field::Null),
             ("01".to_string(), Field::Bool(false)),
             ("02".to_string(), Field::Byte(3)),
@@ -1513,10 +1512,10 @@ mod tests {
     #[test]
     fn test_row_complex_field_fmt() {
         // Complex types
-        let row = make_row(vec![
+        let row = Row::new(vec![
             (
                 "00".to_string(),
-                Field::Group(make_row(vec![
+                Field::Group(Row::new(vec![
                     ("x".to_string(), Field::Null),
                     ("Y".to_string(), Field::Int(2)),
                 ])),
@@ -1548,7 +1547,7 @@ mod tests {
     #[test]
     fn test_row_primitive_accessors() {
         // primitives
-        let row = make_row(vec![
+        let row = Row::new(vec![
             ("a".to_string(), Field::Null),
             ("b".to_string(), Field::Bool(false)),
             ("c".to_string(), Field::Byte(3)),
@@ -1590,7 +1589,7 @@ mod tests {
     #[test]
     fn test_row_primitive_invalid_accessors() {
         // primitives
-        let row = make_row(vec![
+        let row = Row::new(vec![
             ("a".to_string(), Field::Null),
             ("b".to_string(), Field::Bool(false)),
             ("c".to_string(), Field::Byte(3)),
@@ -1619,10 +1618,10 @@ mod tests {
 
     #[test]
     fn test_row_complex_accessors() {
-        let row = make_row(vec![
+        let row = Row::new(vec![
             (
                 "a".to_string(),
-                Field::Group(make_row(vec![
+                Field::Group(Row::new(vec![
                     ("x".to_string(), Field::Null),
                     ("Y".to_string(), Field::Int(2)),
                 ])),
@@ -1653,10 +1652,10 @@ mod tests {
 
     #[test]
     fn test_row_complex_invalid_accessors() {
-        let row = make_row(vec![
+        let row = Row::new(vec![
             (
                 "a".to_string(),
-                Field::Group(make_row(vec![
+                Field::Group(Row::new(vec![
                     ("x".to_string(), Field::Null),
                     ("Y".to_string(), Field::Int(2)),
                 ])),
@@ -1802,7 +1801,7 @@ mod tests {
 
     #[test]
     fn test_list_complex_accessors() {
-        let list = make_list(vec![Field::Group(make_row(vec![
+        let list = make_list(vec![Field::Group(Row::new(vec![
             ("x".to_string(), Field::Null),
             ("Y".to_string(), Field::Int(2)),
         ]))]);
@@ -1826,7 +1825,7 @@ mod tests {
 
     #[test]
     fn test_list_complex_invalid_accessors() {
-        let list = make_list(vec![Field::Group(make_row(vec![
+        let list = make_list(vec![Field::Group(Row::new(vec![
             ("x".to_string(), Field::Null),
             ("Y".to_string(), Field::Int(2)),
         ]))]);
@@ -1961,7 +1960,7 @@ mod tests {
             ("Y".to_string(), Field::Double(2.2)),
             ("Z".to_string(), Field::Str("abc".to_string())),
         ];
-        let row = Field::Group(make_row(fields));
+        let row = Field::Group(Row::new(fields));
         assert_eq!(
             row.to_json_value(),
             serde_json::json!({"X": 1, "Y": 2.2, "Z": "abc"})
@@ -1990,14 +1989,14 @@ mod tests {
 #[cfg(test)]
 #[allow(clippy::many_single_char_names)]
 mod api_tests {
-    use super::{make_list, make_map, make_row};
+    use super::{make_list, make_map, Row};
     use crate::record::Field;
 
     #[test]
     fn test_field_visibility() {
-        let row = make_row(vec![(
+        let row = Row::new(vec![(
             "a".to_string(),
-            Field::Group(make_row(vec![
+            Field::Group(Row::new(vec![
                 ("x".to_string(), Field::Null),
                 ("Y".to_string(), Field::Int(2)),
             ])),
@@ -2009,7 +2008,7 @@ mod api_tests {
                 match column.1 {
                     Field::Group(r) => {
                         assert_eq!(
-                            &make_row(vec![
+                            &Row::new(vec![
                                 ("x".to_string(), Field::Null),
                                 ("Y".to_string(), Field::Int(2)),
                             ]),
@@ -2027,7 +2026,7 @@ mod api_tests {
     fn test_list_element_access() {
         let expected = vec![
             Field::Int(1),
-            Field::Group(make_row(vec![
+            Field::Group(Row::new(vec![
                 ("x".to_string(), Field::Null),
                 ("Y".to_string(), Field::Int(2)),
             ])),
diff --git a/parquet/src/record/reader.rs b/parquet/src/record/reader.rs
index fd6ca7cdd..be58f5c5a 100644
--- a/parquet/src/record/reader.rs
+++ b/parquet/src/record/reader.rs
@@ -24,7 +24,7 @@ use crate::basic::{ConvertedType, Repetition};
 use crate::errors::{ParquetError, Result};
 use crate::file::reader::{FileReader, RowGroupReader};
 use crate::record::{
-    api::{make_list, make_map, make_row, Field, Row},
+    api::{make_list, make_map, Field, Row},
     triplet::TripletIter,
 };
 use crate::schema::types::{ColumnPath, SchemaDescPtr, SchemaDescriptor, Type, TypePtr};
@@ -399,7 +399,7 @@ impl Reader {
                 for reader in readers {
                     fields.push((String::from(reader.field_name()), reader.read_field()?));
                 }
-                Ok(make_row(fields))
+                Ok(Row::new(fields))
             }
             _ => panic!("Cannot call read() on {self}"),
         }
@@ -434,7 +434,7 @@ impl Reader {
                         fields.push((String::from(reader.field_name()), Field::Null));
                     }
                 }
-                let row = make_row(fields);
+                let row = Row::new(fields);
                 Field::Group(row)
             }
             Reader::RepeatedReader(_, def_level, rep_level, ref mut reader) => {
@@ -826,7 +826,7 @@ mod tests {
     macro_rules! row {
         ($($e:tt)*) => {
             {
-                make_row(vec![$($e)*])
+                Row::new(vec![$($e)*])
             }
         }
     }
diff --git a/parquet_derive/LICENSE.txt b/parquet_derive/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/parquet_derive/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/parquet_derive/NOTICE.txt b/parquet_derive/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/parquet_derive/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file


Test patch:
diff --git a/arrow-integration-test/LICENSE.txt b/arrow-integration-test/LICENSE.txt
new file mode 120000
index 000000000..4ab43736a
--- /dev/null
+++ b/arrow-integration-test/LICENSE.txt
@@ -0,0 +1,1 @@
+../LICENSE.txt
\ No newline at end of file
diff --git a/arrow-integration-test/NOTICE.txt b/arrow-integration-test/NOTICE.txt
new file mode 120000
index 000000000..eb9f24e04
--- /dev/null
+++ b/arrow-integration-test/NOTICE.txt
@@ -0,0 +1,1 @@
+../NOTICE.txt
\ No newline at end of file
diff --git a/arrow-pyarrow-integration-testing/Cargo.toml b/arrow-pyarrow-integration-testing/Cargo.toml
index 0834f2d13..03d08df30 100644
--- a/arrow-pyarrow-integration-testing/Cargo.toml
+++ b/arrow-pyarrow-integration-testing/Cargo.toml
@@ -34,4 +34,4 @@ crate-type = ["cdylib"]
 
 [dependencies]
 arrow = { path = "../arrow", features = ["pyarrow"] }
-pyo3 = { version = "0.22", features = ["extension-module"] }
+pyo3 = { version = "0.23", features = ["extension-module"] }
diff --git a/arrow-pyarrow-integration-testing/src/lib.rs b/arrow-pyarrow-integration-testing/src/lib.rs
index e12c1389e..d4908fff0 100644
--- a/arrow-pyarrow-integration-testing/src/lib.rs
+++ b/arrow-pyarrow-integration-testing/src/lib.rs
@@ -43,7 +43,7 @@ fn to_py_err(err: ArrowError) -> PyErr {
 #[pyfunction]
 fn double(array: &Bound<PyAny>, py: Python) -> PyResult<PyObject> {
     // import
-    let array = make_array(ArrayData::from_pyarrow_bound(&array)?);
+    let array = make_array(ArrayData::from_pyarrow_bound(array)?);
 
     // perform some operation
     let array = array