huggingface / huggingface/datasets

`push_to_hub()` keeps stale `dataset_info.features` when re-pushing a single split with different columns

Open Beginner friendly
#8,608 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
22k
Forks
3.4k
Avg merge
5d 7h
Merged PRs (30d)
17

Description

### Describe the bug

Re-pushing a dataset's only split with different columns updates the shards and the split
sizes in the README, but silently keeps the **old** `dataset_info.features`. The card then
declares a schema the parquet files no longer have, and the dataset viewer fails on the
first batch with `CastError: Couldn't cast ... because column names don't match`.
`load_dataset` fails the same way.

In `_get_updated_dataset_card` (`src/datasets/arrow_dataset.py`), when the repo already has
a `dataset_info` for the config, the existing `repo_info` is loaded and its splits and sizes
are updated, but the `features` argument is never assigned to it:

```python
if repo_info is not None and not remove_other_splits:
for split_info, deleted_size, uploaded_size in zip(splits_info, deleted_sizes, uploaded_sizes):
split = split_info.name
if repo_info.splits and any(s != split for s in repo_info.splits):
if features != repo_info.features:
raise ValueError(...)
...
repo_info.splits[split] = split_info
info_to_dump = repo_info # <- repo_info.features is still the old schema
```

There is a mismatch guard, but it only fires when *other* splits exist
(`any(s != split for s in repo_info.splits)`). A dataset with a single `train` split being
replaced never trips it, so the changed schema is dropped without a warning.

The result is a card that is half-updated: `num_examples` reflects the new push while
`features` is from the previous one, which makes it look like the push succeeded.

### Steps to reproduce the bug

```python
from datasets import Dataset

ds = Dataset.from_dict({"x": [1, 2, 3], "dropped": ["a", "b", "c"]})
ds.push_to_hub("/repro", split="train")

# same split, different columns
ds2 = Dataset.from_dict({"x": [1, 2, 3, 4], "added": [10, 20, 30, 40]})
ds2.push_to_hub("/repro", split="train")

# README now declares x + dropped, while the parquet holds x + added
from datasets import load_dataset
load_dataset("/repro") # CastError: ... because column names don't match
```

The card logic can also be exercised directly, without the Hub:

```python
from fsspec.implementations.dirfs import DirFileSystem
from fsspec.implementations.memory import MemoryFileSystem
from datasets import config
from datasets.arrow_dataset import _get_updated_dataset_card
from datasets.features import Features, Value
from datasets.info import DatasetInfosDict
from datasets.splits import SplitInfo

fs = DirFileSystem("/repro", fs=MemoryFileSystem(skip_instance_cache=True))
with fs.open(config.REPOCARD_FILENAME, "w") as f:
f.write(
"---\nconfigs:\n- config_name: default\n data_files:\n - split: train\n path: data/train-*\n"
"dataset_info:\n features:\n - name: x\n dtype: int64\n - name: dropped\n dtype: string\n"
" splits:\n - name: train\n num_bytes: 40\n num_examples: 5\n"
" download_size: 40\n dataset_size: 40\n---\n"
)

card, _ = _get_updated_dataset_card(
fs=fs,
config_name="default",
splits_info=[SplitInfo(name="train", num_bytes=80, num_examples=7)],
features=Features({"x": Value("int64"), "added": Value("int64")}),
data_dir="data",
set_default=None,
uploaded_sizes=[80],
deleted_sizes=[40],
remove_other_splits=False,
)

info = DatasetInfosDict.from_dataset_card_data(card.data)["default"]
print(list(info.features)) # ['x', 'dropped'] <- stale
print(info.splits["train"].num_examples) # 7 <- updated
```

### Expected behavior

The pushed `features` should be written to the card when the push replaces the split set the
card describes, so the declared schema matches the shards that were just uploaded.

An alternative would be to widen the existing guard to raise whenever the features change,
but that would break the legitimate case of re-pushing a single-split dataset with different
columns, which works fine today apart from the card.

### Environment info

- `datasets` version: 5.0.1, and the code is unchanged on `main` (d336dcb)
- Platform: macOS (darwin)
- Python: 3.13

Happy to open a PR - I have a one-line fix and two unit tests alongside the existing
`_get_updated_dataset_card` tests in `tests/test_buckets.py`.

Contributor guide

Open the contributing guide

Research direction

Start in src/datasets/arrow_dataset.py at _get_updated_dataset_card and review the existing tests in tests/test_buckets.py. Exercise the helper with the in-memory card and replacement train split described in the issue. Done means the updated card reports the new features alongside the updated split metadata, with regression tests covering the single-split replacement.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.