diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 39679c79b..7ee2f962f 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.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.ingestion.types.score_body import ScoreBody from langfuse.api.resources.prompts.types import ( @@ -2461,6 +2466,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, *, diff --git a/tests/test_datasets.py b/tests/test_datasets.py index c1b81868d..051dcfbf6 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 @@ -419,3 +419,111 @@ 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_get_dataset_with_folder_name(): + """Test that get_dataset works with folder-format names containing slashes.""" + langfuse = Langfuse(debug=False) + + # 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) + + # 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 + + +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 run + run_name = f"run-{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 + + # 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 + + +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.name == run_name + assert run.dataset_name == folder_name + assert run.metadata == {"key": "value"} + assert "/" in run_name # Verify slashes are preserved in run name + + +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.message is not None + + 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