From 0d9bd8fd492d172fc289e3368fe2263804ebf796 Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:51:02 +0100 Subject: [PATCH 1/8] feat(client): add methods to langfuse client to fetch and delete dataset runs --- langfuse/_client/client.py | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 4c76f11e2..48530166d 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -79,6 +79,11 @@ from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache from langfuse.api.resources.commons.errors.error import Error +from langfuse.api.resources.commons.types import DatasetRunWithItems +from langfuse.api.resources.datasets.types import ( + DeleteDatasetRunResponse, + PaginatedDatasetRuns, +) from langfuse.api.resources.ingestion.types.score_body import ScoreBody from langfuse.api.resources.prompts.types import ( CreatePromptRequest_Chat, @@ -2456,6 +2461,78 @@ def get_dataset( handle_fern_exception(e) raise e + def get_dataset_run( + self, dataset_name: str, *, run_name: str + ) -> DatasetRunWithItems: + """Fetch a dataset run by dataset name and run name. + + Args: + dataset_name (str): The name of the dataset. + run_name (str): The name of the run. + + Returns: + DatasetRunWithItems: The dataset run with its items. + """ + try: + return self.api.datasets.get_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + + def get_dataset_runs( + self, + dataset_name: str, + *, + page: Optional[int] = None, + limit: Optional[int] = None, + ) -> PaginatedDatasetRuns: + """Fetch all runs for a dataset. + + Args: + dataset_name (str): The name of the dataset. + page (Optional[int]): Page number, starts at 1. + limit (Optional[int]): Limit of items per page. + + Returns: + PaginatedDatasetRuns: Paginated list of dataset runs. + """ + try: + return self.api.datasets.get_runs( + dataset_name=self._url_encode(dataset_name), + page=page, + limit=limit, + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + + def delete_dataset_run( + self, dataset_name: str, *, run_name: str + ) -> DeleteDatasetRunResponse: + """Delete a dataset run and all its run items. This action is irreversible. + + Args: + dataset_name (str): The name of the dataset. + run_name (str): The name of the run. + + Returns: + DeleteDatasetRunResponse: Confirmation of deletion. + """ + try: + return self.api.datasets.delete_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + def run_experiment( self, *, From a162a6de0487098cfc21d7e2202b0bed5d6795b6 Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:21:38 +0100 Subject: [PATCH 2/8] feat(client): add methods to langfuse client to fetch and delete dataset runs --- langfuse/_client/client.py | 16 ++++++------ tests/test_datasets.py | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 48530166d..d8140edbf 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2462,7 +2462,7 @@ def get_dataset( raise e def get_dataset_run( - self, dataset_name: str, *, run_name: str + self, *, dataset_name: str, run_name: str ) -> DatasetRunWithItems: """Fetch a dataset run by dataset name and run name. @@ -2475,8 +2475,8 @@ def get_dataset_run( """ try: return self.api.datasets.get_run( - dataset_name=self._url_encode(dataset_name), - run_name=self._url_encode(run_name), + dataset_name=self._url_encode(dataset_name, is_url_param=True), + run_name=self._url_encode(run_name, is_url_param=True), request_options=None, ) except Error as e: @@ -2485,8 +2485,8 @@ def get_dataset_run( def get_dataset_runs( self, - dataset_name: str, *, + dataset_name: str, page: Optional[int] = None, limit: Optional[int] = None, ) -> PaginatedDatasetRuns: @@ -2502,7 +2502,7 @@ def get_dataset_runs( """ try: return self.api.datasets.get_runs( - dataset_name=self._url_encode(dataset_name), + dataset_name=self._url_encode(dataset_name, is_url_param=True), page=page, limit=limit, request_options=None, @@ -2512,7 +2512,7 @@ def get_dataset_runs( raise e def delete_dataset_run( - self, dataset_name: str, *, run_name: str + self, *, dataset_name: str, run_name: str ) -> DeleteDatasetRunResponse: """Delete a dataset run and all its run items. This action is irreversible. @@ -2525,8 +2525,8 @@ def delete_dataset_run( """ try: return self.api.datasets.delete_run( - dataset_name=self._url_encode(dataset_name), - run_name=self._url_encode(run_name), + dataset_name=self._url_encode(dataset_name, is_url_param=True), + run_name=self._url_encode(run_name, is_url_param=True), request_options=None, ) except Error as e: diff --git a/tests/test_datasets.py b/tests/test_datasets.py index c64a4adc1..372bc8eda 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -418,3 +418,54 @@ def execute_dataset_item(item, run_name): assert "args" in trace.input assert trace.input["args"][0] == expected_input assert trace.output == expected_input + + +def test_dataset_runs_with_special_characters(): + """Test that dataset runs work correctly with special characters in names.""" + langfuse = Langfuse(debug=False) + + # Test with various special characters that need URL encoding + dataset_name = f"test/dataset with spaces & special chars {create_uuid()[:5]}" + run_name = f"run/name with % and # {create_uuid()[:5]}" + + langfuse.create_dataset(name=dataset_name) + input_data = json.dumps({"input": "Test data"}) + langfuse.create_dataset_item(dataset_name=dataset_name, input=input_data) + + dataset = langfuse.get_dataset(dataset_name) + assert len(dataset.items) == 1 + + # Create a dataset run with special characters in the run name + for item in dataset.items: + with item.run( + run_name=run_name, + run_metadata={"test": "value"}, + run_description="Test run with special chars", + ): + pass + + langfuse.flush() + time.sleep(1) + + # Test get_dataset_runs with special characters in dataset name + runs = langfuse.get_dataset_runs(dataset_name=dataset_name) + assert len(runs.data) == 1 + assert runs.data[0].name == run_name + assert runs.data[0].metadata == {"test": "value"} + assert runs.data[0].description == "Test run with special chars" + + # Test get_dataset_run with special characters in both dataset and run name + run = langfuse.get_dataset_run(dataset_name=dataset_name, run_name=run_name) + assert run.run_name == run_name + assert run.dataset_name == dataset_name + assert len(run.dataset_run_items) == 1 + + # Test delete_dataset_run with special characters + delete_response = langfuse.delete_dataset_run( + dataset_name=dataset_name, run_name=run_name + ) + assert delete_response.deleted_run_items_count == 1 + + # Verify the run was deleted + runs_after_delete = langfuse.get_dataset_runs(dataset_name=dataset_name) + assert len(runs_after_delete.data) == 0 From 70398665df8c91268ea7e52d818d2195397e78f1 Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:26:54 +0100 Subject: [PATCH 3/8] refactor(client): remove unnecessary asterisks from method signatures in Langfuse class --- langfuse/_client/client.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 8656b4f36..7e8b22aba 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2466,9 +2466,7 @@ def get_dataset( handle_fern_exception(e) raise e - def get_dataset_run( - self, *, dataset_name: str, run_name: str - ) -> DatasetRunWithItems: + def get_dataset_run(self, dataset_name: str, run_name: str) -> DatasetRunWithItems: """Fetch a dataset run by dataset name and run name. Args: @@ -2490,7 +2488,6 @@ def get_dataset_run( def get_dataset_runs( self, - *, dataset_name: str, page: Optional[int] = None, limit: Optional[int] = None, @@ -2517,7 +2514,7 @@ def get_dataset_runs( raise e def delete_dataset_run( - self, *, dataset_name: str, run_name: str + self, dataset_name: str, run_name: str ) -> DeleteDatasetRunResponse: """Delete a dataset run and all its run items. This action is irreversible. From 6cc5d2cb1d7fd76835fd0977b5e863ea474d91c5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:43:19 +0200 Subject: [PATCH 4/8] make arguments keyword arguments only --- langfuse/_client/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 7e8b22aba..1a88cc8b8 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2466,7 +2466,9 @@ def get_dataset( handle_fern_exception(e) raise e - def get_dataset_run(self, dataset_name: str, run_name: str) -> DatasetRunWithItems: + def get_dataset_run( + self, *, dataset_name: str, run_name: str + ) -> DatasetRunWithItems: """Fetch a dataset run by dataset name and run name. Args: @@ -2488,6 +2490,7 @@ def get_dataset_run(self, dataset_name: str, run_name: str) -> DatasetRunWithIte def get_dataset_runs( self, + *, dataset_name: str, page: Optional[int] = None, limit: Optional[int] = None, From d4a1bd3fb5242a397a6639393bac93641aa673e5 Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:49:42 +0100 Subject: [PATCH 5/8] fix: keyword args only --- langfuse/_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 1a88cc8b8..8656b4f36 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2517,7 +2517,7 @@ def get_dataset_runs( raise e def delete_dataset_run( - self, dataset_name: str, run_name: str + self, *, dataset_name: str, run_name: str ) -> DeleteDatasetRunResponse: """Delete a dataset run and all its run items. This action is irreversible. From 3f3374cc4dc59a66d9ff180600ee15067ddec7d9 Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:52:40 +0100 Subject: [PATCH 6/8] chore: tests --- tests/test_datasets.py | 122 ++++++++++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 33 deletions(-) diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 5b999ad61..610d40c75 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -220,7 +220,7 @@ def test_get_dataset_runs(): langfuse.flush() time.sleep(1) # Give API time to process - runs = langfuse.api.datasets.get_runs(dataset_name) + runs = langfuse.get_dataset_runs(dataset_name=dataset_name) assert len(runs.data) == 2 assert runs.data[0].name == run_name_2 @@ -421,52 +421,108 @@ def execute_dataset_item(item, run_name): assert trace.output == expected_input -def test_dataset_runs_with_special_characters(): - """Test that dataset runs work correctly with special characters in names.""" +def test_get_dataset_with_folder_name(): + """Test that get_dataset works with folder-format names containing slashes.""" langfuse = Langfuse(debug=False) - # Test with various special characters that need URL encoding - dataset_name = f"test/dataset with spaces & special chars {create_uuid()[:5]}" - run_name = f"run/name with % and # {create_uuid()[:5]}" + # Create a dataset with slashes in the name (folder format) + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) - langfuse.create_dataset(name=dataset_name) - input_data = json.dumps({"input": "Test data"}) - langfuse.create_dataset_item(dataset_name=dataset_name, input=input_data) + # Fetch the dataset using the wrapper method + dataset = langfuse.get_dataset(folder_name) + assert dataset.name == folder_name + assert "/" in dataset.name # Verify slashes are preserved - dataset = langfuse.get_dataset(dataset_name) + +def test_get_dataset_runs_with_folder_name(): + """Test that get_dataset_runs works with folder-format dataset names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) assert len(dataset.items) == 1 - # Create a dataset run with special characters in the run name + # Create a run + run_name = f"run-{create_uuid()[:8]}" for item in dataset.items: - with item.run( - run_name=run_name, - run_metadata={"test": "value"}, - run_description="Test run with special chars", - ): + with item.run(run_name=run_name): pass langfuse.flush() - time.sleep(1) + time.sleep(1) # Give API time to process - # Test get_dataset_runs with special characters in dataset name - runs = langfuse.get_dataset_runs(dataset_name=dataset_name) + # Fetch runs using the new wrapper method + runs = langfuse.get_dataset_runs(dataset_name=folder_name) assert len(runs.data) == 1 assert runs.data[0].name == run_name - assert runs.data[0].metadata == {"test": "value"} - assert runs.data[0].description == "Test run with special chars" - # Test get_dataset_run with special characters in both dataset and run name - run = langfuse.get_dataset_run(dataset_name=dataset_name, run_name=run_name) + +def test_get_dataset_run_with_folder_names(): + """Test that get_dataset_run works with folder-format dataset and run names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) + assert len(dataset.items) == 1 + + # Create a run with slashes in the name + run_name = f"run/nested/{create_uuid()[:8]}" + for item in dataset.items: + with item.run(run_name=run_name, run_metadata={"key": "value"}): + pass + + langfuse.flush() + time.sleep(1) # Give API time to process + + # Fetch the specific run using the new wrapper method + run = langfuse.get_dataset_run(dataset_name=folder_name, run_name=run_name) assert run.run_name == run_name - assert run.dataset_name == dataset_name - assert len(run.dataset_run_items) == 1 + assert run.metadata == {"key": "value"} + assert "/" in folder_name # Verify slashes are preserved - # Test delete_dataset_run with special characters - delete_response = langfuse.delete_dataset_run( - dataset_name=dataset_name, run_name=run_name - ) - assert delete_response.deleted_run_items_count == 1 - # Verify the run was deleted - runs_after_delete = langfuse.get_dataset_runs(dataset_name=dataset_name) - assert len(runs_after_delete.data) == 0 +def test_delete_dataset_run_with_folder_names(): + """Test that delete_dataset_run works with folder-format dataset and run names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) + + # Create a run with slashes in the name + run_name = f"run/to/delete/{create_uuid()[:8]}" + for item in dataset.items: + with item.run(run_name=run_name): + pass + + langfuse.flush() + time.sleep(1) # Give API time to process + + # Verify the run exists + runs_before = langfuse.get_dataset_runs(dataset_name=folder_name) + assert len(runs_before.data) == 1 + + # Delete the run using the new wrapper method + result = langfuse.delete_dataset_run(dataset_name=folder_name, run_name=run_name) + assert result.deleted_run_items_count == 1 + + time.sleep(1) # Give API time to process deletion + + # Verify the run is deleted + runs_after = langfuse.get_dataset_runs(dataset_name=folder_name) + assert len(runs_after.data) == 0 From 8f85a7d2bf8925a4f231c7f678814807c355f17b Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:21:31 +0100 Subject: [PATCH 7/8] chore: simplify URL encoding in dataset methods by removing unnecessary is_url_param argument --- langfuse/_client/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 8656b4f36..ef154fce4 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2480,8 +2480,8 @@ def get_dataset_run( """ try: return self.api.datasets.get_run( - dataset_name=self._url_encode(dataset_name, is_url_param=True), - run_name=self._url_encode(run_name, is_url_param=True), + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), request_options=None, ) except Error as e: @@ -2507,7 +2507,7 @@ def get_dataset_runs( """ try: return self.api.datasets.get_runs( - dataset_name=self._url_encode(dataset_name, is_url_param=True), + dataset_name=self._url_encode(dataset_name), page=page, limit=limit, request_options=None, @@ -2530,8 +2530,8 @@ def delete_dataset_run( """ try: return self.api.datasets.delete_run( - dataset_name=self._url_encode(dataset_name, is_url_param=True), - run_name=self._url_encode(run_name, is_url_param=True), + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), request_options=None, ) except Error as e: From 617b4e7d387d68afd9ee3beb0139ed4d79acf06d Mon Sep 17 00:00:00 2001 From: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:16:06 +0100 Subject: [PATCH 8/8] fix: tests --- tests/test_datasets.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 610d40c75..051dcfbf6 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -487,9 +487,10 @@ def test_get_dataset_run_with_folder_names(): # Fetch the specific run using the new wrapper method run = langfuse.get_dataset_run(dataset_name=folder_name, run_name=run_name) - assert run.run_name == run_name + assert run.name == run_name + assert run.dataset_name == folder_name assert run.metadata == {"key": "value"} - assert "/" in folder_name # Verify slashes are preserved + assert "/" in run_name # Verify slashes are preserved in run name def test_delete_dataset_run_with_folder_names(): @@ -519,7 +520,7 @@ def test_delete_dataset_run_with_folder_names(): # Delete the run using the new wrapper method result = langfuse.delete_dataset_run(dataset_name=folder_name, run_name=run_name) - assert result.deleted_run_items_count == 1 + assert result.message is not None time.sleep(1) # Give API time to process deletion