Repository: watts-dev/watts

Issue:
Add clarification of input file deletion from Database
This is for my review for https://github.com/openjournals/joss-reviews/issues/4735  .

In the documentation for the detailed usage of WATTS on the Database class, it is mentioned that if one uses the `Database.clear()` method that
> Be aware that clearing the database will delete all the corresponding results on disk, including input and output files from the workflow.

I am curious if this includes deleting the input files where they were specified on disc, or just the paths to those locations in the database? Does this also include the `extra_inputs` and `extra_template_inputs` files if they are specified? 

I also think it would be useful to show how to clear out single objects from the database, rather than the whole database. I can imagine a use case where a user might want to delete a single object that was an error and not all of the entries in the database. 

Discussion:

Patch:
diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst
index 6ccce5b..98b949c 100755
--- a/doc/source/user/usage.rst
+++ b/doc/source/user/usage.rst
@@ -326,7 +326,23 @@ plugins. If you want to change the default database path used in plugins, the
     >>> db.path
     PosixPath('/opt/watts_db')
 
-To clear results from the database, simply use the
+To remove a result from the databse, you can call the
+:meth:`~watts.Database.remove` method, passing a :class:`watts.Results` object::
+
+    >>> db = watts.Database()
+    >>> db
+    [<ResultsOpenMC: 2022-01-01 12:05:02.130384>,
+     <ResultsOpenMC: 2022-01-01 12:11:38.037813>,
+     <ResultsMOOSE: 2022-01-02 08:45:12.846409>]
+    >>> moose_result = db[-1]
+    >>> db.remove(moose_result)
+    >>> db
+    [<ResultsOpenMC: 2022-01-01 12:05:02.130384>,
+     <ResultsOpenMC: 2022-01-01 12:11:38.037813>]
+
+Note that removing a database result will delete the data directory associated
+with the result but will not affect the input files stored in their original
+location on your system. To clear all results from the database, simply use the
 :meth:`~watts.Database.clear` method:
 
 .. code-block::
@@ -335,5 +351,7 @@ To clear results from the database, simply use the
     >>> db
     []
 
-Be aware that clearing the database **will** delete all the corresponding
-results on disk, including input and output files from the workflow.
+As with the :meth:`~watts.Database.remove` method, clearing the database will
+delete all the corresponding results on disk, including copies of the input and
+output files from the workflow stored in the data directory. Original input
+files stored outside the database directory will be unaffected.
diff --git a/src/watts/database.py b/src/watts/database.py
index 73407bc..05b7ee1 100644
--- a/src/watts/database.py
+++ b/src/watts/database.py
@@ -5,7 +5,7 @@ from collections.abc import Sequence
 from pathlib import Path
 import pprint
 import shutil
-from typing import List, Union
+from typing import Union
 from warnings import warn
 
 import platformdirs
@@ -136,6 +136,18 @@ class Database(Sequence):
             shutil.rmtree(dir)
         self._results.clear()
 
+    def remove(self, result: Results):
+        """Remove a single result from the database
+
+        Parameters
+        ----------
+        result
+            Result to remove from the database
+
+        """
+        self._results.remove(result)
+        shutil.rmtree(result.base_path)
+
     def show_summary(self):
         """Show a summary of results in database"""
         for result in self._results:


Test patch:
diff --git a/tests/test_database.py b/tests/test_database.py
index 9bd988f..ca4befa 100644
--- a/tests/test_database.py
+++ b/tests/test_database.py
@@ -3,6 +3,7 @@
 
 from datetime import datetime
 from pathlib import Path
+from uuid import uuid4
 
 import pytest
 import watts
@@ -16,7 +17,7 @@ def restore_database_path():
 
 
 def get_result():
-    return watts.ResultsOpenMC(
+    res = watts.ResultsOpenMC(
         params=watts.Parameters(value=1, lab='Argonne'),
         name='Workflow',
         time=datetime.now(),
@@ -24,6 +25,12 @@ def get_result():
         outputs=['statepoint.50.h5'],
     )
 
+    # Set base path of result
+    db = watts.Database()
+    res.base_path = db.path / uuid4().hex
+    res.base_path.mkdir()
+    return res
+
 
 def test_change_default_dir(run_in_tmpdir):
     watts.Database.set_default_path('new_database')
@@ -56,8 +63,10 @@ def test_specify_path(run_in_tmpdir):
 def test_add_results(run_in_tmpdir):
     db = watts.Database('tmp_db')
 
-    db.add_result(get_result())
-    db.add_result(get_result())
+    res1 = get_result()
+    res2 = get_result()
+    db.add_result(res1)
+    db.add_result(res2)
 
     # Basic sanity checks
     assert len(db) == 2
@@ -68,6 +77,10 @@ def test_add_results(run_in_tmpdir):
         assert len(result.inputs) == 1
         assert len(result.outputs) == 1
 
+    # Remove method
+    db.remove(res1)
+    assert db[0] is res2
+
     # Ensure database can be cleared
     db.clear()
     assert len(db) == 0