Skip to content

[OPIK-6341] [SDK] feat: add --destination-project + --exclude-experiments to opik import dataset#6573

Draft
JetoPistola wants to merge 1 commit intomainfrom
danield/OPIK-6341-add-destination-project-and-exclude-experiments-flags
Draft

[OPIK-6341] [SDK] feat: add --destination-project + --exclude-experiments to opik import dataset#6573
JetoPistola wants to merge 1 commit intomainfrom
danield/OPIK-6341-add-destination-project-and-exclude-experiments-flags

Conversation

@JetoPistola
Copy link
Copy Markdown
Contributor

Details

First half of OPIK-6341 — adds --destination-project NAME and --exclude-experiments to opik import dataset, and threads destination_project through the dataset / experiment importers (transplants the plumbing from parked PR #6555 so it can land independently). With --destination-project set, the dataset import path now also runs import_experiments_from_directory against any sibling experiments/ dir using the same project override (gated by --exclude-experiments). Defaults preserve today's opik import behaviour exactly: cross-instance migrations and no-flag invocations are unchanged.

  • The same-workspace suffix-rename workaround (the part that makes self-hosted v1→v2 same-workspace migration actually work despite the (workspace_id, name) uniqueness constraint) is not in this commit. Keeping it as a separate follow-up commit so the rename design (<source>__<sanitized_project>, _001/_002 fallback, opik-auto-copy tag, provenance description) can be reviewed in isolation. Still thinking through edge cases — e.g. how to interact with the planned cleanup-sweep ticket, whether the tag should live on the dataset or as metadata, how to size the get_datasets() collision-detection cap.
  • Until Commit 2 lands, same-workspace migration still 409s with a clear error rather than working. Cross-instance migration is fully unblocked.
  • Implementation note: kept the existing try get_dataset / except → create_dataset shape rather than switching to get_or_create_dataset (as PR [OPIK-6246] [SDK] feat: add 'opik copy dataset' CLI command #6555 does) to minimize behaviour change in this commit. Commit 2 will likely revisit that choice in the rename branch.

Change checklist

  • User facing
  • Documentation update

Issues

AI-WATERMARK

AI-WATERMARK: yes

  • Tools: Claude Code
  • Model(s): Claude Opus 4.7 (1M context)
  • Scope: full implementation (plumbing transplant, Click flags, sibling-experiments wiring, tests)
  • Human verification: code review pending; manual end-to-end smoke test pending against localhost:5174 (will be done before un-drafting alongside Commit 2)

Testing

Unit tests (run from sdks/python):

  • 13 new tests in tests/unit/cli/test_import_dataset.py covering:
    • Click surface: --destination-project / --exclude-experiments parsed and threaded into _import_by_type; --help lists them; no-flags invocation preserves old kwargs (regression guard).
    • destination_project plumbing: client.get_dataset / client.create_dataset / client.trace / client.span all receive the override; recreate_experiments rewrites project_name; no-override path unchanged.
    • Sibling-experiments branch: fires when --destination-project set + experiments/ dir present; suppressed by --exclude-experiments; no-op when experiments/ absent (typical opik export dataset layout); does NOT fire without --destination-project.
  • 211 pre-existing tests/unit/cli/ tests still pass — no regressions.
PYTHONPATH="$(pwd)/src" python -m pytest tests/unit/cli/ -q
# 211 passed

Pre-commit (ruff, ruff-format, mypy) clean via sdks/python/.pre-commit-config.yaml.

Manual smoke test against localhost:5174 is pending and will be done before un-drafting alongside Commit 2.

Documentation

N/A for this PR. CLI help text is the user-facing surface for v1; broader docs / migration-guide updates will land alongside the wider OPIK-5859 epic.

…ents to opik import dataset

Plumbs `destination_project` through `import_datasets_from_directory`,
`import_experiments_from_directory`, `_import_traces_from_projects_directory`,
`_build_dataset_item_id_map`, and `recreate_experiments`. Defaults preserve
today's `opik import` behaviour exactly.

Adds two flags on `opik import dataset`:
- `--destination-project NAME` overrides every `project_name` the importers
  would otherwise infer.
- `--exclude-experiments` skips the new sibling-experiments import branch.

When `--destination-project` is set and a sibling `experiments/` directory
exists alongside `datasets/`, the dataset import path now also runs
`import_experiments_from_directory` with the same project override (gated
by `--exclude-experiments`). Cross-instance migration paths
(no `experiments/` dir) are unchanged.

This is the first half of OPIK-6341. The same-workspace suffix-rename
workaround (the part that lets self-hosted users import a dataset back
into the same workspace under a different project despite the
`(workspace_id, name)` uniqueness constraint) is intentionally left for
a follow-up commit so the rename design can be reviewed independently.

Implements OPIK-6341.
@github-actions github-actions Bot added python Pull requests that update Python code tests Including test files, or tests related like configuration. Python SDK labels Apr 30, 2026
Comment on lines +107 to +112
def test_help_lists_new_flags(self) -> None:
runner = CliRunner()
result = runner.invoke(cli, ["import", "ws", "dataset", "--help"])
assert result.exit_code == 0
assert "--destination-project" in result.output
assert "--exclude-experiments" in result.output
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Test names like test_help_lists_new_flags don’t match the test_<WHAT>__<CASE_DESCRIPTION>__<EXPECTED_RESULT>/test_<WHAT>__happyflow contract—should we rename them to follow the SDK convention?

Finding type: AI Coding Guidelines | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
sdks/python/tests/unit/cli/test_import_dataset.py around lines 107-512, rename the test
methods in classes TestClickSurface, TestDestinationProjectPlumbing, and
TestSiblingExperimentsBranch to follow the required naming contract from
sdks/python/AGENTS.md and .agents/skills/python-sdk/testing.md (either
`test_<WHAT>__<CASE_DESCRIPTION>__<EXPECTED_RESULT>` or `test_<WHAT>__happyflow`). For
each existing `test_*` method (e.g., test_help_lists_new_flags,
test_destination_project_threaded_into_import_by_type,
test_exclude_experiments_threaded_into_import_by_type,
test_no_flags_preserves_old_invocation, and the sibling experiments/plumbing tests),
update the function name only—keep the test bodies/assertions unchanged. Ensure the
new names clearly encode the WHAT being tested, the scenario/case (flags set, missing
directory, no destination project), and the expected result (called/not called,
destination_project threaded/None, correct project_name usage, exit_code assertions).

Comment on lines +138 to +149
if destination_project is not None and not exclude_experiments:
experiments_dir = base_path / "experiments"
if experiments_dir.exists() and any(
experiments_dir.glob("experiment_*.json")
):
debug_print(
f"Found experiments directory at {experiments_dir}, importing with destination_project={destination_project!r}",
debug,
)
experiments_stats = import_experiments_from_directory(
client,
experiments_dir,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This new _import_by_type block duplicates the full experiments import logic in import_all, so should we extract a shared helper (or reuse the existing import_all helper) to keep the experiments import in one place?

Finding type: Code Dedup and Conventions | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Comment on lines +157 to +162
# the summary surfaces both. ``import_experiments_from_directory``
# already returns the dataset keys it touched, so prefer
# the larger of the two when keys overlap rather than
# silently overwriting dataset counts with zero.
for key, value in experiments_stats.items():
stats[key] = max(stats.get(key, 0), value)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using max(...) undercounts the overlapping datasets_*/experiments_*/traces_*/prompts_* counters across the dataset and sibling-experiments phases, so should we accumulate these counters instead of taking the larger value?

Finding type: Logical Bugs | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
sdks/python/src/opik/cli/imports/__init__.py around lines 130-163, inside
`_import_by_type` under the `if import_type == "dataset"` and `if destination_project is
not None and not exclude_experiments` block, the code merges `stats` with
`experiments_stats` using `stats[key] = max(stats.get(key, 0), value)`. Replace this
merge strategy so overlapping counters from the dataset import and the
sibling-experiments import are accumulated (e.g., sum them) rather than taking only the
larger value; this prevents the summary and later “Import completed with …
error(s)” logic from undercounting work. Ensure that keys returned only by
`import_experiments_from_directory` (e.g., experiments_*/traces_*/prompts_*) are still
added to `stats` as-is, and only the overlap behavior changes from max to accumulation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Python SDK python Pull requests that update Python code tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant