chore: persist train/test/transformed_training csvs before preflight check#660
chore: persist train/test/transformed_training csvs before preflight check#660nina-xu wants to merge 1 commit into
Conversation
Signed-off-by: nina-xu <19981858+nina-xu@users.noreply.github.com>
Walkthrough
ChangesDataset persistence flow
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/nemo_safe_synthesizer/sdk/library_builder.py (1)
435-437: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winReplace the runtime assertion with an explicit invariant check.
If the workdir invariant is ever broken, optimized Python removes the
assertand the next line fails with an opaqueAttributeError. Raise a preciseRuntimeErrorinstead.As per coding guidelines:
src/**/*.pymust not useassertfor validation in library code; raise an appropriate exception instead.
[details]Proposed fix
if not check_only: - assert self._workdir is not None + if self._workdir is None: + raise RuntimeError("Workdir must be initialized before persisting processed datasets") self._workdir.ensure_directories()Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 60c84a90-dd08-4be6-96cb-1fbd03be7a8c
📒 Files selected for processing (1)
src/nemo_safe_synthesizer/sdk/library_builder.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{md,markdown,py}
📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)
**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Place durable implementation guidance in function and class docstrings for public contracts and source comments for local invariants
Target Python 3.11–3.13 with modern syntax (X | Y,list[str],Self). Python 3.14+ is not supported
**/*.py: Use American English spelling in Python code, documentation, and messages.
UseField(description=...)for every Pydantic model field.
Use assignment-styleField()by default; useAnnotatedonly for additional metadata such as validators, constrained aliases, or discriminated unions.
Use@dataclass(frozen=True)for immutable value objects and validators; use mutable dataclasses only for builders, accumulators, and pipeline state.
Usefield(default_factory=list)instead of mutable list defaults.
UseStrEnumfor string-valued configuration or serialization enums and plainEnumfor internal constants.
Obtain loggers withobservability.get_logger(__name__); do not calllogging.getLogger()orstructlog.get_logger()directly.
Use.runtime,.user, and.systemcategory loggers appropriately.
Do not useprint()for operational library output; use the approved logger,click.echo(), orsys.stdout.write()where appropriate.
Useextra={}for machine-queryable logging data and f-strings only for human-readable context.
Raise errors from the custom Safe Synthesizer error hierarchy, using the documented dual inheritance for user and internal errors.
Keep shared package code compatible with Python 3.11; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic parameters.
PreferX | Y, built-in collection generics, andSelfoverOptional,Union, and legacy typing collections.
Use collection ABCs for function arguments and concrete collection types for return values.
UseProtocolfor structural subtyping and avoidAnywhenobject, generics, or protocols are suitable.
UseTYPE_CHECKINGguards for heavy imports such as pandas, torch, and transformers.
...
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
src/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
src/**/*.py: Use relative imports for package code undersrc/.
Do not useassertfor validation in library code; raise an appropriate exception instead.
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
⚙️ CodeRabbit configuration file
Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
**/*
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
**/*: Every source file requires the SPDX copyright and license header appropriate to its file format.
End files with a newline, remove trailing whitespace, use one space between sentences, and keep code, comments, and docstrings within 120 characters.
**/*: All merged commits must use Conventional Commits format:<type>(<scope>): <description>or<type>: <description>, with a lowercase valid type and a description no longer than 100 characters.
All contributions require a DCOSigned-off-bytrailer and a cryptographic commit signature.
Before submitting a pull request, run formatting, checks, and tests usingmise run format,mise run check, andmise run test.
Branches other thanmainmust use lowercase author-prefixed names in one of the documented forms, optionally including an issue ID and category.
Release tags must use avprefix and PEP 440 stable or release-candidate versions, such asv1.0.0orv0.1.0rc0; alpha versions and dashed-rcsuffixes are not used.
Do not move a published release tag; create and validate a new release candidate when code changes.
Usemise run <task>for project tasks; the Makefile only bootstraps mise and provides deprecated compatibility messages.
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
⚙️ CodeRabbit configuration file
**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.
- Refactor suggestion: use for local maintainability problems introduced
by the diff when they have clear future cost, such as duplicated setup,
unclear boundaries, over-mocking, avoidable complexity, or opaque test
helpers.- Nitpick: avoid in chill mode. Do not emit formatting, import-order,
wording, or style-only comments unless automated tools cannot catch the
issue and it affects maintainability.Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.
- Major: incorrect generation/training/evaluation behavior, broken
CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
cleanup and process-isolation bugs likely to fail CI or production
runs.- Minor: localized bugs, missing focused tests for changed behavior, or
bad test patterns that weaken regression coverage.- Trivial: small cleanup with no behavior impact. Usually suppress in
chill mode.- Info: context only. Avoid unless it helps reviewers understand risk.
Safe-Synthesizer-specific review focus: - Data ...
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
**/*.{py,sh,yaml,yml,md}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
All source files with
.py,.sh,.yaml,.yml, or.mdextensions must include SPDX copyright headers, except files listed in.copyrightignore.
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
src/nemo_safe_synthesizer/**/*.py
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Write durable module-level guidance in Python docstrings and source comments so it appears in the generated API reference.
Files:
src/nemo_safe_synthesizer/sdk/library_builder.py
🔇 Additional comments (1)
src/nemo_safe_synthesizer/sdk/library_builder.py (1)
438-446: 🔒 Security & PrivacyConfirm raw-data retention for failed runs.
training.csvandtest.csvcontain the original, unredacted splits, but they are now written before full preflight. A non-check-only preflight failure therefore leaves raw data on disk. Confirm that failed workdirs have the intended access controls and cleanup/retention policy; otherwise use a protected temporary artifact lifecycle.As per path instructions: privacy changes involving PII replacement and persisted datasets are high-risk and require explicit review.
Source: Path instructions
| # Persist the train/test splits now -- before the full preflight run | ||
| # below -- so that a preflight failure (e.g. group_exceeds_context) | ||
| # still leaves the processed datasets on disk for inspection. Skipped on | ||
| # the ``--validate`` path (``check_only``), which intentionally elides | ||
| # CSV writes. | ||
| if not check_only: | ||
| assert self._workdir is not None | ||
| self._workdir.ensure_directories() | ||
| # ``training.csv`` is the canonical persisted original training | ||
| # split -- reloaded by load_from_save_path and used for evaluation. | ||
| self._original_training_df.to_csv(self._workdir.dataset.training, index=False) | ||
| if not self._training_df.equals(self._original_training_df): | ||
| # The transformed (e.g. PII-replaced) training data is saved for | ||
| # inspection only -- we don't need it in generation or evaluation. | ||
| self._training_df.to_csv(self._workdir.dataset.transformed_training, index=False) | ||
| if self._test_df is not None: | ||
| self._test_df.to_csv(self._workdir.dataset.test, index=False) | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add regression coverage for persistence ordering and check_only.
Add focused tests using tmp_path that verify full-run persistence remains available when the later preflight raises, while process_data(check_only=True) creates no split CSVs. These are the two user-visible contracts introduced by this change.
As per path instructions: new behavior needs focused tests near the affected subsystem.
Source: Path instructions
| # inspection only -- we don't need it in generation or evaluation. | ||
| self._training_df.to_csv(self._workdir.dataset.transformed_training, index=False) | ||
| if self._test_df is not None: | ||
| self._test_df.to_csv(self._workdir.dataset.test, index=False) |
There was a problem hiding this comment.
agent-assisted (review-pr): Suggested addition addressing the raw-data-retention question raised by CodeRabbit.
The reordering itself looks correct, verified the failure-path behavior directly (wrote and ran a throwaway test against this exact commit: training.csv does persist when the full preflight check fails). The one open question was CodeRabbit's: raw training.csv/test.csv now land on disk even on a failed non-check_only run, where previously nothing was written.
Rather than blocking on that (it seems like the intended tradeoff, and workdirs already have no separate retention policy), a small log line would at least make the new behavior visible to the user in the moment it matters -- when preflight fails right after this write, they'd otherwise have no signal that their data was still saved.
| self._test_df.to_csv(self._workdir.dataset.test, index=False) | |
| self._test_df.to_csv(self._workdir.dataset.test, index=False) | |
| logger.user.info( | |
| f"Processed training data saved to {self._workdir.dataset.training} " | |
| "(available for inspection even if the preflight check below fails)", | |
| extra={"path": str(self._workdir.dataset.training)}, | |
| ) |
Verified this passes ruff check/ruff format and all 22 existing tests in tests/sdk/test_process_data.py.
There was a problem hiding this comment.
yeah that's a good suggestion. Really I think what needs to be saved is the transformed dataset, if any, because the user should have a copy of the original dataset they can inspect. I can change to only save early if transformation, i.e. PII replacement is done. That might just make the code a bit wacky looking though. What do you think is a good behavior to have here?
Summary
A quick adjustment to the order of operations around preflight:
Current behavior:
train/test split -> PII replacement -> preflight check -> persisting train/test/transformed_train
New behavior:
train/test split -> PII replacement -> persisting train/test/transformed_train -> preflight check
This is so that if preflight fails, we have the transformed training split to inspect. This happened to me because a faulty PII replacement prototype altered the shape of the input data significantly, thus failing the preflight. I suspect this would happen less frequently for main, but maybe it doesn't hurt to have? I'm open to closing this PR if we think this is not worth the while.
Pre-Review Checklist
Ensure that the following pass:
mise run format && mise run checkor via prek validation.mise run testpasses locallymise run test:e2epasses locallymise run test:ci-containerpasses locally (recommended)/syncon this PR to trigger a run (auto-triggers on ready-for-review)Pre-Merge Checklist
Other Notes
Summary by CodeRabbit