Repository: fractal-analytics-platform/fractal-client (language: python)

Issue:
Add `delete` commands
Requires https://github.com/fractal-analytics-platform/fractal-server/issues/386

- [x] project - tested
- [x] dataset - tested
- [x] resource - tested
- [x] workflow - tested
- [x] workflow task - tested
*  ~~task (missing)~~ postponed


Discussion / hints:

Fix patch:
diff --git a/fractal/cmd/__init__.py b/fractal/cmd/__init__.py
index eef6c8e0d..e5fe8904f 100644
--- a/fractal/cmd/__init__.py
+++ b/fractal/cmd/__init__.py
@@ -8,6 +8,7 @@
 from ..interface import RichJsonInterface
 from ..response import check_response
 from ._dataset import dataset_add_resource
+from ._dataset import dataset_delete
 from ._dataset import dataset_delete_resource
 from ._dataset import dataset_edit
 from ._dataset import dataset_show
@@ -16,10 +17,12 @@
 from ._job import job_status
 from ._project import project_add_dataset
 from ._project import project_create
+from ._project import project_delete
 from ._project import project_list
 from ._project import project_show
 from ._task import task_collect_pip
 from ._task import task_collection_check
+from ._task import task_delete
 from ._task import task_edit
 from ._task import task_list
 from ._workflow import workflow_add_task
@@ -55,6 +58,8 @@ async def project(
             metadata_filename=kwargs.pop("metadata"),
             **kwargs,
         )
+    elif subcmd == "delete":
+        iface = await project_delete(client, **kwargs)
     else:
         raise NoCommandError(f"Command project {subcmd} not found")
 
@@ -96,6 +101,8 @@ async def dataset(
             dataset_id=dataset_id,
             dataset_update_dict=dataset_update_dict,
         )
+    elif subcmd == "delete":
+        iface = await dataset_delete(client, **kwargs)
     else:
         raise NoCommandError(f"Command dataset {subcmd} not found")
     return iface
@@ -148,6 +155,8 @@ async def task(
         iface = await task_collection_check(client, **kwargs)
     elif subcmd == "edit":
         iface = await task_edit(client, **kwargs)
+    elif subcmd == "delete":
+        iface = await task_delete(client, **kwargs)
     else:
         raise NoCommandError(f"Command task {subcmd} not found")
     return iface
diff --git a/fractal/cmd/_dataset.py b/fractal/cmd/_dataset.py
index 3f9eca582..7e6bd4934 100644
--- a/fractal/cmd/_dataset.py
+++ b/fractal/cmd/_dataset.py
@@ -134,3 +134,14 @@ async def dataset_show(
             )
         group = Group(table, table_res)
         return RichConsoleInterface(retcode=0, data=group)
+
+
+async def dataset_delete(
+    client: AuthClient, project_id: int, dataset_id: int, **kwargs
+) -> PrintInterface:
+
+    res = await client.delete(
+        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}"
+    )
+    check_response(res, expected_status_code=204)
+    return PrintInterface(retcode=0, data="")
diff --git a/fractal/cmd/_project.py b/fractal/cmd/_project.py
index 4cc0ed130..6ab242c3d 100644
--- a/fractal/cmd/_project.py
+++ b/fractal/cmd/_project.py
@@ -121,3 +121,14 @@ async def project_add_dataset(
         return PrintInterface(retcode=0, data=new_dataset.id)
     else:
         return RichJsonInterface(retcode=0, data=new_dataset.dict())
+
+
+async def project_delete(
+    client: AuthClient, project_id: int, **kwargs
+) -> PrintInterface:
+
+    res = await client.delete(
+        f"{settings.BASE_URL}/project/{project_id}",
+    )
+    check_response(res, expected_status_code=204)
+    return PrintInterface(retcode=0, data="")
diff --git a/fractal/cmd/_task.py b/fractal/cmd/_task.py
index d031d2144..6c6621f9f 100644
--- a/fractal/cmd/_task.py
+++ b/fractal/cmd/_task.py
@@ -85,3 +85,16 @@ async def task_edit(
     )
     new_task = check_response(res, expected_status_code=200, coerce=TaskRead)
     return RichJsonInterface(retcode=0, data=new_task.dict())
+
+
+async def task_delete(
+    client: AuthClient, task_id: int, **kwargs
+) -> PrintInterface:
+
+    raise NotImplementedError("task_delete")
+
+    # res = await client.delete(
+    #    f"{settings.BASE_URL}/xxxx"
+    # )
+    # check_response(res, expected_status_code=204)
+    # return PrintInterface(retcode=0, data="")
diff --git a/fractal/cmd/_workflow.py b/fractal/cmd/_workflow.py
index 5dd977e22..2f0ddce47 100644
--- a/fractal/cmd/_workflow.py
+++ b/fractal/cmd/_workflow.py
@@ -91,10 +91,10 @@ async def workflow_list(
 async def workflow_delete(
     client: AuthClient,
     *,
-    id: int,
+    workflow_id: int,
     **kwargs,
 ) -> BaseInterface:
-    res = await client.delete(f"{settings.BASE_URL}/workflow/{id}")
+    res = await client.delete(f"{settings.BASE_URL}/workflow/{workflow_id}")
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
 
@@ -102,10 +102,10 @@ async def workflow_delete(
 async def workflow_show(
     client: AuthClient,
     *,
-    id: int,
+    workflow_id: int,
     **kwargs,
 ) -> RichJsonInterface:
-    res = await client.get(f"{settings.BASE_URL}/workflow/{id}")
+    res = await client.get(f"{settings.BASE_URL}/workflow/{workflow_id}")
     workflow = check_response(res, expected_status_code=200)
     return RichJsonInterface(retcode=0, data=workflow)
 
@@ -113,7 +113,7 @@ async def workflow_show(
 async def workflow_add_task(
     client: AuthClient,
     *,
-    id: int,
+    workflow_id: int,
     batch: bool = False,
     task_id_or_name: str,
     order: int = None,
@@ -138,7 +138,7 @@ async def workflow_add_task(
             workflow_task.meta = meta
 
     res = await client.post(
-        f"{settings.BASE_URL}/workflow/{id}/add-task/",
+        f"{settings.BASE_URL}/workflow/{workflow_id}/add-task/",
         json=workflow_task.dict(),
     )
     workflow = check_response(
@@ -156,7 +156,7 @@ async def workflow_add_task(
 async def workflow_edit_task(
     client: AuthClient,
     *,
-    id: int,
+    workflow_id: int,
     workflow_task_id: int,
     args_file: Optional[str] = None,
     meta_file: Optional[str] = None,
@@ -168,7 +168,7 @@ async def workflow_edit_task(
     # method within argparse).
     if not (args_file or meta_file):
         raise ValueError(
-            "At least one of {args_file,meta_file} arguments is " "required"
+            "At least one of {args_file,meta_file} arguments is required"
         )
 
     # Combine args/meta payload
@@ -184,7 +184,8 @@ async def workflow_edit_task(
 
     payload_update = WorkflowTaskUpdate(**payload)
     res = await client.patch(
-        f"{settings.BASE_URL}/workflow/{id}/edit-task/{workflow_task_id}",
+        f"{settings.BASE_URL}/"
+        f"workflow/{workflow_id}/edit-task/{workflow_task_id}",
         json=payload_update.dict(exclude_unset=True),
     )
     workflow_task = check_response(
@@ -197,13 +198,14 @@ async def workflow_edit_task(
 async def workflow_remove_task(
     client: AuthClient,
     *,
-    id: int,
+    workflow_id: int,
     workflow_task_id: int,
     **kwargs,
 ) -> BaseInterface:
 
     res = await client.delete(
-        f"{settings.BASE_URL}/workflow/{id}/rm-task/{workflow_task_id}"
+        f"{settings.BASE_URL}/"
+        f"workflow/{workflow_id}/rm-task/{workflow_task_id}"
     )
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
@@ -212,7 +214,7 @@ async def workflow_remove_task(
 async def workflow_edit(
     client: AuthClient,
     *,
-    id: str,
+    workflow_id: int,
     **workflow_update_dict,
 ) -> BaseInterface:
     workflow_update = WorkflowUpdate(**workflow_update_dict)
@@ -221,7 +223,7 @@ async def workflow_edit(
         return PrintInterface(retcode=1, data="Nothing to update")
 
     res = await client.patch(
-        f"{settings.BASE_URL}/workflow/{id}", json=payload
+        f"{settings.BASE_URL}/workflow/{workflow_id}", json=payload
     )
     new_workflow = check_response(
         res, expected_status_code=200, coerce=WorkflowRead
diff --git a/fractal/parser.py b/fractal/parser.py
index 7e53538ae..566b6e76b 100644
--- a/fractal/parser.py
+++ b/fractal/parser.py
@@ -52,6 +52,7 @@
 
 subparsers_main = parser_main.add_subparsers(title="Commands:", dest="cmd")
 
+
 # REGISTER GROUP
 register_parser = subparsers_main.add_parser(
     "register", help="Register with the Fractal server"
@@ -65,6 +66,8 @@
     "--password",
     help=("Password for the new user"),
 )
+
+
 # PROJECT GROUP
 project_parser = subparsers_main.add_parser("project", help="project commands")
 project_subparsers = project_parser.add_subparsers(
@@ -105,11 +108,13 @@
     "project_id", type=int, help="Id of project to show"
 )
 
-
 # project delete
 project_delete_parser = project_subparsers.add_parser(
     "delete", help="Delete project"
 )
+project_delete_parser.add_argument(
+    "project_id", type=int, help="Id of project to delete"
+)
 
 # project add-dataset
 project_add_dataset_parser = project_subparsers.add_parser(
@@ -148,7 +153,7 @@
     "-g", "--glob-pattern", help="Glob pattern"
 )
 
-# dataset add-resource
+# dataset rm-resource
 dataset_rm_resource_parser = dataset_subparsers.add_parser(
     "rm-resource", help="Remove resource to existing dataset"
 )
@@ -198,6 +203,13 @@
 dataset_show_parser.add_argument("project_id", type=int, help="Project id")
 dataset_show_parser.add_argument("dataset_id", type=int, help="Dataset id")
 
+# dataset delete
+dataset_delete_parser = dataset_subparsers.add_parser(
+    "delete", help="Delete dataset", argument_default=ap.SUPPRESS
+)
+dataset_delete_parser.add_argument("project_id", type=int, help="Project id")
+dataset_delete_parser.add_argument("dataset_id", type=int, help="Dataset id")
+
 
 # TASK GROUP
 task_parser = subparsers_main.add_parser("task", help="task commands")
@@ -257,7 +269,6 @@
     help="Also include task-collection logs",
 )
 
-
 # task edit
 task_edit_parser = task_subparsers.add_parser(
     "edit", help="Edit task", argument_default=ap.SUPPRESS
@@ -266,11 +277,6 @@
     "task_id_or_name", help="ID or name of task to edit", type=str
 )
 task_edit_parser.add_argument("--name", help="New task name")
-task_edit_parser.add_argument(
-    "--resource-type",
-    choices=["task", "workflow"],
-    help="New resource type",
-)
 task_edit_parser.add_argument(
     "--input-type",
     help="New input type",
@@ -283,6 +289,15 @@
     "--default-args", help="Filename containing JSON encoded default arguments"
 )
 
+# task delete
+task_delete_parser = task_subparsers.add_parser(
+    "delete", help="Delete task", argument_default=ap.SUPPRESS
+)
+task_delete_parser.add_argument(
+    "task_id_or_name", help="ID or name of task to delete", type=str
+)
+
+
 # WORKFLOW GROUP
 
 # workflow new
@@ -314,11 +329,11 @@
 )
 
 # workflow delete
-workflow_new_parser = workflow_subparsers.add_parser(
+workflow_delete_parser = workflow_subparsers.add_parser(
     "delete", help="Delete workflow"
 )
-workflow_new_parser.add_argument(
-    "id",
+workflow_delete_parser.add_argument(
+    "workflow_id",
     help="Workflow id",
 )
 
@@ -327,7 +342,7 @@
     "show", help="Show a workflow"
 )
 workflow_new_parser.add_argument(
-    "id",
+    "workflow_id",
     help="Workflow id",
 )
 
@@ -336,7 +351,7 @@
     "add-task", help="Add a new task to a specific workflow"
 )
 workflow_add_task_parser.add_argument(
-    "id",
+    "workflow_id",
     help="Workflow id",
 )
 workflow_add_task_parser.add_argument(
@@ -360,13 +375,12 @@
     ),
 )
 
-
 # workflow edit task
 workflow_edit_task_parser = workflow_subparsers.add_parser(
     "edit-task", help="Edit a task within a specific workflow"
 )
 workflow_edit_task_parser.add_argument(
-    "id",
+    "workflow_id",
     help="Workflow id",
 )
 workflow_edit_task_parser.add_argument(
@@ -388,13 +402,12 @@
     ),
 )
 
-
 # workflow remove task
 workflow_remove_task_parser = workflow_subparsers.add_parser(
     "rm-task", help="Remove a task in a specific workflow"
 )
 workflow_remove_task_parser.add_argument(
-    "id",
+    "workflow_id",
     help="Workflow id",
 )
 workflow_remove_task_parser.add_argument(
@@ -407,7 +420,7 @@
     "edit", help="Edit workflow", argument_default=ap.SUPPRESS
 )
 workflow_edit_parser.add_argument(
-    "id",
+    "workflow_id",
     help="Workflow id",
 )
 workflow_edit_parser.add_argument("--name", help="New workflow name")
diff --git a/poetry.lock b/poetry.lock
index 69feee21a..766304f8b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -597,14 +597,14 @@ sqlmodel = "*"
 
 [[package]]
 name = "fractal-server"
-version = "1.0.0b10"
+version = "1.0.0b12"
 description = "Server component of the Fractal analytics platform"
 category = "dev"
 optional = false
 python-versions = ">=3.8,<4.0"
 files = [
-    {file = "fractal_server-1.0.0b10-py3-none-any.whl", hash = "sha256:3d41a9805f1da81f90a6b57b09e8f07184e0bb5cf075d3cc48f58d84808671fa"},
-    {file = "fractal_server-1.0.0b10.tar.gz", hash = "sha256:afcd64753f4513cbe3804026485c23aaa8c1f1d38cfe139c7f936edb2ed6465f"},
+    {file = "fractal_server-1.0.0b12-py3-none-any.whl", hash = "sha256:103f4e9c9b8a1f7152574f90fcf454746ffd138f9c628428973bd9f0ff4acd36"},
+    {file = "fractal_server-1.0.0b12.tar.gz", hash = "sha256:20400accb76432e523c7db5760a701afb0931bc89d483f73a919f760ddd6da70"},
 ]
 
 [package.dependencies]
@@ -1035,52 +1035,52 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "1.10.3"
+version = "1.10.4"
 description = "Data validation and settings management using python type hints"
 category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pydantic-1.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef012251ca6bc899f0b5a9239c527dec30915462f35c3a58aab6ddff09708484"},
-    {file = "pydantic-1.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:810e1510da10bc6c59300221ba55e17cfb7b62279fe87c6466f9e293975a6dd5"},
-    {file = "pydantic-1.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a83e92f7311c703fb1d64dba81d1e374786465a4e260b0616f731401e1f875a8"},
-    {file = "pydantic-1.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4efd2e1f63cdd0877e24ca5d5edd7dc65a24b915da04e67b1fe485a0f7f3508"},
-    {file = "pydantic-1.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d15d22b5d666b7b610d9a99a73de6b27be8982ee3af4de0cb32586ee72557f25"},
-    {file = "pydantic-1.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d29e5353593117937901a6329c7295955eacbf4856a0c01da5319f416ad18967"},
-    {file = "pydantic-1.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:106302e18978c22c51b013d347db6ef5589776137663b16dc77ea3162bc82711"},
-    {file = "pydantic-1.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85e48eb95c39a5fd4500d148e8330d878fafe9e3eca6c253bdf0bd0af2b71371"},
-    {file = "pydantic-1.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9862e881689e1c067c11b307513465e6509647f9f10e3c48d100120351449087"},
-    {file = "pydantic-1.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21cca3b77f9a7a69f51991e26780427396226a5a576c72635e25b8439dd0170"},
-    {file = "pydantic-1.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df2fa7102d1bec588d360833777d47772ce7f6aecc0335be43de710a8d034f42"},
-    {file = "pydantic-1.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7eb71ad9fb1f6911ca2b7010039a9d61a3f14bd4f60a1c57e9250f8d87375fb"},
-    {file = "pydantic-1.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:762a6560e6d31d0a5558ee95cdece616dd6d92c94e3a9a41fb46f2d733a66f49"},
-    {file = "pydantic-1.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:38dc429316685a3b13ca008c907e2cc9e164068e49612b5284d7cbb01504c3a3"},
-    {file = "pydantic-1.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:000a7d934e182f6e368340382338dea5423b0503a3a5cafd3f2b75e684fe67f2"},
-    {file = "pydantic-1.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec151222823911a72aeb3c855947fe10b31e718484b233eb7d2d98a5df3d3c7b"},
-    {file = "pydantic-1.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a63805997d1ab9082c2c89017d3368369689660a35a7d8a8fccb67f77d5cf4d8"},
-    {file = "pydantic-1.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5fa3374562b3d4ea45bdff711c01854e5ce6c9ca9e2f37a6e94313412249000e"},
-    {file = "pydantic-1.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf121ec413f943803e9401fae0e58898bf9e68a97bdd9eea4d055499936a2e75"},
-    {file = "pydantic-1.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:e580ec5dcb7ff6861ef2de3b7c8a9af4112691bebd392221ed70de57fe846ae9"},
-    {file = "pydantic-1.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:495ad4077575e2629f775a7635c4f383d279b3de2439880b76ce27758db98902"},
-    {file = "pydantic-1.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cf20586026691c7aa59f0372488a75699194ab6ff7142577d585272b45e12ba"},
-    {file = "pydantic-1.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f918f2dcd740ac3b2603a1a8bf091a151385a31d02fac5903a2bbf2336d2025"},
-    {file = "pydantic-1.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddcec93519cb0ea63d0d7e8462042b46191020f862b88302815586707f4e8acc"},
-    {file = "pydantic-1.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3701a2971a8d0c7274b28a2d3fa9146390a51e7ffda4bc2406d10fda64d6f727"},
-    {file = "pydantic-1.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e8b895bf2faf61dba6ab9990e01e99d0b96e3d6a23e810d85060a51fbc27e1dc"},
-    {file = "pydantic-1.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:85bbea6c5b9bbd07532a875ab09a6d4987d89eb5566c6b8d2cc60f2dcdec5300"},
-    {file = "pydantic-1.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0627a759a14dc47cdca10e3590c86df368d96b46b23db44c986286656007d253"},
-    {file = "pydantic-1.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9197d3f22eefa113bcb0564cfc5812ccf889aaff08a6459c9ed04796b8f88c14"},
-    {file = "pydantic-1.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aef6dc6b7c6ae2dea27eea5fde6b5c766397a6c765c19414713dbc832a245d"},
-    {file = "pydantic-1.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac2a986c200c1739ce1a358d545f133d3a50b896c88c0c02a35c661120d85692"},
-    {file = "pydantic-1.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9ee33436a271e46ed3be422cb23f8c75c2dce53abf7259b7dd173ed1b7cabb66"},
-    {file = "pydantic-1.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e7c2d9ffca1124e751db9ae529968dbbcefd0b8db69ce958bb7cf777716e7b8"},
-    {file = "pydantic-1.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:6804f70ebf7e1d37bf8e0d0baf2bee20e3b07229b600db4c46eddd5f3738308a"},
-    {file = "pydantic-1.10.3-py3-none-any.whl", hash = "sha256:c50085e5ebd9da2e7d67353969185f6a6c190ed4142f93a46aa294c8213c466a"},
-    {file = "pydantic-1.10.3.tar.gz", hash = "sha256:01d450f1b6a642c98f58630e807f7554df0a8ce669ffaff087ce9e1fd4ff7ec8"},
+    {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"},
+    {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"},
+    {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"},
+    {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"},
+    {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"},
+    {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"},
+    {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"},
+    {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"},
+    {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"},
+    {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"},
+    {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"},
+    {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"},
+    {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"},
+    {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"},
+    {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"},
+    {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"},
+    {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"},
+    {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"},
+    {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"},
+    {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"},
+    {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"},
+    {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"},
+    {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"},
+    {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"},
+    {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"},
+    {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"},
+    {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"},
+    {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"},
+    {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"},
+    {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"},
+    {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"},
+    {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"},
+    {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"},
+    {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"},
+    {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"},
+    {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"},
 ]
 
 [package.dependencies]
-typing-extensions = ">=4.1.0"
+typing-extensions = ">=4.2.0"
 
 [package.extras]
 dotenv = ["python-dotenv (>=0.10.4)"]
@@ -1727,4 +1727,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.8"
-content-hash = "c1fb17261f4ceac618f8d1d3af87e39f863de394e9cfb183ef00056b62d778ea"
+content-hash = "37e587f6a704851f8e40f521eedd02eb9daeaf923a716dad2f058de88d3cf3df"
diff --git a/pyproject.toml b/pyproject.toml
index 28a05ca8a..ef0f1aa9a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ pytest = "^7.1.2"
 bumpver = "^2022.1118"
 pytest-asyncio = "^0.19.0"
 # fractal-server = "^1.0.0"
-fractal-server = ">=1.0.0b10"
+fractal-server = ">=1.0.0b12"
 coverage = {extras = ["toml"], version = "^6.5.0"}
 
 [tool.poetry.group.doc.dependencies]


Test patch:
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 58ffbd085..9129b1c05 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -22,7 +22,7 @@ async def test_auth_fail(client):
         await auth()
 
 
-async def test_auth_registerd(client, register_user):
+async def test_auth_registered(client, register_user):
     """
     GIVEN no user registered
     WHEN when fetching a token
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index fe2a0465d..8be189fe5 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -2,12 +2,15 @@
 
 
 async def test_add_resource(register_user, invoke):
+
+    # Create a project with its default dataset
     res = await invoke("project new prj0 prj_path0")
     project_id = res.data["id"]
     dataset_id = res.data["dataset_list"][0]["id"]
     assert res.data["dataset_list"][0]["resource_list"] == []
 
-    PATH = "/new/ds/path"
+    # Add a resource
+    PATH = "/some/path"
     res = await invoke(
         f"dataset add-resource {project_id} {dataset_id} {PATH}"
     )
@@ -52,7 +55,22 @@ async def test_edit_dataset(register_user, invoke):
     # provided are indeed not updated
 
 
+async def test_delete_dataset(register_user, invoke):
+    # Create a project with its default dataset
+    res = await invoke("project new prj0 prj_path0")
+    project_id = res.data["id"]
+    dataset_id = res.data["dataset_list"][0]["id"]
+
+    # Delete dataset
+    res = await invoke(f"dataset delete {project_id} {dataset_id}")
+
+    # Check that dataset show fails
+    with pytest.raises(SystemExit):
+        res = await invoke(f"dataset show {project_id} {dataset_id}")
+
+
 async def test_show_dataset(register_user, invoke):
+    # Create a project with its default dataset
     res = await invoke("project new prj0 prj_path0")
     project_id = res.data["id"]
     dataset_id = res.data["dataset_list"][0]["id"]
@@ -63,19 +81,39 @@ async def test_show_dataset(register_user, invoke):
 
 
 async def test_delete_resource(register_user, invoke):
+
+    # Create a project with its default dataset
     res = await invoke("project new prj0 prj_path0")
     project_id = res.data["id"]
     dataset_id = res.data["dataset_list"][0]["id"]
     assert res.data["dataset_list"][0]["resource_list"] == []
 
-    PATH = "/new/ds/path"
+    # Add a resource
+    PATH = "/some/path"
     res = await invoke(
-        f"--batch dataset add-resource {project_id} {dataset_id} {PATH}"
+        f"dataset add-resource {project_id} {dataset_id} {PATH}"
     )
-    resource_id = res.data
+    res.show()
+    assert res.retcode == 0
+    assert res.data["path"] == PATH
+    assert res.data["dataset_id"] == dataset_id
+    resource_id = res.data["id"]
 
+    # Remove a resource
     res = await invoke(
         f"dataset rm-resource {project_id} {dataset_id} {resource_id}"
     )
+    assert res.retcode == 0
+
+    # Check that the resource was removed
+    res = await invoke(f"dataset show {project_id} {dataset_id}")
+    res.show()
+
+    # Add a new resource, and check that it has the same id as the one that was
+    # removed
+    res = await invoke(
+        f"dataset add-resource {project_id} {dataset_id} {PATH}"
+    )
     res.show()
     assert res.retcode == 0
+    assert resource_id == res.data["id"]
diff --git a/tests/test_project.py b/tests/test_project.py
index 24269543b..813b4171e 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -1,3 +1,4 @@
+import pytest
 from devtools import debug
 
 
@@ -10,6 +11,26 @@ async def test_project_create(register_user, invoke):
     assert res.data["project_dir"] == PROJECT_PATH
 
 
+async def test_project_delete(register_user, invoke):
+
+    # Create project
+    res = await invoke("project new MyProj1 /some/path/1")
+    res.show()
+    project_id_1 = res.data["id"]
+
+    # Show project
+    res = await invoke(f"project show {project_id_1}")
+    res.show()
+
+    # Delete project
+    res = await invoke(f"project delete {project_id_1}")
+    assert res.retcode == 0
+
+    # Try to show deleted project, and fail
+    with pytest.raises(SystemExit):
+        res = await invoke(f"project show {project_id_1}")
+
+
 async def test_project_create_batch(register_user, invoke):
     res = await invoke("--batch project new project_name project_path")
     debug(res)
diff --git a/tests/test_workflow.py b/tests/test_workflow.py
index 0a56e644c..8e625e050 100644
--- a/tests/test_workflow.py
+++ b/tests/test_workflow.py
@@ -25,6 +25,34 @@ async def test_workflow_new(register_user, invoke):
     assert res_wf.data["project_id"] == res_pj.data["id"]
 
 
+async def test_workflow_delete(register_user, invoke):
+    # Create project
+    res_pj = await invoke("project new project_name /some/path")
+    assert res_pj.retcode == 0
+    project_id = res_pj.data["id"]
+
+    # Create workflow
+    res_wf = await invoke(f"workflow new MyWorkflow {project_id}")
+    workflow_id = res_wf.data["id"]
+    assert res_wf.retcode == 0
+
+    # List workflows
+    res_list = await invoke(f"workflow list {project_id}")
+    assert res_list.retcode == 0
+    debug(res_list.data)
+    assert len(res_list.data) == 1
+
+    # Delete workflow
+    res_delete = await invoke(f"workflow delete {workflow_id}")
+    assert res_delete.retcode == 0
+
+    # List workflows
+    res_list = await invoke(f"workflow list {project_id}")
+    assert res_list.retcode == 0
+    debug(res_list.data)
+    assert len(res_list.data) == 0
+
+
 async def test_workflow_list(register_user, invoke):
     PROJECT_NAME = "project_name"
     PROJECT_PATH = "project_path"
@@ -122,6 +150,33 @@ async def test_add_task(
     assert res.data.startswith("Added")
 
 
+async def test_remove_task(
+    invoke,
+    register_user,
+    task_factory,
+    workflow_factory,
+    tmp_path: Path,
+):
+    # Create workflow and task
+    wf = await workflow_factory()
+    t = await task_factory()
+
+    # Add task to workflow, twice
+    cmd = f"workflow add-task {wf.id} {t.id}"
+    res = await invoke(cmd)
+    assert res.retcode == 0
+    res = await invoke(cmd)
+    assert res.retcode == 0
+    workflow_task_id_1 = res.data["task_list"][0]["id"]
+
+    # Remove task 1 from workflow
+    cmd = f"workflow rm-task {wf.id} {workflow_task_id_1}"
+    debug(cmd)
+    res = await invoke(cmd)
+    assert res.retcode == 0
+    debug(res.data)
+
+
 async def test_add_task_by_name(
     invoke,
     register_user,