Skip to content

Commit a875edb

Browse files
committed
CC Self-review. Applied lint. Using new method in the high level API (XarrayContext). This leads to two failed tests, but this could be caused by test errors.
1 parent bac5d5f commit a875edb

8 files changed

Lines changed: 610 additions & 629 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ __pycache__
88
.idea
99
target
1010
test_data
11-
*.so
11+
*.so

Cargo.lock

Lines changed: 0 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,10 @@ exclude = ["perf_tests/*"]
77

88
[dependencies]
99
arrow = { version = "55.2.0", features = ["pyarrow"] }
10-
arrow-array = { version = "55.2.0" }
11-
arrow-schema = { version = "55.2.0" }
12-
async-trait = "0.1.89"
1310
datafusion = { version = "49.0.0" }
14-
datafusion-ffi = {version = "49.0.0" }
15-
futures = "0.3.31"
11+
datafusion-ffi = { version = "49.0.0" }
1612
pyo3 = { version = "0.24.1", features = ["extension-module"] }
17-
tokio = { version = "1.46.1", features = ["rt", "rt-multi-thread", "macros"] }
13+
tokio = { version = "1.46.1", features = ["rt"] }
1814

1915

2016
[build-dependencies]

src/lib.rs

Lines changed: 26 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,25 @@ impl PartitionStream for PyArrowStreamPartition {
7373
Python::with_gil(|py| {
7474
let bound = obj.bind(py);
7575

76-
// Create a stream reader and collect all batches
7776
match ArrowArrayStreamReader::from_pyarrow_bound(bound) {
78-
Ok(reader) => reader.filter_map(|result| result.ok()).collect(),
79-
Err(_) => vec![],
77+
Ok(reader) => {
78+
// Collect batches, propagating errors as warnings
79+
// In streaming context, we can't easily return errors,
80+
// so we log and skip failed batches
81+
reader
82+
.filter_map(|result| match result {
83+
Ok(batch) => Some(batch),
84+
Err(e) => {
85+
eprintln!("Warning: Failed to read batch: {e}");
86+
None
87+
}
88+
})
89+
.collect()
90+
}
91+
Err(e) => {
92+
eprintln!("Warning: Failed to create stream reader: {e}");
93+
vec![]
94+
}
8095
}
8196
})
8297
}
@@ -86,7 +101,6 @@ impl PartitionStream for PyArrowStreamPartition {
86101
}
87102
};
88103

89-
// Wrap the batches in a MemoryStream
90104
Box::pin(
91105
MemoryStream::try_new(batches, Arc::clone(&self.schema), None)
92106
.expect("MemoryStream creation should not fail with valid schema"),
@@ -190,28 +204,10 @@ impl LazyArrowStreamTable {
190204
)
191205
}
192206

193-
/// Get the schema of the table.
207+
/// Get the schema of the table as a PyArrow Schema.
194208
fn schema(&self, py: Python<'_>) -> PyResult<PyObject> {
195-
let schema = self.table.schema();
196-
// Convert to PyArrow schema
197-
let pyarrow = py.import("pyarrow")?;
198-
let schema_cls = pyarrow.getattr("schema")?;
199-
200-
// Build field list
201-
let fields: Vec<_> = schema
202-
.fields()
203-
.iter()
204-
.map(|f: &arrow::datatypes::FieldRef| {
205-
let field_fn = pyarrow.getattr("field").unwrap();
206-
let dtype = arrow_type_to_pyarrow(py, f.data_type()).unwrap();
207-
field_fn
208-
.call1((f.name(), dtype, f.is_nullable()))
209-
.unwrap()
210-
})
211-
.collect();
212-
213-
let result = schema_cls.call1((fields,))?;
214-
Ok(result.into())
209+
use arrow::pyarrow::ToPyArrow;
210+
self.table.schema().to_pyarrow(py)
215211
}
216212

217213
fn __repr__(&self) -> String {
@@ -222,84 +218,30 @@ impl LazyArrowStreamTable {
222218
}
223219
}
224220

225-
/// Get schema from a Python object that has a schema attribute or method.
221+
/// Get schema from a Python object that has a schema attribute.
226222
///
227-
/// This function extracts the schema WITHOUT consuming the stream, which is
223+
/// This extracts the schema WITHOUT consuming the stream, which is
228224
/// important for lazy evaluation.
229225
fn get_schema_from_stream(stream: &Bound<'_, PyAny>) -> PyResult<SchemaRef> {
230226
use arrow::datatypes::Schema;
231227
use arrow::pyarrow::FromPyArrow;
232228

233-
// Try to get the schema attribute (PyArrow RecordBatchReader has .schema property)
234229
let py_schema = stream.getattr("schema").map_err(|e| {
235230
pyo3::exceptions::PyTypeError::new_err(format!(
236-
"Object does not have a 'schema' attribute: {e}. \
237-
Expected a RecordBatchReader or similar object with a schema property."
231+
"Object must have a 'schema' attribute (e.g., RecordBatchReader): {e}"
238232
))
239233
})?;
240234

241-
// Convert the PyArrow Schema to Rust Schema
242235
let schema = Schema::from_pyarrow_bound(&py_schema).map_err(|e| {
243-
pyo3::exceptions::PyTypeError::new_err(format!(
244-
"Failed to convert schema: {e}"
245-
))
236+
pyo3::exceptions::PyTypeError::new_err(format!("Failed to convert schema: {e}"))
246237
})?;
247238

248239
Ok(Arc::new(schema))
249240
}
250241

251-
/// Convert Arrow DataType to PyArrow type
252-
fn arrow_type_to_pyarrow(py: Python<'_>, dtype: &arrow::datatypes::DataType) -> PyResult<PyObject> {
253-
let pyarrow = py.import("pyarrow")?;
254-
255-
use arrow::datatypes::DataType;
256-
let result = match dtype {
257-
DataType::Null => pyarrow.call_method0("null")?,
258-
DataType::Boolean => pyarrow.call_method0("bool_")?,
259-
DataType::Int8 => pyarrow.call_method0("int8")?,
260-
DataType::Int16 => pyarrow.call_method0("int16")?,
261-
DataType::Int32 => pyarrow.call_method0("int32")?,
262-
DataType::Int64 => pyarrow.call_method0("int64")?,
263-
DataType::UInt8 => pyarrow.call_method0("uint8")?,
264-
DataType::UInt16 => pyarrow.call_method0("uint16")?,
265-
DataType::UInt32 => pyarrow.call_method0("uint32")?,
266-
DataType::UInt64 => pyarrow.call_method0("uint64")?,
267-
DataType::Float16 => pyarrow.call_method0("float16")?,
268-
DataType::Float32 => pyarrow.call_method0("float32")?,
269-
DataType::Float64 => pyarrow.call_method0("float64")?,
270-
DataType::Utf8 => pyarrow.call_method0("utf8")?,
271-
DataType::LargeUtf8 => pyarrow.call_method0("large_utf8")?,
272-
DataType::Binary => pyarrow.call_method0("binary")?,
273-
DataType::LargeBinary => pyarrow.call_method0("large_binary")?,
274-
DataType::Date32 => pyarrow.call_method0("date32")?,
275-
DataType::Date64 => pyarrow.call_method0("date64")?,
276-
DataType::Timestamp(unit, tz) => {
277-
let unit_str = match unit {
278-
arrow::datatypes::TimeUnit::Second => "s",
279-
arrow::datatypes::TimeUnit::Millisecond => "ms",
280-
arrow::datatypes::TimeUnit::Microsecond => "us",
281-
arrow::datatypes::TimeUnit::Nanosecond => "ns",
282-
};
283-
match tz {
284-
Some(tz) => pyarrow.call_method1("timestamp", (unit_str, tz.to_string()))?,
285-
None => pyarrow.call_method1("timestamp", (unit_str,))?,
286-
}
287-
}
288-
_ => {
289-
// Fallback: convert to string and let PyArrow parse it
290-
let type_str = format!("{dtype}");
291-
pyarrow
292-
.getattr("type_for_alias")?
293-
.call1((type_str.as_str(),))?
294-
}
295-
};
296-
297-
Ok(result.into())
298-
}
299-
300242
/// Python module initialization
301243
#[pymodule]
302244
fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
303245
m.add_class::<LazyArrowStreamTable>()?;
304246
Ok(())
305-
}
247+
}

xarray_sql/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
from .df import read_xarray, from_map
2-
from .reader import XarrayRecordBatchReader, read_xarray_lazy
2+
from .reader import XarrayRecordBatchReader, read_xarray_lazy, read_xarray_table
33
from .sql import XarrayContext
44
from ._native import LazyArrowStreamTable
5+
6+
__all__ = [
7+
# High-level API (recommended)
8+
"read_xarray_table",
9+
"XarrayContext",
10+
# Lower-level building blocks
11+
"read_xarray",
12+
"read_xarray_lazy",
13+
"from_map",
14+
"XarrayRecordBatchReader",
15+
"LazyArrowStreamTable",
16+
]

0 commit comments

Comments
 (0)