-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[NA] [SDK] fix: address review comments on dataset insert deduplication #8115
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
Changes from 1 commit
fa41d67
00c79c8
9c86938
6b69b47
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 |
|---|---|---|
|
|
@@ -284,6 +284,10 @@ def get_test_suites( | |
| dataset_items_count=dataset_fern.dataset_items_count, | ||
| client=client, | ||
| ) | ||
| # This suite already holds items on the backend that we have not | ||
| # hashed locally, so the first insert must sync before it can tell | ||
| # a duplicate from a new item. | ||
| suite_dataset.__internal_api__hashes_synced__ = False | ||
|
Comment on lines
+287
to
+290
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. Listed suites lose owning project scopeIn Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents
Collaborator
Author
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. Not taking this one here, because it is pre-existing rather than introduced by this change.
Resolving the owning project per listed item, the way
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. Thanks for the context—this is an intentional lazy-sync trade-off shared with
Comment on lines
+287
to
+290
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. Missing listed-suite deduplication coverageThe regression test only checks the private Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by Other fix methodsPrompt for AI Agents
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. Commit 00c79c8 addressed this comment by adding an integration-style regression test that retrieves a populated suite, inserts a duplicate and a new item, and asserts only the new item is submitted. |
||
|
|
||
| suites.append( | ||
| test_suite_module.TestSuite( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -551,6 +551,68 @@ def test_insert__version_endpoint_unreachable__uploads_sequentially(monkeypatch) | |
| ), "A failing version probe must not break insert; it falls back to sequential" | ||
|
|
||
|
|
||
| def test_insert__version_probe_recovers__parallel_upload_resumes(monkeypatch): | ||
| """A transient probe failure must not pin the dataset to sequential uploads.""" | ||
| _small_batches(monkeypatch, size=_GATE_BATCH_SIZE) | ||
| mock_rest_client = Mock() | ||
| mock_rest_client.version.side_effect = [ | ||
| ConnectionError("backend unreachable"), | ||
| {"version": constants.MIN_BACKEND_VERSION_FOR_PARALLEL_INSERT}, | ||
| ] | ||
| dataset = Dataset( | ||
| name="test_dataset", | ||
| description="Test description", | ||
| project_name="Test project", | ||
| rest_client=mock_rest_client, | ||
| ) | ||
|
|
||
| dataset.insert(_make_items(4), deduplication=False) | ||
| dataset.insert(_make_items(4), deduplication=False) | ||
|
|
||
| assert mock_rest_client.version.call_count == 2, ( | ||
| "The failed probe must be retried rather than cached as unsupported" | ||
| ) | ||
| assert dataset._parallel_insert_supported, ( | ||
| "Once the backend answers, parallel upload must be available again" | ||
| ) | ||
|
Comment on lines
+582
to
+593
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. Parallel upload regression goes undetected
Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents
Collaborator
Author
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. Addressed in 00c79c8.
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. Addressed. The spy now verifies the effective worker count reaching |
||
|
|
||
|
|
||
| def test_insert__unparseable_version__probed_once(monkeypatch): | ||
| """An unparseable version is a conclusive answer, so it must still cache.""" | ||
| _small_batches(monkeypatch, size=_GATE_BATCH_SIZE) | ||
| mock_rest_client = _mock_rest_client("dev-local") | ||
| dataset = Dataset( | ||
| name="test_dataset", | ||
| description="Test description", | ||
| project_name="Test project", | ||
| rest_client=mock_rest_client, | ||
| ) | ||
|
|
||
| for _ in range(3): | ||
| dataset.insert(_make_items(4), deduplication=False) | ||
|
|
||
| assert mock_rest_client.version.call_count == 1, ( | ||
| "A version the SDK cannot parse will not change, so it must not be re-probed" | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_value", ["false", 0, 1, None, "", []]) | ||
| def test_insert__non_bool_deduplication__raises_before_any_request(bad_value): | ||
| mock_rest_client = Mock() | ||
| dataset = Dataset( | ||
| name="test_dataset", | ||
| description="Test description", | ||
| project_name="Test project", | ||
| rest_client=mock_rest_client, | ||
| ) | ||
|
|
||
| with pytest.raises(ValueError, match="deduplication must be a bool"): | ||
| dataset.insert(_make_items(3), deduplication=bad_value) | ||
|
|
||
| mock_rest_client.datasets.create_or_update_dataset_items.assert_not_called() | ||
| mock_rest_client.version.assert_not_called() | ||
|
|
||
|
|
||
| def test_insert__sequential__uploads_sequentially_without_probing_version(monkeypatch): | ||
| mock_rest_client = _mock_rest_client() | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| from unittest.mock import Mock | ||
|
|
||
| from opik.api_objects.dataset import rest_operations | ||
|
|
||
|
|
||
| def _find_datasets_returning(*pages) -> Mock: | ||
| """A rest client whose find_datasets yields the given pages, then an empty one. | ||
|
|
||
| The pagination loop only stops on an empty page, so the trailing empty page | ||
| is what keeps these tests from spinning forever. | ||
| """ | ||
| mock_rest_client = Mock() | ||
| mock_rest_client.datasets.find_datasets.side_effect = [ | ||
| Mock(content=list(page)) for page in (*pages, ()) | ||
| ] | ||
| return mock_rest_client | ||
|
|
||
|
|
||
| def _backend_dataset(name: str, type_: str, items_total: int) -> Mock: | ||
| dataset_fern = Mock() | ||
| dataset_fern.configure_mock( | ||
| name=name, | ||
| description="", | ||
| type=type_, | ||
| dataset_items_count=items_total, | ||
| ) | ||
| return dataset_fern | ||
|
|
||
|
|
||
| def test_get_test_suites__suite_holds_backend_items__first_insert_syncs_hashes(): | ||
| """A listed suite must not treat its empty local hash set as authoritative. | ||
|
|
||
| Otherwise the first deduplicated insert compares against nothing, decides | ||
| every item is new, and resubmits items the suite already holds. | ||
| """ | ||
| mock_rest_client = _find_datasets_returning( | ||
| [_backend_dataset("my-suite", "evaluation_suite", items_total=25)] | ||
| ) | ||
|
|
||
| suites = rest_operations.get_test_suites( | ||
| project_name="Test project", | ||
| rest_client=mock_rest_client, | ||
| ) | ||
|
|
||
| assert len(suites) == 1 | ||
| assert not suites[0]._dataset.__internal_api__hashes_synced__, ( | ||
| "A suite listed from the backend has items we have not hashed locally, " | ||
| "so the first insert must sync before deduplicating" | ||
| ) | ||
|
Comment on lines
+42
to
+72
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. Suite deduplication behavior remains untestedThis test only inspects private Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents
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. Commit 00c79c8 addressed this comment by exercising the first deduplicating insert with existing and new items, then asserting only the new item is submitted. It does not explicitly assert the fetch call itself. |
||
|
|
||
|
|
||
| def test_get_test_suites__non_suite_datasets__are_skipped(): | ||
| mock_rest_client = _find_datasets_returning( | ||
| [ | ||
| _backend_dataset("plain-dataset", "dataset", items_total=3), | ||
| _backend_dataset("my-suite", "evaluation_suite", items_total=3), | ||
| ] | ||
| ) | ||
|
|
||
| suites = rest_operations.get_test_suites( | ||
| project_name="Test project", | ||
| rest_client=mock_rest_client, | ||
| ) | ||
|
|
||
| assert [suite.name for suite in suites] == ["my-suite"] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internal inserts bypass backend safety gate
The internal insertion funnel validates
deduplicationbut forwardsnum_threadsto_send_batcheswithout applying the_parallel_insert_supportedgate, so direct calls withnum_threads > 1useThreadPoolExecutoragainst old backends instead of the fallback ininsert()— should we apply the same worker-count compatibility check here?Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit 00c79c8 addressed this comment by applying the
_parallel_insert_supportedgate inside the internal insertion funnel before forwardingnum_threadsto batch sending. Unsupported backends now force worker count to 1, including for direct internal callers.