-
Notifications
You must be signed in to change notification settings - Fork 200
feat: define public generation invocation contract #854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/slurm-execution
Are you sure you want to change the base?
Changes from 6 commits
2841525
63040a9
4c9c794
a5cac7e
b0a55c7
cf0f0bd
40daaa9
1945750
40ab405
8ad60d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
|
|
||
| import data_designer.config.errors as config_errors | ||
| from data_designer.config import ( | ||
| DataDesignerConfigBuilder, | ||
| InvalidConfigError, | ||
| InvalidFileFormatError, | ||
| InvalidFilePathError, | ||
| ) | ||
|
|
||
|
|
||
| def test_invalid_config_error_is_publicly_exported() -> None: | ||
| assert InvalidConfigError is config_errors.InvalidConfigError | ||
|
|
||
|
|
||
| def test_builder_file_errors_are_publicly_exported() -> None: | ||
| assert InvalidFileFormatError is config_errors.InvalidFileFormatError | ||
| assert InvalidFilePathError is config_errors.InvalidFilePathError | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("filename", ["missing.yaml", "missing.json", "missing.YAML", "missing.JsOn"]) | ||
| def test_from_config_normalizes_missing_file_error(tmp_path: Path, filename: str) -> None: | ||
| with pytest.raises(InvalidFilePathError) as exc_info: | ||
| DataDesignerConfigBuilder.from_config(str(tmp_path / filename)) | ||
|
|
||
| assert isinstance(exc_info.value.__cause__, FileNotFoundError) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("extension", ["json", "yaml", "yml"]) | ||
| def test_from_config_accepts_inline_config_ending_in_supported_extension(extension: str) -> None: | ||
| builder = DataDesignerConfigBuilder.from_config( | ||
| f""" | ||
| columns: | ||
| - name: category | ||
| column_type: sampler | ||
| sampler_type: category | ||
| params: | ||
| values: | ||
| - value.{extension}""" | ||
| ) | ||
|
|
||
| assert builder.get_column_config("category").params.values == [f"value.{extension}"] | ||
|
|
||
|
|
||
| def test_from_config_normalizes_undecodable_file_error(tmp_path: Path) -> None: | ||
| config_path = tmp_path / "invalid.yaml" | ||
| config_path.write_bytes(b"\xff") | ||
|
|
||
| with pytest.raises(InvalidFileFormatError) as exc_info: | ||
| DataDesignerConfigBuilder.from_config(config_path) | ||
|
|
||
| assert isinstance(exc_info.value.__cause__, UnicodeDecodeError) | ||
|
|
||
|
|
||
| def test_from_config_normalizes_malformed_config_error() -> None: | ||
| with pytest.raises(InvalidFileFormatError): | ||
| DataDesignerConfigBuilder.from_config("data_designer: [") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,10 +266,12 @@ def create( | |
|
|
||
| Returns: | ||
| DatasetCreationResults object with methods for loading the generated dataset, | ||
| analysis results, and displaying sample records for inspection. | ||
| analysis results, displaying sample records, and inspecting completion, | ||
| artifact, and resume metadata. | ||
|
|
||
| Raises: | ||
| DataDesignerGenerationError: If an error occurs during dataset generation. | ||
| DataDesignerEarlyShutdownError: If early shutdown produces no records. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this accurate |
||
| DataDesignerProfilingError: If an error occurs during dataset profiling. | ||
| """ | ||
| logger.info("π¨ Creating Data Designer dataset") | ||
|
|
@@ -378,6 +380,10 @@ def create( | |
| config_builder=config_builder, | ||
| dataset_metadata=dataset_metadata, | ||
| task_traces=task_traces, | ||
| requested_num_records=num_records, | ||
| early_shutdown=builder.early_shutdown, | ||
| requested_resume_mode=resume, | ||
| effective_resume_mode=builder.artifact_storage.resume, | ||
| ) | ||
|
|
||
| async def acreate( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| from data_designer.config.config_builder import DataDesignerConfigBuilder | ||
| from data_designer.config.dataset_metadata import DatasetMetadata | ||
| from data_designer.config.errors import InvalidFileFormatError | ||
| from data_designer.config.run_config import ResumeMode | ||
| from data_designer.config.seed_source_dataframe import DataFrameSeedSource | ||
| from data_designer.config.utils.visualization import WithRecordSamplerMixin | ||
| from data_designer.engine.dataset_builders.errors import ArtifactStorageError | ||
|
|
@@ -50,6 +51,10 @@ def __init__( | |
| config_builder: DataDesignerConfigBuilder, | ||
| dataset_metadata: DatasetMetadata, | ||
| task_traces: list[TaskTrace] | None = None, | ||
| requested_num_records: int | None = None, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be nullable? |
||
| early_shutdown: bool | None = None, | ||
| requested_resume_mode: ResumeMode | None = None, | ||
| effective_resume_mode: ResumeMode | None = None, | ||
| ): | ||
| """Creates a new instance with results based on a dataset creation run. | ||
|
|
||
|
|
@@ -62,12 +67,40 @@ def __init__( | |
| Resume note: only contains traces for the current invocation; traces | ||
| from earlier ``create()`` calls that this run resumed are not | ||
| retained. | ||
| requested_num_records: Number of records requested for this invocation. | ||
| early_shutdown: Whether generation stopped at the early-shutdown gate. | ||
| ``None`` when no generation invocation produced this result object. | ||
| requested_resume_mode: Resume mode requested for this invocation, or | ||
| ``None`` when no generation invocation produced this result object. | ||
| effective_resume_mode: Resume mode selected after compatibility checks, | ||
| or ``None`` when no generation invocation produced this result object. | ||
| """ | ||
| self.artifact_storage = artifact_storage | ||
| self._analysis = analysis | ||
| self._config_builder = config_builder | ||
| self.dataset_metadata = dataset_metadata | ||
| self.task_traces: list[TaskTrace] = task_traces or [] | ||
| self.requested_num_records = requested_num_records | ||
| self.early_shutdown = early_shutdown | ||
| self.requested_resume_mode = requested_resume_mode | ||
| self.effective_resume_mode = effective_resume_mode | ||
|
|
||
| @property | ||
| def dataset_path(self) -> Path: | ||
| """Return the resolved dataset directory for this result.""" | ||
| return self.artifact_storage.base_dataset_path | ||
|
|
||
| @property | ||
| def actual_num_records(self) -> int: | ||
| """Return the total number of records in the result dataset.""" | ||
| return self.count_records() | ||
|
|
||
| @property | ||
| def is_partial(self) -> bool | None: | ||
| """Return whether the result contains fewer records than requested.""" | ||
| if self.requested_num_records is None: | ||
| return None | ||
| return self.actual_num_records < self.requested_num_records | ||
|
|
||
| def load_analysis(self) -> DatasetProfilerResults: | ||
| """Load the profiling analysis results for the generated dataset. | ||
|
|
@@ -83,8 +116,17 @@ def load_dataset(self) -> pd.DataFrame: | |
|
|
||
| Returns: | ||
| A pandas DataFrame containing the full generated dataset. | ||
|
|
||
| Raises: | ||
| ArtifactStorageError: If the dataset artifacts are missing. | ||
| """ | ||
| return self.artifact_storage.load_dataset() | ||
| try: | ||
| dataset = self.artifact_storage.load_dataset() | ||
| except OSError as e: | ||
| raise ArtifactStorageError(f"Failed to load dataset artifacts: {e}") from e | ||
| if dataset is not None and dataset.empty: | ||
| self._get_batch_files() | ||
| return dataset | ||
|
|
||
| def to_config_builder(self, columns: list[str] | None = None) -> DataDesignerConfigBuilder: | ||
| """Create a new config builder seeded from this result dataset. | ||
|
|
@@ -108,10 +150,19 @@ def count_records(self) -> int: | |
|
|
||
| Returns: | ||
| Total row count across all batch parquet files. | ||
|
|
||
| Raises: | ||
| ArtifactStorageError: If the dataset artifacts are missing. | ||
| """ | ||
| batch_files = sorted(self.artifact_storage.final_dataset_path.glob("batch_*.parquet")) | ||
| batch_files = self._get_batch_files() | ||
| return sum(lazy.pq.read_metadata(f).num_rows for f in batch_files) | ||
|
|
||
| def _get_batch_files(self) -> list[Path]: | ||
| batch_files = sorted(self.artifact_storage.final_dataset_path.glob("batch_*.parquet")) | ||
| if not batch_files: | ||
| raise ArtifactStorageError("No batch parquet files found.") | ||
| return batch_files | ||
|
|
||
| def load_processor_dataset(self, processor_name: str) -> pd.DataFrame: | ||
| """Load the dataset generated by a processor. | ||
|
|
||
|
|
@@ -178,9 +229,7 @@ def export(self, path: Path | str, *, format: ExportFormat | None = None) -> Pat | |
| raise InvalidFileFormatError( | ||
| f"Unsupported export format: {resolved_format!r}. Choose one of: {', '.join(SUPPORTED_EXPORT_FORMATS)}." | ||
| ) | ||
| batch_files = sorted(self.artifact_storage.final_dataset_path.glob("batch_*.parquet")) | ||
| if not batch_files: | ||
| raise ArtifactStorageError("No batch parquet files found to export.") | ||
| batch_files = self._get_batch_files() | ||
| if resolved_format == "jsonl": | ||
| _export_jsonl(batch_files, path) | ||
| elif resolved_format == "csv": | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.