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

Issue:
Align with fractal-server 1.3
Ref
* https://github.com/fractal-analytics-platform/fractal-server/pull/669
Align with fractal-server=1.2.5

Align with fractal-server=1.2.5



Discussion / hints:

Fix patch:
diff --git a/fractal/cmd/__init__.py b/fractal/cmd/__init__.py
index 221dac48a..7526d332a 100644
--- a/fractal/cmd/__init__.py
+++ b/fractal/cmd/__init__.py
@@ -5,43 +5,43 @@
 from ..config import settings
 from ..interface import BaseInterface
 from ..interface import PrintInterface
-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
-from ._job import job_download_logs
-from ._job import job_list
-from ._job import job_show
-from ._project import project_add_dataset
-from ._project import project_create
-from ._project import project_delete
-from ._project import project_edit
-from ._project import project_list
-from ._project import project_show
+from ._dataset import delete_dataset
+from ._dataset import delete_resource
+from ._dataset import get_dataset
+from ._dataset import patch_dataset
+from ._dataset import post_dataset
+from ._dataset import post_resource
+from ._job import get_job
+from ._job import get_job_list
+from ._job import get_job_logs
+from ._project import delete_project
+from ._project import get_project
+from ._project import get_project_list
+from ._project import patch_project
+from ._project import post_project
+from ._task import delete_task
+from ._task import get_task_list
+from ._task import patch_task
+from ._task import post_task
 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 ._task import task_new
 from ._user import user_delete
 from ._user import user_edit
 from ._user import user_list
 from ._user import user_register
 from ._user import user_show
 from ._user import user_whoami
-from ._workflow import workflow_add_task
+from ._workflow import delete_workflow
+from ._workflow import delete_workflowtask
+from ._workflow import get_workflow
+from ._workflow import get_workflow_list
+from ._workflow import patch_workflow
+from ._workflow import patch_workflowtask
+from ._workflow import post_workflow
+from ._workflow import post_workflowtask
 from ._workflow import workflow_apply
-from ._workflow import workflow_delete
-from ._workflow import workflow_edit
-from ._workflow import workflow_edit_task
 from ._workflow import workflow_export
 from ._workflow import workflow_import
-from ._workflow import workflow_list
-from ._workflow import workflow_new
-from ._workflow import workflow_remove_task
-from ._workflow import workflow_show
 
 
 class NoCommandError(ValueError):
@@ -52,24 +52,25 @@ async def project(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "new":
-        iface = await project_create(client, batch=batch, **kwargs)
+        iface = await post_project(client, batch=batch, **kwargs)
     elif subcmd == "show":
-        iface = await project_show(client, **kwargs)
+        iface = await get_project(client, **kwargs)
     elif subcmd == "list":
-        iface = await project_list(client, **kwargs)
+        iface = await get_project_list(client, **kwargs)
     elif subcmd == "edit":
-        iface = await project_edit(client, **kwargs)
+        iface = await patch_project(client, **kwargs)
     elif subcmd == "add-dataset":
-        iface = await project_add_dataset(
+        iface = await post_dataset(
             client,
             batch=batch,
             project_id=kwargs.pop("project_id"),
             dataset_name=kwargs.pop("dataset_name"),
             metadata_filename=kwargs.pop("metadata"),
+            type=kwargs.pop("type"),
             **kwargs,
         )
     elif subcmd == "delete":
-        iface = await project_delete(client, **kwargs)
+        iface = await delete_project(client, **kwargs)
     else:
         raise NoCommandError(f"Command project {subcmd} not found")
 
@@ -80,9 +81,9 @@ async def dataset(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "show":
-        iface = await dataset_show(client, **kwargs)
+        iface = await get_dataset(client, **kwargs)
     elif subcmd == "add-resource":
-        iface = await dataset_add_resource(
+        iface = await post_resource(
             client,
             batch=batch,
             project_id=kwargs.pop("project_id"),
@@ -91,7 +92,7 @@ async def dataset(
             **kwargs,
         )
     elif subcmd == "rm-resource":
-        iface = await dataset_delete_resource(
+        iface = await delete_resource(
             client,
             batch=batch,
             project_id=kwargs.pop("project_id"),
@@ -102,14 +103,14 @@ async def dataset(
     elif subcmd == "edit":
         project_id = int(kwargs.pop("project_id"))
         dataset_id = int(kwargs.pop("dataset_id"))
-        iface = await dataset_edit(
+        iface = await patch_dataset(
             client,
             project_id=project_id,
             dataset_id=dataset_id,
             **kwargs,
         )
     elif subcmd == "delete":
-        iface = await dataset_delete(client, **kwargs)
+        iface = await delete_dataset(client, **kwargs)
     else:
         raise NoCommandError(f"Command dataset {subcmd} not found")
     return iface
@@ -119,17 +120,17 @@ async def task(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "list":
-        iface = await task_list(client, **kwargs)
+        iface = await get_task_list(client, **kwargs)
     elif subcmd == "collect":
         iface = await task_collect_pip(client, batch=batch, **kwargs)
     elif subcmd == "check-collection":
         iface = await task_collection_check(client, **kwargs)
     elif subcmd == "new":
-        iface = await task_new(client, batch=batch, **kwargs)
+        iface = await post_task(client, batch=batch, **kwargs)
     elif subcmd == "edit":
-        iface = await task_edit(client, **kwargs)
+        iface = await patch_task(client, **kwargs)
     elif subcmd == "delete":
-        iface = await task_delete(client, **kwargs)
+        iface = await delete_task(client, **kwargs)
     else:
         raise NoCommandError(f"Command task {subcmd} not found")
     return iface
@@ -139,21 +140,21 @@ async def workflow(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "show":
-        iface = await workflow_show(client, **kwargs)
+        iface = await get_workflow(client, **kwargs)
     elif subcmd == "new":
-        iface = await workflow_new(client, batch=batch, **kwargs)
+        iface = await post_workflow(client, batch=batch, **kwargs)
     elif subcmd == "list":
-        iface = await workflow_list(client, batch=batch, **kwargs)
+        iface = await get_workflow_list(client, batch=batch, **kwargs)
     elif subcmd == "edit":
-        iface = await workflow_edit(client, **kwargs)
+        iface = await patch_workflow(client, **kwargs)
     elif subcmd == "delete":
-        iface = await workflow_delete(client, **kwargs)
+        iface = await delete_workflow(client, **kwargs)
     elif subcmd == "add-task":
-        iface = await workflow_add_task(client, batch=batch, **kwargs)
+        iface = await post_workflowtask(client, batch=batch, **kwargs)
     elif subcmd == "edit-task":
-        iface = await workflow_edit_task(client, **kwargs)
+        iface = await patch_workflowtask(client, **kwargs)
     elif subcmd == "rm-task":
-        iface = await workflow_remove_task(client, **kwargs)
+        iface = await delete_workflowtask(client, **kwargs)
     elif subcmd == "apply":
         iface = await workflow_apply(client, **kwargs)
     elif subcmd == "import":
@@ -169,11 +170,11 @@ async def job(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "list":
-        iface = await job_list(client, batch=batch, **kwargs)
+        iface = await get_job_list(client, batch=batch, **kwargs)
     elif subcmd == "show":
-        iface = await job_show(client, batch=batch, **kwargs)
+        iface = await get_job(client, batch=batch, **kwargs)
     elif subcmd == "download-logs":
-        iface = await job_download_logs(client, **kwargs)
+        iface = await get_job_logs(client, **kwargs)
     else:
         raise NoCommandError(f"Command job {subcmd} not found")
     return iface
diff --git a/fractal/cmd/_dataset.py b/fractal/cmd/_dataset.py
index 3757c5904..b769a23c9 100644
--- a/fractal/cmd/_dataset.py
+++ b/fractal/cmd/_dataset.py
@@ -5,6 +5,7 @@
 from rich.table import Table
 
 from ..authclient import AuthClient
+from ..common.schemas import DatasetCreate
 from ..common.schemas import DatasetRead
 from ..common.schemas import DatasetUpdate
 from ..common.schemas import ResourceCreate
@@ -17,7 +18,41 @@
 from ..response import check_response
 
 
-async def dataset_add_resource(
+async def post_dataset(
+    client: AuthClient,
+    project_id: int,
+    dataset_name: str,
+    metadata_filename: Optional[str] = None,
+    type: Optional[str] = None,
+    batch: bool = False,
+    **kwargs,
+) -> RichJsonInterface:
+
+    if metadata_filename is None:
+        meta = {}
+    else:
+        with open(metadata_filename, "r") as f:
+            meta = json.load(f)
+
+    dataset_dict = dict(name=dataset_name, meta=meta)
+    if type:
+        dataset_dict["type"] = type
+    dataset = DatasetCreate(**dataset_dict)
+
+    res = await client.post(
+        f"{settings.BASE_URL}/project/{project_id}/dataset/",
+        json=dataset.dict(exclude_unset=True),
+    )
+    new_dataset = check_response(
+        res, expected_status_code=201, coerce=DatasetRead
+    )
+    if batch:
+        return PrintInterface(retcode=0, data=new_dataset.id)
+    else:
+        return RichJsonInterface(retcode=0, data=new_dataset.dict())
+
+
+async def post_resource(
     client: AuthClient,
     *,
     batch: bool = False,
@@ -36,7 +71,10 @@ async def dataset_add_resource(
     resource = ResourceCreate(path=path)
 
     res = await client.post(
-        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}",
+        (
+            f"{settings.BASE_URL}/project/{project_id}/"
+            f"dataset/{dataset_id}/resource/"
+        ),
         json=resource.dict(),
     )
     new_resource = check_response(
@@ -48,7 +86,7 @@ async def dataset_add_resource(
         return RichJsonInterface(retcode=0, data=new_resource.dict())
 
 
-async def dataset_delete_resource(
+async def delete_resource(
     client: AuthClient,
     *,
     project_id: int,
@@ -57,13 +95,16 @@ async def dataset_delete_resource(
     **kwargs,
 ) -> BaseInterface:
     res = await client.delete(
-        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}/{resource_id}"
+        (
+            f"{settings.BASE_URL}/project/{project_id}/"
+            f"dataset/{dataset_id}/resource/{resource_id}"
+        )
     )
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
 
 
-async def dataset_edit(
+async def patch_dataset(
     client: AuthClient,
     *,
     project_id: int,
@@ -97,7 +138,7 @@ async def dataset_edit(
         return PrintInterface(retcode=1, data="Nothing to update")
 
     res = await client.patch(
-        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}",
+        (f"{settings.BASE_URL}/project/{project_id}/" f"dataset/{dataset_id}"),
         json=payload,
     )
     new_dataset = check_response(
@@ -106,11 +147,11 @@ async def dataset_edit(
     return RichJsonInterface(retcode=0, data=new_dataset.dict())
 
 
-async def dataset_show(
+async def get_dataset(
     client: AuthClient, *, project_id: int, dataset_id: int, **kwargs
 ) -> BaseInterface:
     res = await client.get(
-        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}"
+        f"{settings.BASE_URL}/project/{project_id}/dataset/{dataset_id}"
     )
     from rich.console import Group
 
@@ -143,12 +184,12 @@ async def dataset_show(
         return RichConsoleInterface(retcode=0, data=group)
 
 
-async def dataset_delete(
+async def delete_dataset(
     client: AuthClient, project_id: int, dataset_id: int, **kwargs
 ) -> PrintInterface:
 
     res = await client.delete(
-        f"{settings.BASE_URL}/project/{project_id}/{dataset_id}"
+        f"{settings.BASE_URL}/project/{project_id}/dataset/{dataset_id}"
     )
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
diff --git a/fractal/cmd/_job.py b/fractal/cmd/_job.py
index ea390e837..ec0791c4c 100644
--- a/fractal/cmd/_job.py
+++ b/fractal/cmd/_job.py
@@ -15,8 +15,9 @@
 from ..response import check_response
 
 
-async def job_show(
+async def get_job(
     client: AuthClient,
+    project_id: int,
     job_id: int,
     batch: bool = False,
     do_not_separate_logs: bool = False,
@@ -26,7 +27,9 @@ async def job_show(
     Query the status of a workflow-execution job
     """
 
-    res = await client.get(f"{settings.BASE_URL}/job/{job_id}")
+    res = await client.get(
+        f"{settings.BASE_URL}/project/{project_id}/job/{job_id}"
+    )
     job = check_response(
         res, expected_status_code=200, coerce=ApplyWorkflowRead
     )
@@ -44,14 +47,14 @@ async def job_show(
             )
 
 
-async def job_list(
+async def get_job_list(
     client: AuthClient,
     project_id: int,
     batch: bool = False,
     **kwargs,
 ) -> BaseInterface:
 
-    res = await client.get(f"{settings.BASE_URL}/project/{project_id}/jobs/")
+    res = await client.get(f"{settings.BASE_URL}/project/{project_id}/job/")
     jobs = check_response(res, expected_status_code=200)
 
     # Coerce to a list of ApplyWorkflowRead objects
@@ -81,8 +84,9 @@ async def job_list(
         return RichConsoleInterface(retcode=0, data=table)
 
 
-async def job_download_logs(
+async def get_job_logs(
     client: AuthClient,
+    project_id: int,
     job_id: int,
     output_folder: str,
     **kwargs,
@@ -95,7 +99,9 @@ async def job_download_logs(
         )
 
     # Send request to server
-    res = await client.get(f"{settings.BASE_URL}/job/download/{job_id}")
+    res = await client.get(
+        f"{settings.BASE_URL}/project/{project_id}/job/{job_id}/download/"
+    )
 
     # NOTE: We cannot use our default check_response here, because res._content
     # is binary. Therefore we check the status code by hand
diff --git a/fractal/cmd/_project.py b/fractal/cmd/_project.py
index 2d18a97b2..b201cbd3e 100644
--- a/fractal/cmd/_project.py
+++ b/fractal/cmd/_project.py
@@ -1,4 +1,3 @@
-import json
 import logging
 from typing import Optional
 from typing import Union
@@ -6,8 +5,6 @@
 from rich.table import Table
 
 from ..authclient import AuthClient
-from ..common.schemas import DatasetCreate
-from ..common.schemas import DatasetRead
 from ..common.schemas import ProjectCreate
 from ..common.schemas import ProjectRead
 from ..common.schemas import ProjectUpdate
@@ -19,7 +16,7 @@
 from ..response import check_response
 
 
-async def project_create(
+async def post_project(
     client: AuthClient,
     name: str,
     dataset: Optional[str] = None,
@@ -51,7 +48,9 @@ async def project_create(
         return RichJsonInterface(retcode=0, data=project.dict())
 
 
-async def project_list(client: AuthClient, **kwargs) -> RichConsoleInterface:
+async def get_project_list(
+    client: AuthClient, **kwargs
+) -> RichConsoleInterface:
 
     res = await client.get(f"{settings.BASE_URL}/project/")
     res = check_response(res, expected_status_code=200)
@@ -84,7 +83,7 @@ async def project_list(client: AuthClient, **kwargs) -> RichConsoleInterface:
     return RichConsoleInterface(retcode=0, data=table)
 
 
-async def project_show(
+async def get_project(
     client: AuthClient, project_id: int, **kwargs
 ) -> RichJsonInterface:
     res = await client.get(
@@ -94,40 +93,7 @@ async def project_show(
     return RichJsonInterface(retcode=0, data=project.dict())
 
 
-async def project_add_dataset(
-    client: AuthClient,
-    project_id: int,
-    dataset_name: str,
-    metadata_filename: Optional[str] = None,
-    type: Optional[str] = None,
-    batch: bool = False,
-    **kwargs,
-) -> RichJsonInterface:
-
-    if metadata_filename is None:
-        meta = {}
-    else:
-        meta = json.loads(metadata_filename)
-
-    dataset_dict = dict(name=dataset_name, meta=meta)
-    if type:
-        dataset_dict["type"] = type
-    dataset = DatasetCreate(**dataset_dict)
-
-    res = await client.post(
-        f"{settings.BASE_URL}/project/{project_id}/",
-        json=dataset.dict(exclude_unset=True),
-    )
-    new_dataset = check_response(
-        res, expected_status_code=201, coerce=DatasetRead
-    )
-    if batch:
-        return PrintInterface(retcode=0, data=new_dataset.id)
-    else:
-        return RichJsonInterface(retcode=0, data=new_dataset.dict())
-
-
-async def project_delete(
+async def delete_project(
     client: AuthClient, project_id: int, **kwargs
 ) -> PrintInterface:
 
@@ -138,7 +104,7 @@ async def project_delete(
     return PrintInterface(retcode=0, data="")
 
 
-async def project_edit(
+async def patch_project(
     client: AuthClient,
     project_id: int,
     new_name: Optional[str] = None,
diff --git a/fractal/cmd/_task.py b/fractal/cmd/_task.py
index faef42f1b..8f9f04063 100644
--- a/fractal/cmd/_task.py
+++ b/fractal/cmd/_task.py
@@ -16,7 +16,7 @@
 from .utils import refresh_task_cache
 
 
-async def task_list(client: AuthClient, **kwargs) -> RichJsonInterface:
+async def get_task_list(client: AuthClient, **kwargs) -> RichJsonInterface:
     task_list = await refresh_task_cache(client=client, **kwargs)
     return RichJsonInterface(retcode=0, data=task_list)
 
@@ -85,7 +85,7 @@ async def task_collection_check(
         )
 
 
-async def task_new(
+async def post_task(
     client: AuthClient,
     *,
     name: str,
@@ -122,7 +122,7 @@ async def task_new(
         return RichJsonInterface(retcode=0, data=new_task.dict())
 
 
-async def task_edit(
+async def patch_task(
     client: AuthClient,
     *,
     task_id_or_name: str,
@@ -169,8 +169,8 @@ async def task_edit(
     return RichJsonInterface(retcode=0, data=new_task.dict())
 
 
-async def task_delete(
-    client: AuthClient, task_id: int, **kwargs
+async def delete_task(
+    client: AuthClient, *, task_id: int = None, **kwargs
 ) -> PrintInterface:
 
     raise NotImplementedError("task_delete")
diff --git a/fractal/cmd/_workflow.py b/fractal/cmd/_workflow.py
index a6baad466..36502f283 100644
--- a/fractal/cmd/_workflow.py
+++ b/fractal/cmd/_workflow.py
@@ -22,7 +22,7 @@
 from .utils import get_cached_task_by_name
 
 
-async def workflow_new(
+async def post_workflow(
     client: AuthClient,
     name: str,
     project_id: int,
@@ -35,7 +35,7 @@ async def workflow_new(
     )
     logging.info(workflow)
     res = await client.post(
-        f"{settings.BASE_URL}/workflow/",
+        f"{settings.BASE_URL}/project/{project_id}/workflow/",
         json=workflow.dict(),
     )
     workflow = check_response(
@@ -47,7 +47,7 @@ async def workflow_new(
         return RichJsonInterface(retcode=0, data=workflow.dict())
 
 
-async def workflow_list(
+async def get_workflow_list(
     client: AuthClient,
     project_id: int,
     batch: bool = False,
@@ -55,39 +55,46 @@ async def workflow_list(
 ) -> RichJsonInterface:
 
     res = await client.get(
-        f"{settings.BASE_URL}/project/{project_id}/workflows/"
+        f"{settings.BASE_URL}/project/{project_id}/workflow/"
     )
     workflow_list = check_response(res, expected_status_code=200)
     return RichJsonInterface(retcode=0, data=workflow_list)
 
 
-async def workflow_delete(
+async def delete_workflow(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     **kwargs,
 ) -> BaseInterface:
-    res = await client.delete(f"{settings.BASE_URL}/workflow/{workflow_id}")
+    res = await client.delete(
+        f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}"
+    )
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
 
 
-async def workflow_show(
+async def get_workflow(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     **kwargs,
 ) -> RichJsonInterface:
-    res = await client.get(f"{settings.BASE_URL}/workflow/{workflow_id}")
+    res = await client.get(
+        f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}"
+    )
     workflow = check_response(
         res, expected_status_code=200, coerce=WorkflowRead
     )
     return RichJsonInterface(retcode=0, data=workflow.dict())
 
 
-async def workflow_add_task(
+async def post_workflowtask(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     batch: bool = False,
     task_id_or_name: str,
@@ -103,9 +110,9 @@ async def workflow_add_task(
         task_id = await get_cached_task_by_name(task_id_or_name, client)
 
     if order is None:
-        workflow_task = WorkflowTaskCreate(task_id=task_id)
+        workflow_task = WorkflowTaskCreate()
     else:
-        workflow_task = WorkflowTaskCreate(task_id=task_id, order=order)
+        workflow_task = WorkflowTaskCreate(order=order)
     if args_file:
         with Path(args_file).open("r") as f:
             args = json.load(f)
@@ -116,7 +123,11 @@ async def workflow_add_task(
             workflow_task.meta = meta
 
     res = await client.post(
-        f"{settings.BASE_URL}/workflow/{workflow_id}/add-task/",
+        (
+            f"{settings.BASE_URL}/project/{project_id}/"
+            f"workflow/{workflow_id}/wftask/"
+            f"?{task_id=}"
+        ),
         json=workflow_task.dict(exclude_unset=True),
     )
     workflow_task = check_response(
@@ -129,9 +140,10 @@ async def workflow_add_task(
         return RichJsonInterface(retcode=0, data=workflow_task.dict())
 
 
-async def workflow_edit_task(
+async def patch_workflowtask(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     workflow_task_id: int,
     args_file: Optional[str] = None,
@@ -160,8 +172,10 @@ async def workflow_edit_task(
 
     payload_update = WorkflowTaskUpdate(**payload)
     res = await client.patch(
-        f"{settings.BASE_URL}/"
-        f"workflow/{workflow_id}/edit-task/{workflow_task_id}",
+        (
+            f"{settings.BASE_URL}/project/{project_id}/"
+            f"workflow/{workflow_id}/wftask/{workflow_task_id}"
+        ),
         json=payload_update.dict(exclude_unset=True),
     )
     workflow_task = check_response(
@@ -171,25 +185,27 @@ async def workflow_edit_task(
     return RichJsonInterface(retcode=0, data=workflow_task.dict())
 
 
-async def workflow_remove_task(
+async def delete_workflowtask(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     workflow_task_id: int,
     **kwargs,
 ) -> BaseInterface:
 
     res = await client.delete(
-        f"{settings.BASE_URL}/"
-        f"workflow/{workflow_id}/rm-task/{workflow_task_id}"
+        f"{settings.BASE_URL}/project/{project_id}/"
+        f"workflow/{workflow_id}/wftask/{workflow_task_id}"
     )
     check_response(res, expected_status_code=204)
     return PrintInterface(retcode=0, data="")
 
 
-async def workflow_edit(
+async def patch_workflow(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     **workflow_update_dict,
 ) -> BaseInterface:
@@ -199,7 +215,8 @@ async def workflow_edit(
         return PrintInterface(retcode=1, data="Nothing to update")
 
     res = await client.patch(
-        f"{settings.BASE_URL}/workflow/{workflow_id}", json=payload
+        f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}",
+        json=payload,
     )
     new_workflow = check_response(
         res, expected_status_code=200, coerce=WorkflowRead
@@ -210,12 +227,10 @@ async def workflow_edit(
 async def workflow_apply(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     input_dataset_id: int,
-    output_dataset_id: int,
-    # output_dataset_id: Optional[int] = None,
-    overwrite_input: bool = False,
-    project_id: Optional[int] = None,
+    output_dataset_id: Optional[int] = None,
     worker_init: Optional[str] = None,
     **kwargs,
 ) -> BaseInterface:
@@ -223,17 +238,24 @@ async def workflow_apply(
         workflow_id=workflow_id,
         input_dataset_id=input_dataset_id,
         output_dataset_id=output_dataset_id,
-        overwrite_input=overwrite_input,
     )
     # Prepare ApplyWorkflowCreate object, without None attributes
-    if project_id:
-        apply_wf_create_dict["project_id"] = project_id
     if worker_init:
         apply_wf_create_dict["worker_init"] = worker_init
     apply_wf_create = ApplyWorkflowCreate(**apply_wf_create_dict)
 
+    # Prepare query parameters
+    query_parameters = f"input_dataset_id={input_dataset_id}"
+    if output_dataset_id is not None:
+        query_parameters = (
+            f"{query_parameters}&output_dataset_id={output_dataset_id}"
+        )
+
     res = await client.post(
-        f"{settings.BASE_URL}/project/apply/",
+        (
+            f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}/"
+            f"apply/?{query_parameters}"
+        ),
         json=apply_wf_create.dict(exclude_unset=True),
     )
     apply_wf_read = check_response(
@@ -254,7 +276,7 @@ async def workflow_import(
         workflow = json.load(f)
     workflow = WorkflowImport(**workflow)
     res = await client.post(
-        f"{settings.BASE_URL}/project/{project_id}/import-workflow/",
+        f"{settings.BASE_URL}/project/{project_id}/workflow/import/",
         json=workflow.dict(exclude_unset=True),
     )
     wf_read = check_response(
@@ -272,12 +294,16 @@ async def workflow_import(
 async def workflow_export(
     client: AuthClient,
     *,
+    project_id: int,
     workflow_id: int,
     json_file: str,
     **kwargs,
 ) -> BaseInterface:
     res = await client.get(
-        f"{settings.BASE_URL}/workflow/{workflow_id}/export/"
+        (
+            f"{settings.BASE_URL}/project/{project_id}/"
+            f"workflow/{workflow_id}/export/"
+        ),
     )
     workflow = check_response(
         res, expected_status_code=200, coerce=WorkflowExport
diff --git a/fractal/common b/fractal/common
index bbe6a6251..70a620718 160000
--- a/fractal/common
+++ b/fractal/common
@@ -1,1 +1,1 @@
-Subproject commit bbe6a625177f222c726453ae04c5ec2dae39ac9e
+Subproject commit 70a620718b1cc65cc2a0bff0df58d16cb7053f94
diff --git a/fractal/parser.py b/fractal/parser.py
index f692538c1..436900f83 100644
--- a/fractal/parser.py
+++ b/fractal/parser.py
@@ -118,6 +118,11 @@
     "--metadata",
     help="Path to file containing dataset metadata in JSON format.",
 )
+project_add_dataset_parser.add_argument(
+    "--type",
+    help="Dataset type",
+)
+
 
 # project edit
 project_edit_parser = project_subparsers.add_parser(
@@ -420,10 +425,7 @@
     description="List workflows for given project",
     allow_abbrev=False,
 )
-workflow_list_parser.add_argument(
-    "project_id",
-    help="Project ID",
-)
+workflow_list_parser.add_argument("project_id", type=int, help="Project ID")
 
 # workflow new
 workflow_new_parser = workflow_subparsers.add_parser(
@@ -434,22 +436,18 @@
 workflow_new_parser.add_argument(
     "name",
     help="Workflow name (must be unique, and not only made of numbers only)",
+    type=str,
 )
-workflow_new_parser.add_argument(
-    "project_id",
-    help="Project ID",
-)
+workflow_new_parser.add_argument("project_id", type=int, help="Project ID")
 
 # workflow show
-workflow_new_parser = workflow_subparsers.add_parser(
+workflow_show_parser = workflow_subparsers.add_parser(
     "show",
     description="Show workflow",
     allow_abbrev=False,
 )
-workflow_new_parser.add_argument(
-    "workflow_id",
-    help="Workflow ID",
-)
+workflow_show_parser.add_argument("project_id", type=int, help="Project ID")
+workflow_show_parser.add_argument("workflow_id", type=int, help="Workflow ID")
 
 # workflow edit
 workflow_edit_parser = workflow_subparsers.add_parser(
@@ -458,11 +456,8 @@
     argument_default=ap.SUPPRESS,
     allow_abbrev=False,
 )
-workflow_edit_parser.add_argument(
-    "workflow_id",
-    type=int,
-    help="Workflow ID",
-)
+workflow_edit_parser.add_argument("project_id", type=int, help="Project ID")
+workflow_edit_parser.add_argument("workflow_id", type=int, help="Workflow ID")
 workflow_edit_parser.add_argument("--name", type=str, help="New workflow name")
 
 # workflow delete
@@ -471,9 +466,9 @@
     description="Delete workflow",
     allow_abbrev=False,
 )
+workflow_delete_parser.add_argument("project_id", type=int, help="Project ID")
 workflow_delete_parser.add_argument(
-    "workflow_id",
-    help="Workflow ID",
+    "workflow_id", type=int, help="Workflow ID"
 )
 
 
@@ -483,8 +478,12 @@
     description="Add new task to specific workflow",
     allow_abbrev=False,
 )
+workflow_add_task_parser.add_argument(
+    "project_id", type=int, help="Project ID"
+)
 workflow_add_task_parser.add_argument(
     "workflow_id",
+    type=int,
     help="Workflow ID",
 )
 workflow_add_task_parser.add_argument(
@@ -514,8 +513,12 @@
     description="Edit task within specific workflow",
     allow_abbrev=False,
 )
+workflow_edit_task_parser.add_argument(
+    "project_id", type=int, help="Project ID"
+)
 workflow_edit_task_parser.add_argument(
     "workflow_id",
+    type=int,
     help="Workflow ID",
 )
 workflow_edit_task_parser.add_argument(
@@ -543,8 +546,12 @@
     description="Remove task from a specific workflow",
     allow_abbrev=False,
 )
+workflow_remove_task_parser.add_argument(
+    "project_id", type=int, help="Project ID"
+)
 workflow_remove_task_parser.add_argument(
     "workflow_id",
+    type=int,
     help="Workflow ID",
 )
 workflow_remove_task_parser.add_argument(
@@ -559,22 +566,11 @@
     argument_default=ap.SUPPRESS,
     allow_abbrev=False,
 )
+
+workflow_apply_parser.add_argument("project_id")
 workflow_apply_parser.add_argument("workflow_id")
 workflow_apply_parser.add_argument("input_dataset_id")
-workflow_apply_parser.add_argument(
-    "-o", "--output_dataset_id", help="Output dataset ID"
-)
-workflow_apply_parser.add_argument(
-    "--overwrite-input",
-    default=False,
-    action="store_true",
-    help="Allow overwriting the content of the input dataset",
-)
-workflow_apply_parser.add_argument(
-    "-p",
-    "--project-id",
-    help="ID of project the workflow and dataset belong to",
-)
+workflow_apply_parser.add_argument("output_dataset_id")
 workflow_apply_parser.add_argument(
     "-w",
     "--worker-init",
@@ -603,8 +599,14 @@
     description="Export workflow to file",
     allow_abbrev=False,
 )
+workflow_export_parser.add_argument(
+    "project_id",
+    type=int,
+    help="Project ID",
+)
 workflow_export_parser.add_argument(
     "workflow_id",
+    type=int,
     help="Workflow ID",
 )
 workflow_export_parser.add_argument(
@@ -632,6 +634,7 @@
 )
 job_list_parser.add_argument(
     "project_id",
+    type=int,
     help="Project ID",
 )
 
@@ -642,10 +645,8 @@
     argument_default=ap.SUPPRESS,
     allow_abbrev=False,
 )
-job_show_parser.add_argument(
-    "job_id",
-    help="ID of the job",
-)
+job_show_parser.add_argument("project_id", type=int, help="Project ID")
+job_show_parser.add_argument("job_id", type=int, help="Job ID")
 job_show_parser.add_argument(
     "--do-not-separate-logs",
     dest="do_not_separate_logs",
@@ -661,9 +662,9 @@
     allow_abbrev=False,
 )
 job_download_logs_parser.add_argument(
-    "job_id",
-    help="ID of the job",
+    "project_id", type=int, help="Project ID"
 )
+job_download_logs_parser.add_argument("job_id", type=int, help="Job ID")
 job_download_logs_parser.add_argument(
     "--output",
     dest="output_folder",
diff --git a/poetry.lock b/poetry.lock
index b8a70a32e..e292e9ef1 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -621,23 +621,6 @@ oauth = ["httpx-oauth (>=0.4,<0.12)"]
 redis = ["redis (>=4.3.3,<5.0.0)"]
 sqlalchemy = ["fastapi-users-db-sqlalchemy (>=4.0.0)"]
 
-[[package]]
-name = "fastapi-users-db-sqlmodel"
-version = "0.2.0"
-description = "FastAPI Users database adapter for SQLModel."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "fastapi-users-db-sqlmodel-0.2.0.tar.gz", hash = "sha256:d373aada5cc9e46e1cca327e4cb9e9e5c78208b61074955434fdeb4156bad1c3"},
-    {file = "fastapi_users_db_sqlmodel-0.2.0-py3-none-any.whl", hash = "sha256:b4e91fe9afe719040888ad886ea033b8e1f8fcd459b242733e95e33cfa16984c"},
-]
-
-[package.dependencies]
-fastapi-users = ">=10.0.2"
-sqlalchemy = {version = ">=1.4,<1.4.36", extras = ["asyncio"]}
-sqlmodel = "*"
-
 [[package]]
 name = "fractal-server"
 version = "1.2.5"
@@ -653,7 +636,6 @@ aiosqlite = "^0.17.0"
 alembic = "^1.9.1"
 fastapi = "^0.95.0"
 fastapi-users = {version = "^10.1", extras = ["oauth"]}
-fastapi-users-db-sqlmodel = "^0.2.0"
 python-dotenv = "^0.20.0"
 sqlalchemy = "^1.4"
 SQLAlchemy-Utils = "^0.38.3"
@@ -668,8 +650,8 @@ slurm = ["clusterfutures (>=0.5,<0.6)"]
 [package.source]
 type = "git"
 url = "https://github.com/fractal-analytics-platform/fractal-server.git"
-reference = "1.2.5"
-resolved_reference = "0579f8eaebe055edd0d44fc7941a9f9d3b728773"
+reference = "main"
+resolved_reference = "447c21bdc6d07a3851f4da22bee4479b082f8a31"
 
 [[package]]
 name = "greenlet"
@@ -1680,48 +1662,53 @@ test = ["pytest"]
 
 [[package]]
 name = "sqlalchemy"
-version = "1.4.35"
+version = "1.4.41"
 description = "Database Abstraction Library"
 category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
-    {file = "SQLAlchemy-1.4.35-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:093b3109c2747d5dc0fa4314b1caf4c7ca336d5c8c831e3cfbec06a7e861e1e6"},
-    {file = "SQLAlchemy-1.4.35-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6fb6b9ed1d0be7fa2c90be8ad2442c14cbf84eb0709dd1afeeff1e511550041"},
-    {file = "SQLAlchemy-1.4.35-cp27-cp27m-win32.whl", hash = "sha256:d38a49aa75a5759d0d118e26701d70c70a37b896379115f8386e91b0444bfa70"},
-    {file = "SQLAlchemy-1.4.35-cp27-cp27m-win_amd64.whl", hash = "sha256:70e571ae9ee0ff36ed37e2b2765445d54981e4d600eccdf6fe3838bc2538d157"},
-    {file = "SQLAlchemy-1.4.35-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:48036698f20080462e981b18d77d574631a3d1fc2c33b416c6df299ec1d10b99"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:4ba2c1f368bcf8551cdaa27eac525022471015633d5bdafbc4297e0511f62f51"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17316100fcd0b6371ac9211351cb976fd0c2e12a859c1a57965e3ef7f3ed2bc"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9837133b89ad017e50a02a3b46419869cf4e9aa02743e911b2a9e25fa6b05403"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4efb70a62cbbbc052c67dc66b5448b0053b509732184af3e7859d05fdf6223c"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-win32.whl", hash = "sha256:1ff9f84b2098ef1b96255a80981ee10f4b5d49b6cfeeccf9632c2078cd86052e"},
-    {file = "SQLAlchemy-1.4.35-cp310-cp310-win_amd64.whl", hash = "sha256:48f0eb5bcc87a9b2a95b345ed18d6400daaa86ca414f6840961ed85c342af8f4"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da25e75ba9f3fabc271673b6b413ca234994e6d3453424bea36bb5549c5bbaec"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeea6ace30603ca9a8869853bb4a04c7446856d7789e36694cd887967b7621f6"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5dbdbb39c1b100df4d182c78949158073ca46ba2850c64fe02ffb1eb5b70903"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfd8e4c64c30a5219032e64404d468c425bdbc13b397da906fc9bee6591fc0dd"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-win32.whl", hash = "sha256:9dac1924611698f8fe5b2e58601156c01da2b6c0758ba519003013a78280cf4d"},
-    {file = "SQLAlchemy-1.4.35-cp36-cp36m-win_amd64.whl", hash = "sha256:e8b09e2d90267717d850f2e2323919ea32004f55c40e5d53b41267e382446044"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:63c82c9e8ccc2fb4bfd87c24ffbac320f70b7c93b78f206c1f9c441fa3013a5f"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:effadcda9a129cc56408dd5b2ea20ee9edcea24bd58e6a1489fa27672d733182"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2c6c411d8c59afba95abccd2b418f30ade674186660a2d310d364843049fb2c1"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2489e70bfa2356f2d421106794507daccf6cc8711753c442fc97272437fc606"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-win32.whl", hash = "sha256:186cb3bd77abf2ddcf722f755659559bfb157647b3fd3f32ea1c70e8311e8f6b"},
-    {file = "SQLAlchemy-1.4.35-cp37-cp37m-win_amd64.whl", hash = "sha256:babd63fb7cb6b0440abb6d16aca2be63342a6eea3dc7b613bb7a9357dc36920f"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9e1a72197529ea00357640f21d92ffc7024e156ef9ac36edf271c8335facbc1a"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e255a8dd5572b0c66d6ee53597d36157ad6cf3bc1114f61c54a65189f996ab03"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9bec63b1e20ef69484f530fb4b4837e050450637ff9acd6dccc7003c5013abf8"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95411abc0e36d18f54fa5e24d42960ea3f144fb16caaa5a8c2e492b5424cc82c"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-win32.whl", hash = "sha256:28b17ebbaee6587013be2f78dc4f6e95115e1ec8dd7647c4e7be048da749e48b"},
-    {file = "SQLAlchemy-1.4.35-cp38-cp38-win_amd64.whl", hash = "sha256:9e7094cf04e6042c4210a185fa7b9b8b3b789dd6d1de7b4f19452290838e48bd"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:1b4eac3933c335d7f375639885765722534bb4e52e51cdc01a667eea822af9b6"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8edfb09ed2b865485530c13e269833dab62ab2d582fde21026c9039d4d0e62"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6204d06bfa85f87625e1831ca663f9dba91ac8aec24b8c65d02fb25cbaf4b4d7"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28aa2ef06c904729620cc735262192e622db9136c26d8587f71f29ec7715628a"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-win32.whl", hash = "sha256:ecc81336b46e31ae9c9bdfa220082079914e31a476d088d3337ecf531d861228"},
-    {file = "SQLAlchemy-1.4.35-cp39-cp39-win_amd64.whl", hash = "sha256:53c7469b86a60fe2babca4f70111357e6e3d5150373bc85eb3b914356983e89a"},
-    {file = "SQLAlchemy-1.4.35.tar.gz", hash = "sha256:2ffc813b01dc6473990f5e575f210ca5ac2f5465ace3908b78ffd6d20058aab5"},
+    {file = "SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"},
+    {file = "SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"},
+    {file = "SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"},
+    {file = "SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"},
+    {file = "SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"},
+    {file = "SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"},
+    {file = "SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"},
+    {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"},
+    {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"},
+    {file = "SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"},
+    {file = "SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"},
+    {file = "SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"},
+    {file = "SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"},
+    {file = "SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"},
+    {file = "SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"},
+    {file = "SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"},
 ]
 
 [package.dependencies]
@@ -1732,7 +1719,7 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
 aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
 asyncio = ["greenlet (!=0.4.17)"]
 asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
-mariadb-connector = ["mariadb (>=1.0.1)"]
+mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
 mssql = ["pyodbc"]
 mssql-pymssql = ["pymssql"]
 mssql-pyodbc = ["pyodbc"]
@@ -1742,7 +1729,7 @@ mysql-connector = ["mysql-connector-python"]
 oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
 postgresql = ["psycopg2 (>=2.7)"]
 postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
-postgresql-pg8000 = ["pg8000 (>=1.16.6)"]
+postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
 postgresql-psycopg2binary = ["psycopg2-binary"]
 postgresql-psycopg2cffi = ["psycopg2cffi"]
 pymysql = ["pymysql", "pymysql (<1)"]
@@ -1920,4 +1907,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.9"
-content-hash = "08aec035d022064d32ab6be0dacab985dc2aa48911650b1f762f062525b2b434"
+content-hash = "48ee9ddcbd049212554034c7d045621078316f020017c81f2a9a67467cb54a98"
diff --git a/pyproject.toml b/pyproject.toml
index a97e03669..b8ce6e762 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ devtools = "^0.9.0"
 pytest = "^7.1.2"
 bumpver = "^2022.1118"
 pytest-asyncio = "^0.19.0"
-fractal-server = { git = "https://github.com/fractal-analytics-platform/fractal-server.git", tag = "1.2.5" }
+fractal-server = { git = "https://github.com/fractal-analytics-platform/fractal-server.git", branch = "main" }
 coverage = {extras = ["toml"], version = "^6.5.0"}
 pytest-pretty = "^1.0.1"
 


Test patch:
diff --git a/tests/fixtures_testserver.py b/tests/fixtures_testserver.py
index 57376298e..91b47e72b 100644
--- a/tests/fixtures_testserver.py
+++ b/tests/fixtures_testserver.py
@@ -120,7 +120,7 @@ async def _project_factory(user_id=None, **project_args_override):
             from fractal_server.app.security import User
 
             user = await db.get(User, user_id)
-            p.user_member_list.append(user)
+            p.user_list.append(user)
         db.add(p)
         await db.commit()
         await db.refresh(p)
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index ed6e7fb62..05e0eb7c5 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -1,6 +1,39 @@
 import json
 
 import pytest
+from devtools import debug
+
+
+async def test_create_dataset(register_user, invoke, tmp_path):
+    """
+    Test some specific branches of the post_dataset function and parser.
+    """
+
+    res = await invoke("project new prj0")
+    project_id = res.data["id"]
+
+    METADATA = {"some": "value"}
+    TYPE = "sometype"
+
+    file_metadata = str(tmp_path / "metadata.json")
+    with open(file_metadata, "w") as f:
+        json.dump(METADATA, f)
+
+    res = await invoke(
+        (
+            f"project add-dataset {project_id} MyDS "
+            f"--metadata {file_metadata} "
+            f"--type {TYPE}"
+        )
+    )
+    debug(res.data)
+    assert res.retcode == 0
+    assert res.data["meta"] == METADATA
+    assert res.data["type"] == TYPE
+
+    res = await invoke(f"--batch project add-dataset {project_id} MyNewDS")
+    debug(res.data)
+    assert res.retcode == 0
 
 
 async def test_add_resource(register_user, invoke):
diff --git a/tests/test_job.py b/tests/test_job.py
index f7255d16d..b0115003c 100644
--- a/tests/test_job.py
+++ b/tests/test_job.py
@@ -14,6 +14,7 @@ async def test_job_show(
     tmp_path: Path,
     status: str,
     workflow_factory,
+    project_factory,
     job_factory,
 ):
     """
@@ -23,7 +24,9 @@ async def test_job_show(
     """
 
     # Create mock Workflow/ApplyWorkflow objects
-    wf = await workflow_factory()
+    res = await invoke("project new prj0")
+    project_id = res.data["id"]
+    wf = await workflow_factory(project_id=project_id)
     debug(wf)
     log = LOG
     workflow_path = tmp_path / f"workflow_{wf.id}"
@@ -37,7 +40,7 @@ async def test_job_show(
     debug(job)
 
     # Check `job show` output
-    cmd = f"job show {job.id}"
+    cmd = f"job show {project_id} {job.id}"
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 0
@@ -48,13 +51,13 @@ async def test_job_show(
     res.show()
 
     # Check `job show` output with --batch
-    cmd = f"--batch job show {job.id}"
+    cmd = f"--batch job show {project_id} {job.id}"
     res = await invoke(cmd)
     assert res.retcode == 0
     assert res.data == status
 
     # Check `job show` output with `--do-not-separate-logs`
-    cmd = f"job show {job.id} --do-not-separate-logs"
+    cmd = f"job show {project_id} {job.id} --do-not-separate-logs"
     res = await invoke(cmd)
     debug(res.data)
     assert res.retcode == 0
@@ -149,14 +152,20 @@ async def test_job_download_logs(
     # Check that download-logs fails if the output folder already exists
     output_fail = tmp_path / "output_dir_for_logs_fail"
     output_fail.mkdir()
-    cmd = f"job download-logs {job.project_id} --output {str(output_fail)}"
+    cmd = (
+        f"job download-logs {job.project_id} {job.id} "
+        f"--output {str(output_fail)}"
+    )
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 1
 
     # Check standard `job download-logs` output
     output = tmp_path / "output_dir_for_logs"
-    cmd = f"job download-logs {job.project_id} --output {str(output)}"
+    cmd = (
+        f"job download-logs {job.project_id} {job.id} "
+        f"--output {str(output)}"
+    )
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 0
diff --git a/tests/test_task.py b/tests/test_task.py
index 138f7a64b..6abdedc95 100644
--- a/tests/test_task.py
+++ b/tests/test_task.py
@@ -184,3 +184,16 @@ async def test_task_edit(
     debug(res)
     debug(res.data)
     assert res.retcode == 0
+
+
+async def test_task_delete(register_user, invoke):
+    """
+    This is currently a placeholder test, since task-delete is not implemented
+    """
+    res = await invoke("task new _name _command _source")
+    debug(res.data)
+    assert res.retcode == 0
+    task_id = res.data["id"]
+    with pytest.raises(NotImplementedError):
+        debug(f"task delete {task_id}")
+        res = await invoke(f"task delete {task_id}")
diff --git a/tests/test_workflow.py b/tests/test_workflow.py
index 0958a2950..b06a545a6 100644
--- a/tests/test_workflow.py
+++ b/tests/test_workflow.py
@@ -41,7 +41,7 @@ async def test_workflow_delete(register_user, invoke):
     assert len(res_list.data) == 1
 
     # Delete workflow
-    res_delete = await invoke(f"workflow delete {workflow_id}")
+    res_delete = await invoke(f"workflow delete {project_id} {workflow_id}")
     assert res_delete.retcode == 0
 
     # List workflows
@@ -64,14 +64,14 @@ async def test_workflow_edit(register_user, invoke):
 
     # Edit workflow name
     NAME = "new-workflow-name"
-    cmd = f"workflow edit {workflow_id} --name {NAME}"
+    cmd = f"workflow edit {project_id} {workflow_id} --name {NAME}"
     debug(cmd)
     res_edit = await invoke(cmd)
     assert res_edit.retcode == 0
     debug(res_edit.data)
 
     # List workflows, and check edit
-    res = await invoke(f"workflow show {workflow_id}")
+    res = await invoke(f"workflow show {project_id} {workflow_id}")
     debug(res.data)
     assert res.retcode == 0
     assert res.data["name"] == NAME
@@ -140,7 +140,9 @@ async def test_workflow_add_task(
         the WorkflowTask's are correctly registered in the db, and the returned
         object has the right properties
     """
-    wf = await workflow_factory()
+    res = await invoke("project new MyProject")
+    project_id = res.data["id"]
+    wf = await workflow_factory(project_id=project_id)
     t = await task_factory()
 
     # Add a WorkflowTask with --args-file and --meta-file arguments
@@ -153,7 +155,7 @@ async def test_workflow_add_task(
     with meta_file.open("w") as f:
         json.dump(META, f)
     cmd = (
-        f"workflow add-task {wf.id} {t.id} "
+        f"workflow add-task {project_id} {wf.id} {t.id} "
         f"--args-file {args_file} --meta-file {meta_file}"
     )
     debug(cmd)
@@ -166,7 +168,7 @@ async def test_workflow_add_task(
     assert workflow_task["meta"] == META
 
     # Add a WorkflowTask with the --batch option
-    cmd = f"--batch workflow add-task {wf.id} {t.id}"
+    cmd = f"--batch workflow add-task {project_id} {wf.id} {t.id} --order 1"
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 0
@@ -174,7 +176,7 @@ async def test_workflow_add_task(
     workflow_task_id_2 = int(res.data)
 
     # Check that the WorkflowTask's in Workflow.task_list have the correct IDs
-    cmd = f"workflow show {wf.id}"
+    cmd = f"workflow show {project_id} {wf.id}"
     res = await invoke(cmd)
     assert res.retcode == 0
     workflow = res.data
@@ -196,11 +198,13 @@ async def test_workflow_add_task_by_name(
     WHEN the client is invoked to add a task *by name*
     THEN the WorkflowTask is correctly registered in the db
     """
-    wf = await workflow_factory()
+    res = await invoke("project new MyProject")
+    project_id = res.data["id"]
+    wf = await workflow_factory(project_id=project_id)
     task = await task_factory()
     debug(task)
 
-    cmd = f"workflow add-task {wf.id} {task.name}"
+    cmd = f"workflow add-task {project_id} {wf.id} {task.name}"
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 0
@@ -225,6 +229,9 @@ async def test_task_cache_with_non_unique_names(
         * Addressing tasks by name raises a FileNotFoundError
     """
 
+    res = await invoke("project new MyProject")
+    project_id = res.data["id"]
+
     # Create two tasks with the same name
     task1 = await task_factory()
     task2 = await task_factory()
@@ -239,8 +246,8 @@ async def test_task_cache_with_non_unique_names(
 
     # Verify that adding tasks to a worfklow by name (as opposed to "by id")
     # fails because of missing cache file
-    wf = await workflow_factory()
-    cmd = f"workflow add-task {wf.id} {task1.name}"
+    wf = await workflow_factory(project_id=project_id)
+    cmd = f"workflow add-task {project_id} {wf.id} {task1.name}"
     debug(cmd)
     with pytest.raises(FileNotFoundError):
         res = await invoke(cmd)
@@ -253,12 +260,14 @@ async def test_workflow_rm_task(
     workflow_factory,
     tmp_path: Path,
 ):
-    # Create workflow and task
-    wf = await workflow_factory()
+    # Create project, workflow and task
+    res = await invoke("project new MyProject")
+    project_id = res.data["id"]
+    wf = await workflow_factory(project_id=project_id)
     t = await task_factory()
 
     # Add task to workflow, twice
-    cmd = f"workflow add-task {wf.id} {t.id}"
+    cmd = f"workflow add-task {project_id} {wf.id} {t.id}"
     res = await invoke(cmd)
     assert res.retcode == 0
     res = await invoke(cmd)
@@ -266,7 +275,7 @@ async def test_workflow_rm_task(
     workflow_task_id_1 = res.data["id"]
 
     # Remove task 1 from workflow
-    cmd = f"workflow rm-task {wf.id} {workflow_task_id_1}"
+    cmd = f"workflow rm-task {project_id} {wf.id} {workflow_task_id_1}"
     debug(cmd)
     res = await invoke(cmd)
     assert res.retcode == 0
@@ -287,11 +296,14 @@ async def test_workflow_edit_task(
         the WorkflowTask is correctly registered in the db, including custom
         gargs
     """
-    wf = await workflow_factory()
+
+    res = await invoke("project new MyProject")
+    project_id = res.data["id"]
+    wf = await workflow_factory(project_id=project_id)
     t = await task_factory()
 
     # Create task, without overriding arguments
-    cmd = f"workflow add-task {wf.id} {t.id}"
+    cmd = f"workflow add-task {project_id} {wf.id} {t.id}"
     res = await invoke(cmd)
     assert res.retcode == 0
 
@@ -310,7 +322,7 @@ async def test_workflow_edit_task(
     debug(res.data)
     workflow_task_id = res.data["id"]
     cmd = (
-        f"workflow edit-task {wf.id} {workflow_task_id} "
+        f"workflow edit-task {project_id} {wf.id} {workflow_task_id} "
         f"--args-file {args_file} --meta-file {meta_file}"
     )
     debug(cmd)
@@ -320,7 +332,7 @@ async def test_workflow_edit_task(
     assert res.data["meta"] == META
 
     # Check that also the workflow in the db was correctly updated
-    res = await invoke(f"workflow show {wf.id}")
+    res = await invoke(f"workflow show {project_id} {wf.id}")
     assert res.retcode == 0
     debug(res.data)
     assert res.data["task_list"][0]["args"] == ARGS
@@ -335,7 +347,7 @@ async def test_workflow_edit_task(
 
     workflow_task_id = res.data["task_list"][0]["id"]
     cmd = (
-        f"workflow edit-task {wf.id} {workflow_task_id} "
+        f"workflow edit-task {project_id} {wf.id} {workflow_task_id} "
         f"--meta-file {meta_file}"
     )
     debug(cmd)
@@ -399,6 +411,13 @@ async def test_workflow_apply(
     debug(resource)
     assert res.retcode == 0
 
+    # res = await invoke(
+    #     f"dataset add-resource {prj_id} {output_dataset_id} {str(tmp_path)}"
+    # )
+    # resource = res.data
+    # debug(resource)
+    # assert res.retcode == 0
+
     res = await invoke(f"workflow new {WORKFLOW_NAME} {prj_id}")
     workflow = res.data
     workflow_id = workflow["id"]
@@ -406,7 +425,7 @@ async def test_workflow_apply(
     assert res.retcode == 0
 
     TASK_ID = 1
-    res = await invoke(f"workflow add-task {workflow_id} {TASK_ID}")
+    res = await invoke(f"workflow add-task {prj_id} {workflow_id} {TASK_ID}")
     workflow_task = res.data
     debug(workflow_task)
     assert res.retcode == 0
@@ -414,8 +433,8 @@ async def test_workflow_apply(
     debug(TASK_NAME)
 
     cmd = (
-        f"workflow apply {workflow_id} {input_dataset_id} "
-        f"-o {output_dataset_id} -p {prj_id}"
+        f"workflow apply "
+        f"{prj_id} {workflow_id} {input_dataset_id} {output_dataset_id}"
     )
     debug(cmd)
     res = await invoke(cmd)
@@ -428,7 +447,7 @@ async def test_workflow_apply(
     # TODO: add an assertion about the output, instead of calling `job show`
 
     # Check that job completed successfully
-    cmd = f"job show {job_id}"
+    cmd = f"job show {prj_id} {job_id}"
     starting_time = time.perf_counter()
     debug(cmd)
     while True:
@@ -450,13 +469,13 @@ async def test_workflow_apply(
     with open(args_file, "w") as f:
         json.dump({"raise_error": True}, f)
     res = await invoke(
-        f"workflow add-task {workflow_id} {TASK_ID}"
+        f"workflow add-task {prj_id} {workflow_id} {TASK_ID}"
         f" --args-file {args_file}"
     )
     assert res.retcode == 0
     cmd = (
-        f"workflow apply {workflow_id} {input_dataset_id} "
-        f"-o {output_dataset_id} -p {prj['id']}"
+        f"workflow apply "
+        f"{prj_id} {workflow_id} {input_dataset_id} {output_dataset_id}"
     )
     debug(cmd)
     res = await invoke(cmd)
@@ -464,7 +483,7 @@ async def test_workflow_apply(
     job_id = res.data["id"]
 
     # Verify that status is failed, and that there is a log
-    cmd = f"job show {job_id} --do-not-separate-logs"
+    cmd = f"job show {prj_id} {job_id} --do-not-separate-logs"
     starting_time = time.perf_counter()
     while True:
         res = await invoke(cmd)
@@ -488,9 +507,12 @@ async def test_workflow_export(
 ):
     NAME = "WorkFlow"
     wf = await workflow_factory(name=NAME)
+    prj_id = wf.project_id
     wf_id = wf.id
     filename = str(tmp_path / "exported_wf.json")
-    res = await invoke(f"workflow export {wf_id} --json-file {filename}")
+    res = await invoke(
+        f"workflow export {prj_id} {wf_id} --json-file {filename}"
+    )
     debug(res.data)
     assert res.retcode == 0
     with open(filename, "r") as f:
@@ -542,6 +564,6 @@ async def test_workflow_import(
 
     # get the workflow from the server, and check that it is the same
     workflow_id = res.data["id"]
-    res = await invoke(f"workflow show {workflow_id}")
+    res = await invoke(f"workflow show {project_id} {workflow_id}")
     assert res.retcode == 0
     assert res.data == imported_workflow