Repository: probabl-ai/skore

Issue:
Force the creation of project
Currently, one needs to remove manually the `.skore` folder when creating a new project.
It would be handy to have a `--force` option or `force=True/False` option to just overwrite the directory if desired.

Of course, it should not be the default but sometimes, you just want to go fast.

Discussion:

Patch:
diff --git a/skore/src/skore/cli/cli.py b/skore/src/skore/cli/cli.py
index 013f722..355e2d7 100644
--- a/skore/src/skore/cli/cli.py
+++ b/skore/src/skore/cli/cli.py
@@ -56,6 +56,11 @@ def cli(args: list[str]):
         ),
         default=None,
     )
+    parser_create.add_argument(
+        "--overwrite",
+        action="store_true",
+        help="overwrite an existing project with the same name",
+    )
 
     subparsers.add_parser(
         "quickstart", help='Create a "project.skore" file and start the UI'
@@ -73,6 +78,7 @@ def cli(args: list[str]):
         __create(
             project_name=parsed_args.project_name,
             working_dir=parsed_args.working_dir,
+            overwrite=parsed_args.overwrite,
         )
     elif parsed_args.subcommand == "quickstart":
         __quickstart()
diff --git a/skore/src/skore/cli/create_project.py b/skore/src/skore/cli/create_project.py
index e11b929..551b00b 100644
--- a/skore/src/skore/cli/create_project.py
+++ b/skore/src/skore/cli/create_project.py
@@ -1,6 +1,7 @@
 """Implement the "create project" feature."""
 
 import re
+import shutil
 from pathlib import Path
 from typing import Optional, Union
 
@@ -57,7 +58,9 @@ def validate_project_name(project_name: str) -> tuple[bool, Optional[Exception]]
 
 
 def __create(
-    project_name: Union[str, Path], working_dir: Optional[Path] = None
+    project_name: Union[str, Path],
+    working_dir: Optional[Path] = None,
+    overwrite: bool = False,
 ) -> Path:
     """Create a project file named according to `project_name`.
 
@@ -70,6 +73,9 @@ def __create(
         `working_dir`. If `project_name` is an absolute path, `working_dir` will have
         no effect. If set to None (the default), `working_dir` will be re-set to the
         current working directory.
+    overwrite : bool
+        If True, overwrite an existing project with the same name. If False, raise an
+        error if a project with the same name already exists.
 
     Returns
     -------
@@ -97,14 +103,17 @@ def __create(
         project_path.with_name(checked_project_name + ".skore")
     )
 
+    if project_directory.exists():
+        if not overwrite:
+            raise ProjectAlreadyExistsError(
+                f"Unable to create project file '{project_directory}' because a file "
+                "with that name already exists. Please choose a different name or "
+                "use the --overwrite flag with the CLI or overwrite=True with the API."
+            )
+        shutil.rmtree(project_directory)
+
     try:
-        project_directory.mkdir()
-    except FileExistsError as e:
-        raise ProjectAlreadyExistsError(
-            f"Unable to create project file '{project_directory}' because a file "
-            "with that name already exists. Please choose a different name or delete "
-            "the existing file."
-        ) from e
+        project_directory.mkdir(parents=True)
     except PermissionError as e:
         raise ProjectPermissionError(
             f"Unable to create project file '{project_directory}'. "


Test patch:
diff --git a/skore/tests/integration/cli/test_create.py b/skore/tests/integration/cli/test_create.py
index dfd63ed..5a40b25 100644
--- a/skore/tests/integration/cli/test_create.py
+++ b/skore/tests/integration/cli/test_create.py
@@ -89,3 +89,30 @@ def test_create_project_cli_invalid_name(tmp_path):
     with pytest.raises(subprocess.CalledProcessError):
         completed_process.check_returncode()
     assert b"InvalidProjectNameError" in completed_process.stderr
+
+
+def test_create_project_cli_overwrite(tmp_path):
+    """Check the behaviour of the `overwrite` flag/parameter."""
+    completed_process = subprocess.run(
+        f"python -m skore create --working-dir {tmp_path}".split(),
+        capture_output=True,
+    )
+    completed_process.check_returncode()
+    assert (tmp_path / "project.skore").exists()
+
+    # calling the same command without overwriting should fail
+    completed_process = subprocess.run(
+        f"python -m skore create --working-dir {tmp_path}".split(),
+        capture_output=True,
+    )
+    with pytest.raises(subprocess.CalledProcessError):
+        completed_process.check_returncode()
+    assert b"ProjectAlreadyExistsError" in completed_process.stderr
+
+    # calling the same command with overwriting should succeed
+    completed_process = subprocess.run(
+        f"python -m skore create --working-dir {tmp_path} --overwrite".split(),
+        capture_output=True,
+    )
+    completed_process.check_returncode()
+    assert (tmp_path / "project.skore").exists()