Skip to content

feat(ingest): add --skip-existing to skip already-ingested files - #968

Merged
lewisjared merged 7 commits into
mainfrom
feat/ingest-skip-existing
Oct 20, 2026
Merged

lewisjared merged 7 commits into
mainfrom
feat/ingest-skip-existing

Conversation

@lewisjared

@lewisjared lewisjared commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Adds ref datasets ingest --skip-existing, which leaves out files whose path is already in the database before they are parsed. This makes a periodic ingest of a large archive only open new files. It assumes files are immutable, so a full ingest without the flag is still needed to pick up changed files.

With --chunk-size, the database is asked about each chunk of discovered paths, so memory is bounded by the chunk size rather than the size of the archive. Files left after skipping are batched back up to the chunk size, so a mostly skipped run doesn't start a worker pool for each tiny remainder. Without --chunk-size the whole file list is still held in memory, as before.

for chunk in walk(root, chunk_size):
    todo += chunk - db_paths_in(chunk)
    if len(todo) >= chunk_size:
        parse(todo) -> validate -> register

Things worth a close look:

  • Adds a migration with an index on dataset_file.path, so the lookups don't scan the table. Dropping it is safe, but large deployments pay the index build on upgrade.
  • Adapters' find_local_datasets and iter_local_datasets take an optional skip callable. Third-party adapters that don't accept it keep working unless --skip-existing is used.
  • obs4MIPs and ESMValTool reference adapters gain iter_local_datasets, so --chunk-size now streams for every built-in adapter instead of falling back to the whole catalog.
  • A run that finds nothing new logs at info level and exits 0, so cron jobs stay quiet.
  • A new file added to an existing dataset triggers the existing "absent from the current ingest are being kept" warning for the skipped files. Nothing is removed.

While sizing chunks I found that CMIP6 _apply_fixes (a groupby().apply) peaks at about 34 KB per dataset, around 350 MB for a 10k chunk. A vectorised version peaks at about 15 MB. That is left for a separate PR because it changes row order.

Summary by CodeRabbit

  • New Features
    • Added --skip-existing to dataset ingestion, skipping files already in the database without parsing.
    • --chunk-size now supports streaming built-in reference datasets, including obs4MIPs, obs4REF, PMP climatology and ESMValTool reference datasets.
    • Combine both options to process large archives while limiting memory usage.
  • Documentation
    • Added guidance that skipped files are assumed immutable and periodic full ingests are recommended to detect changes.

Adds `ref datasets ingest --skip-existing`.
Files whose path is already in the database are left out before parsing,
so a periodic ingest only opens new files.
This assumes files are immutable, so a full ingest is still needed to pick up changes.

With `--chunk-size` the database is asked about each chunk of discovered paths,
so memory stays bounded by the chunk size rather than the archive size.
Adds an index on `dataset_file.path` to keep those lookups off a table scan.
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

📝 Walkthrough

The ingest command skips files already recorded in the database. Catalog builders and adapters accept skip callbacks. Obs4MIPs and ESMValTool reference adapters support chunked catalog iteration.

Changes: Dataset ingest

Layer / File(s) Summary
Ingested path lookup
packages/climate-ref/src/climate_ref/models/dataset.py, packages/climate-ref/src/climate_ref/migrations/versions/*, packages/climate-ref/src/climate_ref/datasets/__init__.py, packages/climate-ref/tests/unit/cli/test_datasets.py
DatasetFile.path gains a database index. New helpers look up ingested paths in batches and provide a skip callback. Tests cover lookup by source type.
Catalog filtering
packages/climate-ref/src/climate_ref/datasets/base.py, packages/climate-ref/src/climate_ref/datasets/catalog_builder.py, packages/climate-ref/tests/unit/datasets/test_catalog_builder.py, packages/climate-ref/tests/unit/datasets/test_datasets.py
The adapter contract and catalog builders accept skip callbacks. Builders filter paths before parsing, regroup remaining paths into parse batches, and handle cases where all paths are skipped.
Adapter filtering and streaming
packages/climate-ref/src/climate_ref/datasets/{cmip6.py,cmip7.py,esmvaltool_reference.py,obs4mips.py}, packages/climate-ref/tests/unit/datasets/test_esmvaltool_reference.py, packages/climate-ref/tests/unit/datasets/test_obs4mips.py, changelog/968.improvement.md
CMIP6 and CMIP7 pass skip callbacks to catalog builders. Obs4MIPs and ESMValTool reference adapters add chunked iteration and skip handling. Tests cover streaming and empty results.
Ingest command wiring
packages/climate-ref/src/climate_ref/cli/datasets.py, packages/climate-ref/tests/unit/cli/test_datasets.py, changelog/968.feature.md
The CLI adds --skip-existing and passes the callback to streaming or whole-catalog discovery. Empty-result messages distinguish skipped files from missing valid datasets. Tests cover repeat and chunked ingestion.

Sequence Diagram(s)

sequenceDiagram
  participant IngestCLI
  participant DatasetAdapter
  participant CatalogBuilder
  participant SkipCallback
  participant DatasetFileDB
  participant DatasetParser
  IngestCLI->>DatasetAdapter: Pass skip callback to discovery
  DatasetAdapter->>CatalogBuilder: Build catalog from discovered paths
  CatalogBuilder->>SkipCallback: Check discovered paths
  SkipCallback->>DatasetFileDB: Query ingested paths
  DatasetFileDB-->>SkipCallback: Return matching paths
  SkipCallback-->>CatalogBuilder: Return paths to skip
  CatalogBuilder->>DatasetParser: Parse remaining paths
Loading

Priority: ⬇️ Low

Change: Feature

Merge Risk: 🟡 Moderate · up to 7b1de

Resolve the metadata consistency risk and ensure invalid chunked ingests fail visibly instead of reporting success.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Description check Warning The description gives detailed and relevant technical context, but it does not use the required ## Description and ## Checklist sections or confirm the tests, documentation, and changelog checklis… Add the required ## Description and ## Checklist headings. Include checked confirmations for tests added, documentation added where applicable, and the changelog item added to changelog/.‌
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 66 functions across 15 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely summarises the main change: adding --skip-existing to skip files that were already ingested.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description gives detailed and relevant technical context, but it does not use the required ## Description and ## Checklist sections or confirm the tests, documentation, and changelog checklist items.

Resolution

Add the required ## Description and ## Checklist headings. Include checked confirmations for tests added, documentation added where applicable, and the changelog item added to changelog/.‌

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Files left after skipping are batched back up to the chunk size,
so a mostly skipped walk no longer starts a worker pool per tiny chunk.
Moves the skip function factory next to the database lookup and batches lookups with itertools.batched.
…unks

Adds iter_local_datasets to both reference adapters,
so --chunk-size and --skip-existing keep memory bounded for every built-in adapter.
@lewisjared
lewisjared marked this pull request as ready for review October 19, 2026 11:43
@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.39785% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ckages/climate-ref/src/climate_ref/cli/datasets.py 78.57% 2 Missing and 1 partial ⚠️
...ages/climate-ref/src/climate_ref/datasets/cmip7.py 50.00% 1 Missing and 1 partial ⚠️
...s/climate-ref/src/climate_ref/datasets/obs4mips.py 80.00% 1 Missing and 1 partial ⚠️
...f/src/climate_ref/datasets/esmvaltool_reference.py 90.00% 1 Missing ⚠️
Flag Coverage Δ
core 94.16% <91.39%> (+0.01%) ⬆️
providers 87.74% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...s/climate-ref/src/climate_ref/datasets/__init__.py 98.47% <100.00%> (+0.16%) ⬆️
...kages/climate-ref/src/climate_ref/datasets/base.py 96.68% <100.00%> (+0.05%) ⬆️
...te-ref/src/climate_ref/datasets/catalog_builder.py 96.89% <100.00%> (+0.78%) ⬆️
...ages/climate-ref/src/climate_ref/datasets/cmip6.py 96.15% <100.00%> (+0.10%) ⬆️
...ages/climate-ref/src/climate_ref/models/dataset.py 98.18% <100.00%> (ø)
...f/src/climate_ref/datasets/esmvaltool_reference.py 90.19% <90.00%> (+3.23%) ⬆️
...ages/climate-ref/src/climate_ref/datasets/cmip7.py 92.77% <50.00%> (-2.30%) ⬇️
...s/climate-ref/src/climate_ref/datasets/obs4mips.py 86.02% <80.00%> (-1.48%) ⬇️
...ckages/climate-ref/src/climate_ref/cli/datasets.py 91.46% <78.57%> (+0.74%) ⬆️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5a2b3452-f31d-430a-a047-779403be8d83
📥 Commits

Reviewing files that changed from the base of the PR and between 0e6d8b5 and 7d2dc6a.

📒 Files selected for processing: 16
  • changelog/968.feature.md
  • changelog/968.improvement.md
  • packages/climate-ref/src/climate_ref/cli/datasets.py
  • packages/climate-ref/src/climate_ref/datasets/__init__.py
  • packages/climate-ref/src/climate_ref/datasets/base.py
  • packages/climate-ref/src/climate_ref/datasets/catalog_builder.py
  • packages/climate-ref/src/climate_ref/datasets/cmip6.py
  • packages/climate-ref/src/climate_ref/datasets/cmip7.py
  • packages/climate-ref/src/climate_ref/datasets/esmvaltool_reference.py
  • packages/climate-ref/src/climate_ref/datasets/obs4mips.py
  • packages/climate-ref/src/climate_ref/migrations/versions/2026-10-19T0000_f8a9b0c1d2e3_add_dataset_file_path_index.py
  • packages/climate-ref/src/climate_ref/models/dataset.py
  • packages/climate-ref/tests/unit/cli/test_datasets.py
  • packages/climate-ref/tests/unit/datasets/test_catalog_builder.py
  • packages/climate-ref/tests/unit/datasets/test_datasets.py
  • packages/climate-ref/tests/unit/datasets/test_esmvaltool_reference.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread packages/climate-ref/src/climate_ref/cli/datasets.py
Comment thread packages/climate-ref/src/climate_ref/datasets/__init__.py Outdated
… limit

Only passes `skip` to adapters when --skip-existing is set,
so adapters with the previous signature still ingest.
Lowers the path lookup batch to 900, under the 999 parameter cap of SQLite before 3.32.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: aa4d94ee-3a8d-4ad4-8ad9-1e3dbff7e831
📥 Commits

Reviewing files that changed from the base of the PR and between 7d2dc6a and 25174a6.

📒 Files selected for processing: 11
  • packages/climate-ref/src/climate_ref/cli/datasets.py
  • packages/climate-ref/src/climate_ref/datasets/__init__.py
  • packages/climate-ref/src/climate_ref/datasets/base.py
  • packages/climate-ref/src/climate_ref/datasets/catalog_builder.py
  • packages/climate-ref/src/climate_ref/datasets/cmip6.py
  • packages/climate-ref/src/climate_ref/datasets/cmip7.py
  • packages/climate-ref/src/climate_ref/datasets/esmvaltool_reference.py
  • packages/climate-ref/src/climate_ref/datasets/obs4mips.py
  • packages/climate-ref/tests/unit/cli/test_datasets.py
  • packages/climate-ref/tests/unit/datasets/test_catalog_builder.py
  • packages/climate-ref/tests/unit/datasets/test_datasets.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread packages/climate-ref/src/climate_ref/datasets/catalog_builder.py
Comment thread packages/climate-ref/src/climate_ref/datasets/obs4mips.py Outdated
…alid

With --skip-existing, an archive of only invalid files returned an empty catalog and exited 0.
build_catalog now raises the adapter's error when files were parsed but none were valid,
and still returns an empty frame when skip removed every file.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Record parsed-but-invalid directories as failures in chunked mode. · datasets.py:374-387

packages/climate-ref/src/climate_ref/cli/datasets.py:374-387
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Record parsed-but-invalid directories as failures in chunked mode.

When --chunk-size and --skip-existing are enabled, the Obs4MIPs and ESMValTool iterators can parse the remaining files and yield no chunks when all files are invalid. The CLI then leaves total_files at zero, treats the directory as having no new files, and exits successfully without adding the directory to failed_dirs.

Pass the existing adapter-specific error to iter_built_catalogs(). Raise it only when parsing occurred and no valid chunk was emitted. This preserves the successful all-skipped path.

Suggested fix
 def iter_built_catalogs(  # noqa: PLR0913
     paths: list[str],
     parsing_func: DatasetParsingFunction,
     include_patterns: list[str] | None = None,
     n_jobs: int = 1,
     chunk_size: int = 10_000,
     skip: SkipFunction | None = None,
+    error_if_none_valid: str | None = None,
 ) -> Iterator[pd.DataFrame]:
 ...
     if not any_emitted and (skip is None or any_parsed):
+        if error_if_none_valid is not None:
+            raise ValueError(error_if_none_valid)
         logger.warning(f"No valid files found in {paths} matching {include_patterns}")
             n_jobs=self.n_jobs,
             chunk_size=chunk_size,
             skip=skip,
+            error_if_none_valid="No obs4MIPs-compliant datasets found",
         ):
             n_jobs=self.n_jobs,
             chunk_size=chunk_size,
             skip=skip,
+            error_if_none_valid="No ESMValTool reference datasets found",
         ):

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 206de5f6-d97b-4993-898e-b9621e18c35b
📥 Commits

Reviewing files that changed from the base of the PR and between 25174a6 and 7b1dee1.

📒 Files selected for processing: 5
  • packages/climate-ref/src/climate_ref/datasets/catalog_builder.py
  • packages/climate-ref/src/climate_ref/datasets/esmvaltool_reference.py
  • packages/climate-ref/src/climate_ref/datasets/obs4mips.py
  • packages/climate-ref/tests/unit/datasets/test_catalog_builder.py
  • packages/climate-ref/tests/unit/datasets/test_obs4mips.py
🚧 Files skipped from review as they are similar to previous changes: 3
  • packages/climate-ref/tests/unit/datasets/test_catalog_builder.py
  • packages/climate-ref/src/climate_ref/datasets/obs4mips.py
  • packages/climate-ref/src/climate_ref/datasets/catalog_builder.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

@lewisjared
lewisjared merged commit 90b48c6 into main Oct 20, 2026
28 of 29 checks passed
@lewisjared
lewisjared deleted the feat/ingest-skip-existing branch October 20, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant