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

Issue:
Do not use catch-all `**kwargs` in cmd functions



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2245505d..6ea4d4ff6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
     * Require pydantic version to be `>=1.10.8` (\#486, \#490).
     * Make `sqlmodel` a development depedency (\#493).
     * Improve handling of a `ConnectError` in the CI (\#502).
+    * Remove arbitrary `kwargs` from internal functions (\#503).
 
 # 1.2.0
 
diff --git a/fractal/cmd/__init__.py b/fractal/cmd/__init__.py
index bbbcf6358..23e588cff 100644
--- a/fractal/cmd/__init__.py
+++ b/fractal/cmd/__init__.py
@@ -49,29 +49,44 @@ class NoCommandError(ValueError):
     pass
 
 
+def get_kwargs(_parameters, _kwargs):
+    return {k: _kwargs.get(k) for k in _parameters if k in _kwargs}
+
+
 async def project(
-    client: AuthClient, subcmd: str, batch: bool = False, **kwargs
+    client: AuthClient,
+    subcmd: str,
+    batch: bool = False,
+    **kwargs,
 ) -> BaseInterface:
+
     if subcmd == "new":
-        iface = await post_project(client, batch=batch, **kwargs)
+        parameters = ["name", "dataset"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_project(client, batch=batch, **function_kwargs)
     elif subcmd == "show":
-        iface = await get_project(client, **kwargs)
+        parameters = ["project_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_project(client, **function_kwargs)
     elif subcmd == "list":
-        iface = await get_project_list(client, **kwargs)
+        iface = await get_project_list(client)
     elif subcmd == "edit":
-        iface = await patch_project(client, **kwargs)
+        parameters = [
+            "project_id",
+            "new_name",
+            "make_read_only",
+            "remove_read_only",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await patch_project(client, **function_kwargs)
     elif subcmd == "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,
-        )
+        parameters = ["project_id", "dataset_name", "metadata", "type"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_dataset(client, batch=batch, **function_kwargs)
     elif subcmd == "delete":
-        iface = await delete_project(client, **kwargs)
+        parameters = ["project_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_project(client, **function_kwargs)
     else:
         raise NoCommandError(f"Command project {subcmd} not found")
 
@@ -79,39 +94,39 @@ async def project(
 
 
 async def dataset(
-    client: AuthClient, subcmd: str, batch: bool = False, **kwargs
+    client: AuthClient,
+    subcmd: str,
+    batch: bool = False,
+    **kwargs,
 ) -> BaseInterface:
     if subcmd == "show":
-        iface = await get_dataset(client, **kwargs)
+        parameters = ["project_id", "dataset_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_dataset(client, **function_kwargs)
     elif subcmd == "add-resource":
-        iface = await post_resource(
-            client,
-            batch=batch,
-            project_id=kwargs.pop("project_id"),
-            dataset_id=kwargs.pop("dataset_id"),
-            path=kwargs.pop("path"),
-            **kwargs,
-        )
+        parameters = ["project_id", "dataset_id", "path"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_resource(client, batch=batch, **function_kwargs)
     elif subcmd == "rm-resource":
-        iface = await delete_resource(
-            client,
-            batch=batch,
-            project_id=kwargs.pop("project_id"),
-            dataset_id=kwargs.pop("dataset_id"),
-            resource_id=kwargs.pop("resource_id"),
-            **kwargs,
-        )
+        parameters = ["project_id", "dataset_id", "resource_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_resource(client, **function_kwargs)
     elif subcmd == "edit":
-        project_id = int(kwargs.pop("project_id"))
-        dataset_id = int(kwargs.pop("dataset_id"))
-        iface = await patch_dataset(
-            client,
-            project_id=project_id,
-            dataset_id=dataset_id,
-            **kwargs,
-        )
+        parameters = [
+            "project_id",
+            "dataset_id",
+            "new_name",
+            "new_type",
+            "meta_file",
+            "make_read_only",
+            "remove_read_only",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await patch_dataset(client, **function_kwargs)
     elif subcmd == "delete":
-        iface = await delete_dataset(client, **kwargs)
+        parameters = ["project_id", "dataset_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_dataset(client, **function_kwargs)
     else:
         raise NoCommandError(f"Command dataset {subcmd} not found")
     return iface
@@ -121,22 +136,55 @@ async def task(
     client: AuthClient,
     subcmd: str,
     batch: bool = False,
-    verbose: bool = False,
     **kwargs,
 ) -> BaseInterface:
 
     if subcmd == "list":
         iface = await get_task_list(client)
     elif subcmd == "collect":
-        iface = await task_collect_pip(client, batch=batch, **kwargs)
+        parameters = [
+            "package",
+            "package_version",
+            "python_version",
+            "package_extras",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await task_collect_pip(client, batch=batch, **function_kwargs)
     elif subcmd == "check-collection":
-        iface = await task_collection_check(client, **kwargs)
+        parameters = ["state_id", "include_logs", "do_not_separate_logs"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await task_collection_check(client, **function_kwargs)
     elif subcmd == "new":
-        iface = await post_task(client, batch=batch, **kwargs)
+        parameters = [
+            "name",
+            "command",
+            "source",
+            "input_type",
+            "output_type",
+            "version",
+            "default_args_file",
+            "meta_file",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_task(client, batch=batch, **function_kwargs)
     elif subcmd == "edit":
-        iface = await patch_task(client, **kwargs)
+        parameters = [
+            "task_id_or_name",
+            "version",
+            "new_name",
+            "new_command",
+            "new_input_type",
+            "new_output_type",
+            "new_version",
+            "default_args_file",
+            "meta_file",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await patch_task(client, **function_kwargs)
     elif subcmd == "delete":
-        iface = await delete_task(client, **kwargs)
+        parameters = ["task_id_or_name"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_task(client, **function_kwargs)
     else:
         raise NoCommandError(f"Command task {subcmd} not found")
     return iface
@@ -146,47 +194,99 @@ async def workflow(
     client: AuthClient,
     subcmd: str,
     batch: bool = False,
-    verbose: bool = False,
     **kwargs,
 ) -> BaseInterface:
     if subcmd == "show":
-        iface = await get_workflow(client, **kwargs)
+        parameters = ["project_id", "workflow_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_workflow(client, **function_kwargs)
     elif subcmd == "new":
-        iface = await post_workflow(client, batch=batch, **kwargs)
+        parameters = ["name", "project_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_workflow(client, batch=batch, **function_kwargs)
     elif subcmd == "list":
-        iface = await get_workflow_list(client, batch=batch, **kwargs)
+        parameters = ["project_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_workflow_list(client, batch=batch, **function_kwargs)
     elif subcmd == "edit":
-        iface = await patch_workflow(client, **kwargs)
+        parameters = ["project_id", "workflow_id", "new_name"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await patch_workflow(client, **function_kwargs)
     elif subcmd == "delete":
-        iface = await delete_workflow(client, **kwargs)
+        parameters = ["project_id", "workflow_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_workflow(client, **function_kwargs)
     elif subcmd == "add-task":
-        iface = await post_workflowtask(client, batch=batch, **kwargs)
+        parameters = [
+            "project_id",
+            "workflow_id",
+            "task_id_or_name",
+            "version",
+            "order",
+            "args_file",
+            "meta_file",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await post_workflowtask(client, batch=batch, **function_kwargs)
     elif subcmd == "edit-task":
-        iface = await patch_workflowtask(client, **kwargs)
+        parameters = [
+            "project_id",
+            "workflow_id",
+            "workflow_task_id",
+            "args_file",
+            "meta_file",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await patch_workflowtask(client, **function_kwargs)
     elif subcmd == "rm-task":
-        iface = await delete_workflowtask(client, **kwargs)
+        parameters = ["project_id", "workflow_id", "workflow_task_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await delete_workflowtask(client, **function_kwargs)
     elif subcmd == "apply":
-        iface = await workflow_apply(client, **kwargs)
+        parameters = [
+            "project_id",
+            "workflow_id",
+            "input_dataset_id",
+            "output_dataset_id",
+            "worker_init",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await workflow_apply(client, **function_kwargs)
     elif subcmd == "import":
-        iface = await workflow_import(client, batch=batch, **kwargs)
+        parameters = ["project_id", "json_file"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await workflow_import(client, batch=batch, **function_kwargs)
     elif subcmd == "export":
-        iface = await workflow_export(client, **kwargs)
+        parameters = ["project_id", "workflow_id", "json_file"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await workflow_export(client, **function_kwargs)
     else:
         raise NoCommandError(f"Command workflow {subcmd} not found")
     return iface
 
 
 async def job(
-    client: AuthClient, subcmd: str, batch: bool = False, **kwargs
+    client: AuthClient,
+    subcmd: str,
+    batch: bool = False,
+    **kwargs,
 ) -> BaseInterface:
     if subcmd == "list":
-        iface = await get_job_list(client, batch=batch, **kwargs)
+        parameters = ["project_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_job_list(client, batch=batch, **function_kwargs)
     elif subcmd == "show":
-        iface = await get_job(client, batch=batch, **kwargs)
+        parameters = ["project_id", "job_id", "do_not_separate_logs"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_job(client, batch=batch, **function_kwargs)
     elif subcmd == "download-logs":
-        iface = await get_job_logs(client, **kwargs)
+        parameters = ["project_id", "job_id", "output_folder"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await get_job_logs(client, **function_kwargs)
     elif subcmd == "stop":
-        iface = await stop_job(client, batch=batch, **kwargs)
+        parameters = ["project_id", "job_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await stop_job(client, **function_kwargs)
     else:
         raise NoCommandError(f"Command job {subcmd} not found")
     return iface
@@ -212,17 +312,41 @@ async def user(
     client: AuthClient, subcmd: str, batch: bool = False, **kwargs
 ) -> BaseInterface:
     if subcmd == "register":
-        iface = await user_register(client, batch=batch, **kwargs)
+        parameters = [
+            "new_email",
+            "new_password",
+            "cache_dir",
+            "slurm_user",
+            "username",
+            "superuser",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await user_register(client, batch=batch, **function_kwargs)
     elif subcmd == "list":
-        iface = await user_list(client, **kwargs)
+        iface = await user_list(client)
     elif subcmd == "show":
-        iface = await user_show(client, **kwargs)
+        parameters = ["user_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await user_show(client, **function_kwargs)
     elif subcmd == "edit":
-        iface = await user_edit(client, **kwargs)
+        parameters = [
+            "user_id",
+            "new_email",
+            "new_password",
+            "new_cache_dir",
+            "new_slurm_user",
+            "new_username",
+            "make_superuser",
+            "remove_superuser",
+        ]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await user_edit(client, **function_kwargs)
     elif subcmd == "delete":
-        iface = await user_delete(client, **kwargs)
+        parameters = ["user_id"]
+        function_kwargs = get_kwargs(parameters, kwargs)
+        iface = await user_delete(client, **function_kwargs)
     elif subcmd == "whoami":
-        iface = await user_whoami(client, batch=batch, **kwargs)
+        iface = await user_whoami(client, batch=batch)
     else:
         raise NoCommandError(f"Command user {subcmd} not found")
 
diff --git a/fractal/cmd/_dataset.py b/fractal/cmd/_dataset.py
index b769a23c9..63ef09cd5 100644
--- a/fractal/cmd/_dataset.py
+++ b/fractal/cmd/_dataset.py
@@ -20,18 +20,24 @@
 
 async def post_dataset(
     client: AuthClient,
+    *,
     project_id: int,
     dataset_name: str,
-    metadata_filename: Optional[str] = None,
+    metadata: Optional[str] = None,
     type: Optional[str] = None,
     batch: bool = False,
-    **kwargs,
 ) -> RichJsonInterface:
-
-    if metadata_filename is None:
+    """
+    Arguments:
+        project_id: ID of project to add the new dataset to
+        dataset_name: Name of new dataset
+        metadata: Path to file containing dataset metadata in JSON format.
+        type: Dataset type.
+    """
+    if metadata is None:
         meta = {}
     else:
-        with open(metadata_filename, "r") as f:
+        with open(metadata, "r") as f:
             meta = json.load(f)
 
     dataset_dict = dict(name=dataset_name, meta=meta)
@@ -55,11 +61,10 @@ async def post_dataset(
 async def post_resource(
     client: AuthClient,
     *,
-    batch: bool = False,
     project_id: int,
     dataset_id: int,
     path: str,
-    **kwargs,
+    batch: bool = False,
 ) -> BaseInterface:
 
     # Check that path is absolute, which is needed for when the server submits
@@ -92,7 +97,6 @@ async def delete_resource(
     project_id: int,
     dataset_id: int,
     resource_id: int,
-    **kwargs,
 ) -> BaseInterface:
     res = await client.delete(
         (
@@ -114,7 +118,6 @@ async def patch_dataset(
     meta_file: Optional[str] = None,
     make_read_only: bool = False,
     remove_read_only: bool = False,
-    **kwargs,
 ) -> BaseInterface:
 
     # Prepare payload
@@ -148,7 +151,7 @@ async def patch_dataset(
 
 
 async def get_dataset(
-    client: AuthClient, *, project_id: int, dataset_id: int, **kwargs
+    client: AuthClient, *, project_id: int, dataset_id: int
 ) -> BaseInterface:
     res = await client.get(
         f"{settings.BASE_URL}/project/{project_id}/dataset/{dataset_id}"
@@ -157,35 +160,32 @@ async def get_dataset(
 
     dataset = check_response(res, expected_status_code=200, coerce=DatasetRead)
 
-    if kwargs.get("json", False):
-        return RichJsonInterface(retcode=0, data=dataset.dict())
-    else:
-        table = Table(title="Dataset")
-        table.add_column("ID", style="cyan", no_wrap=True)
-        table.add_column("Name", justify="right", style="green")
-        table.add_column("Type", style="white")
-        table.add_column("Meta", justify="center")
-        table.add_column("Read only", justify="center")
-
-        table.add_row(
-            str(dataset.id),
-            dataset.name,
-            dataset.type,
-            json.dumps(dataset.meta, indent=2),
-            "✅" if dataset.read_only else "❌",
-        )
-        table_res = Table(title="Resources")
-        table_res.add_column("Path", justify="center", style="yellow")
-        table_res.add_column("ID", justify="center", style="yellow")
-        table_res.add_column("Dataset ID", justify="center", style="yellow")
-        for r in dataset.resource_list:
-            table_res.add_row(r.path, str(r.id), str(r.dataset_id))
-        group = Group(table, table_res)
-        return RichConsoleInterface(retcode=0, data=group)
+    table = Table(title="Dataset")
+    table.add_column("ID", style="cyan", no_wrap=True)
+    table.add_column("Name", justify="right", style="green")
+    table.add_column("Type", style="white")
+    table.add_column("Meta", justify="center")
+    table.add_column("Read only", justify="center")
+
+    table.add_row(
+        str(dataset.id),
+        dataset.name,
+        dataset.type,
+        json.dumps(dataset.meta, indent=2),
+        "✅" if dataset.read_only else "❌",
+    )
+    table_res = Table(title="Resources")
+    table_res.add_column("Path", justify="center", style="yellow")
+    table_res.add_column("ID", justify="center", style="yellow")
+    table_res.add_column("Dataset ID", justify="center", style="yellow")
+    for r in dataset.resource_list:
+        table_res.add_row(r.path, str(r.id), str(r.dataset_id))
+    group = Group(table, table_res)
+    return RichConsoleInterface(retcode=0, data=group)
 
 
 async def delete_dataset(
-    client: AuthClient, project_id: int, dataset_id: int, **kwargs
+    client: AuthClient, *, project_id: int, dataset_id: int
 ) -> PrintInterface:
 
     res = await client.delete(
diff --git a/fractal/cmd/_job.py b/fractal/cmd/_job.py
index 102abf653..5422e3481 100644
--- a/fractal/cmd/_job.py
+++ b/fractal/cmd/_job.py
@@ -17,11 +17,11 @@
 
 async def get_job(
     client: AuthClient,
+    *,
     project_id: int,
     job_id: int,
-    batch: bool = False,
     do_not_separate_logs: bool = False,
-    **kwargs,
+    batch: bool = False,
 ) -> BaseInterface:
     """
     Query the status of a workflow-execution job
@@ -48,10 +48,7 @@ async def get_job(
 
 
 async def get_job_list(
-    client: AuthClient,
-    project_id: int,
-    batch: bool = False,
-    **kwargs,
+    client: AuthClient, *, project_id: int, batch: bool = False
 ) -> BaseInterface:
 
     res = await client.get(f"{settings.BASE_URL}/project/{project_id}/job/")
@@ -86,10 +83,10 @@ async def get_job_list(
 
 async def get_job_logs(
     client: AuthClient,
+    *,
     project_id: int,
     job_id: int,
     output_folder: str,
-    **kwargs,
 ) -> BaseInterface:
 
     # Check that output_folder does not already exist
@@ -153,10 +150,7 @@ async def get_job_logs(
 
 
 async def stop_job(
-    client: AuthClient,
-    project_id: int,
-    job_id: int,
-    **kwargs,
+    client: AuthClient, *, project_id: int, job_id: int
 ) -> BaseInterface:
     """
     Stop a workflow-execution job
diff --git a/fractal/cmd/_project.py b/fractal/cmd/_project.py
index aa1cff1fd..5aa2eafe9 100644
--- a/fractal/cmd/_project.py
+++ b/fractal/cmd/_project.py
@@ -17,10 +17,10 @@
 
 async def post_project(
     client: AuthClient,
+    *,
     name: str,
     dataset: Optional[str] = None,
     batch: bool = False,
-    **kwargs,
 ) -> BaseInterface:
     # Prepare a ProjectCreate request body
     project_dict = dict(name=name)
@@ -46,9 +46,7 @@ async def post_project(
         return RichJsonInterface(retcode=0, data=project.dict())
 
 
-async def get_project_list(
-    client: AuthClient, **kwargs
-) -> RichConsoleInterface:
+async def get_project_list(client: AuthClient) -> RichConsoleInterface:
 
     res = await client.get(f"{settings.BASE_URL}/project/")
     res = check_response(res, expected_status_code=200)
@@ -82,7 +80,7 @@ async def get_project_list(
 
 
 async def get_project(
-    client: AuthClient, project_id: int, **kwargs
+    client: AuthClient, *, project_id: int
 ) -> RichJsonInterface:
     res = await client.get(
         f"{settings.BASE_URL}/project/{project_id}",
@@ -92,7 +90,7 @@ async def get_project(
 
 
 async def delete_project(
-    client: AuthClient, project_id: int, **kwargs
+    client: AuthClient, *, project_id: int
 ) -> PrintInterface:
 
     res = await client.delete(
@@ -104,11 +102,11 @@ async def delete_project(
 
 async def patch_project(
     client: AuthClient,
+    *,
     project_id: int,
     new_name: Optional[str] = None,
     make_read_only: bool = False,
     remove_read_only: bool = False,
-    **kwargs,
 ) -> Union[RichJsonInterface, PrintInterface]:
     update = {}
     if new_name:
diff --git a/fractal/cmd/_task.py b/fractal/cmd/_task.py
index b6671790f..df5ae5064 100644
--- a/fractal/cmd/_task.py
+++ b/fractal/cmd/_task.py
@@ -32,7 +32,6 @@ async def task_collect_pip(
     python_version: Optional[str] = None,
     package_extras: Optional[str] = None,
     batch: bool = False,
-    **kwargs,
 ) -> BaseInterface:
 
     # Construct TaskCollectPip object
@@ -97,13 +96,12 @@ async def post_task(
     name: str,
     command: str,
     source: str,
-    batch: bool = False,
     input_type: str = "Any",
     output_type: str = "Any",
     version: Optional[str] = None,
-    meta_file: Optional[str] = None,
     default_args_file: Optional[str] = None,
-    **kwargs,
+    meta_file: Optional[str] = None,
+    batch: bool = False,
 ) -> BaseInterface:
     optionals = {}
     if version:
@@ -190,7 +188,9 @@ async def patch_task(
 
 
 async def delete_task(
-    client: AuthClient, *, task_id: int = None, **kwargs
+    client: AuthClient,
+    *,
+    task_id_or_name: str,
 ) -> PrintInterface:
 
     raise NotImplementedError("task_delete")
diff --git a/fractal/cmd/_user.py b/fractal/cmd/_user.py
index b0beba5f7..89c15901b 100644
--- a/fractal/cmd/_user.py
+++ b/fractal/cmd/_user.py
@@ -13,6 +13,7 @@
 
 async def user_register(
     client: AuthClient,
+    *,
     new_email: str,
     new_password: Optional[str] = None,
     slurm_user: Optional[str] = None,
@@ -20,7 +21,6 @@ async def user_register(
     username: Optional[str] = None,
     superuser: bool = False,
     batch: bool = False,
-    **kwargs,
 ) -> Union[RichJsonInterface, PrintInterface]:
 
     user_dict = dict(
@@ -65,7 +65,7 @@ async def user_register(
         return RichJsonInterface(retcode=0, data=data.dict())
 
 
-async def user_list(client: AuthClient, **kwargs) -> RichJsonInterface:
+async def user_list(client: AuthClient) -> RichJsonInterface:
     res = await client.get(f"{settings.FRACTAL_SERVER}/auth/userlist")
     users = check_response(res, expected_status_code=200)
     users = [UserRead(**user).dict() for user in users]
@@ -75,9 +75,7 @@ async def user_list(client: AuthClient, **kwargs) -> RichJsonInterface:
     )
 
 
-async def user_show(
-    client: AuthClient, user_id: str, **kwargs
-) -> RichJsonInterface:
+async def user_show(client: AuthClient, *, user_id: str) -> RichJsonInterface:
     res = await client.get(f"{settings.FRACTAL_SERVER}/auth/users/{user_id}")
     user = check_response(res, expected_status_code=200, coerce=UserRead)
     return RichJsonInterface(
@@ -88,6 +86,7 @@ async def user_show(
 
 async def user_edit(
     client: AuthClient,
+    *,
     user_id: str,
     new_email: Optional[str] = None,
     new_password: Optional[str] = None,
@@ -96,18 +95,20 @@ async def user_edit(
     new_username: Optional[str] = None,
     make_superuser: bool = False,
     remove_superuser: bool = False,
-    **kwargs,
 ) -> Union[RichJsonInterface, PrintInterface]:
+
     user_dict = dict(
         email=new_email,
         password=new_password,
     )
+
     if new_cache_dir is not None:
         user_dict["cache_dir"] = new_cache_dir
     if new_slurm_user is not None:
         user_dict["slurm_user"] = new_slurm_user
     if new_username is not None:
         user_dict["username"] = new_username
+
     user_update = UserUpdate(**user_dict)
 
     if make_superuser:
@@ -118,7 +119,6 @@ async def user_edit(
     payload = user_update.dict(exclude_unset=True, exclude_none=True)
     if not payload:
         return PrintInterface(retcode=1, data="Nothing to update")
-
     res = await client.patch(
         f"{settings.FRACTAL_SERVER}/auth/users/{user_id}", json=payload
     )
@@ -130,9 +130,7 @@ async def user_edit(
     )
 
 
-async def user_delete(
-    client: AuthClient, user_id: str, **kwargs
-) -> PrintInterface:
+async def user_delete(client: AuthClient, *, user_id: str) -> PrintInterface:
 
     res = await client.delete(
         f"{settings.FRACTAL_SERVER}/auth/users/{user_id}"
@@ -142,7 +140,7 @@ async def user_delete(
 
 
 async def user_whoami(
-    client: AuthClient, batch: bool, **kwargs
+    client: AuthClient, *, batch: bool
 ) -> Union[RichJsonInterface, PrintInterface]:
     res = await client.get(f"{settings.FRACTAL_SERVER}/auth/whoami")
     user = check_response(res, expected_status_code=200, coerce=UserRead)
diff --git a/fractal/cmd/_workflow.py b/fractal/cmd/_workflow.py
index c132aa1aa..86c229354 100644
--- a/fractal/cmd/_workflow.py
+++ b/fractal/cmd/_workflow.py
@@ -24,11 +24,7 @@
 
 
 async def post_workflow(
-    client: AuthClient,
-    name: str,
-    project_id: int,
-    batch: bool = False,
-    **kwargs,
+    client: AuthClient, *, name: str, project_id: int, batch: bool = False
 ) -> BaseInterface:
     workflow = WorkflowCreate(
         name=name,
@@ -48,10 +44,7 @@ async def post_workflow(
 
 
 async def get_workflow_list(
-    client: AuthClient,
-    project_id: int,
-    batch: bool = False,
-    **kwargs,
+    client: AuthClient, *, project_id: int, batch: bool = False
 ) -> RichJsonInterface:
 
     res = await client.get(
@@ -62,11 +55,7 @@ async def get_workflow_list(
 
 
 async def delete_workflow(
-    client: AuthClient,
-    *,
-    project_id: int,
-    workflow_id: int,
-    **kwargs,
+    client: AuthClient, *, project_id: int, workflow_id: int
 ) -> BaseInterface:
     res = await client.delete(
         f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}"
@@ -76,11 +65,7 @@ async def delete_workflow(
 
 
 async def get_workflow(
-    client: AuthClient,
-    *,
-    project_id: int,
-    workflow_id: int,
-    **kwargs,
+    client: AuthClient, *, project_id: int, workflow_id: int
 ) -> RichJsonInterface:
     res = await client.get(
         f"{settings.BASE_URL}/project/{project_id}/workflow/{workflow_id}"
@@ -196,7 +181,6 @@ async def delete_workflowtask(
     project_id: int,
     workflow_id: int,
     workflow_task_id: int,
-    **kwargs,
 ) -> BaseInterface:
 
     res = await client.delete(
@@ -236,7 +220,6 @@ async def workflow_apply(
     input_dataset_id: int,
     output_dataset_id: int,
     worker_init: Optional[str] = None,
-    **kwargs,
 ) -> BaseInterface:
     apply_wf_create_dict = dict(
         workflow_id=workflow_id,
@@ -268,12 +251,7 @@ async def workflow_apply(
 
 
 async def workflow_import(
-    client: AuthClient,
-    *,
-    project_id: int,
-    json_file: str,
-    batch: bool = False,
-    **kwargs,
+    client: AuthClient, *, project_id: int, json_file: str, batch: bool = False
 ) -> BaseInterface:
     with Path(json_file).open("r") as f:
         workflow = json.load(f)
@@ -300,7 +278,6 @@ async def workflow_export(
     project_id: int,
     workflow_id: int,
     json_file: str,
-    **kwargs,
 ) -> BaseInterface:
     res = await client.get(
         (


Test patch:
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index 05e0eb7c5..c55ea162e 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -179,6 +179,10 @@ async def test_delete_resource(register_user, invoke):
     assert res.data["dataset_id"] == dataset_id
     resource_id = res.data["id"]
 
+    # Show dataset
+    res = await invoke(f"dataset show {project_id} {dataset_id}")
+    res.show()
+
     # Remove a resource
     res = await invoke(
         f"dataset rm-resource {project_id} {dataset_id} {resource_id}"