From 0c6373735aa22b2df7a33c96fe10e8cbaf6c4bb6 Mon Sep 17 00:00:00 2001 From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:36:17 -0700 Subject: [PATCH] fix: Raise a clear error for unresolvable FileSource file_format in DuckDB offline store Motivation: When using the DuckDB/ibis offline store, a FileSource whose file_format could not be determined (file_format not set and the path does not end in ".parquet", and it is not a Delta table) caused `_read_data_source()` in duckdb.py to fall through its if/elif chain and implicitly return None. That None then propagated to callers that called `.mutate(...)` on it, crashing several stack frames away from the real problem with `AttributeError: 'NoneType' object has no attribute 'mutate'`, with no indication of the actual cause. Approach: Add an explicit `else` branch to `_read_data_source()` that raises a `ValueError` explaining that the file format could not be determined, and suggesting the user either set `file_format` explicitly on the FileSource (e.g. ParquetFormat()) or use a path with a recognized extension (e.g. ".parquet"). This replaces a silent None return with an actionable error at the point where the format could not be resolved. Validation: Added sdk/python/tests/unit/infra/offline_stores/test_duckdb.py, which constructs a FileSource with an unresolvable format (a ".csv" path, no explicit file_format) and asserts that `_read_data_source` raises ValueError. Report: https://github.com/feast-dev/feast/issues/5390 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5 (via Claude Code) --- sdk/python/feast/infra/offline_stores/duckdb.py | 7 +++++++ .../unit/infra/offline_stores/test_duckdb.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 sdk/python/tests/unit/infra/offline_stores/test_duckdb.py diff --git a/sdk/python/feast/infra/offline_stores/duckdb.py b/sdk/python/feast/infra/offline_stores/duckdb.py index 1ff09d9f3cf..ebca2245d46 100644 --- a/sdk/python/feast/infra/offline_stores/duckdb.py +++ b/sdk/python/feast/infra/offline_stores/duckdb.py @@ -95,6 +95,13 @@ def _read_data_source(data_source: DataSource, repo_path: str) -> Table: if storage_options: return ibis.read_delta(data_source.path, storage_options=storage_options) return ibis.read_delta(data_source.path) + else: + raise ValueError( + f"Unable to determine the file format for data source " + f"'{data_source.name}' with path '{data_source.path}'. Either set " + f"'file_format' explicitly on the FileSource (e.g. ParquetFormat()) " + f"or use a path with a recognized file extension (e.g. '.parquet')." + ) def _read_iceberg_catalog_source(data_source: "IcebergSource", repo_path: str) -> Table: diff --git a/sdk/python/tests/unit/infra/offline_stores/test_duckdb.py b/sdk/python/tests/unit/infra/offline_stores/test_duckdb.py new file mode 100644 index 00000000000..1ed46279a8b --- /dev/null +++ b/sdk/python/tests/unit/infra/offline_stores/test_duckdb.py @@ -0,0 +1,15 @@ +import pytest + +from feast.infra.offline_stores.duckdb import _read_data_source +from feast.infra.offline_stores.file_source import FileSource + + +def test_read_data_source_raises_on_unresolvable_file_format(): + data_source = FileSource( + name="driver_hourly_stats_source", + path="data/driver_stats.csv", + timestamp_field="event_timestamp", + ) + + with pytest.raises(ValueError, match="Unable to determine the file format"): + _read_data_source(data_source, repo_path=".")