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

Issue:
Add `--args-file` to `fractal workflow add-task`
Expected use:
```
fractal workflow add-task id_workflow id_task --args-file args.json
```
with
```
$ cat args.json

{"some_arg": "some_value"}
```
Add `--args-file` to `fractal workflow add-task`
Expected use:
```
fractal workflow add-task id_workflow id_task --args-file args.json
```
with
```
$ cat args.json

{"some_arg": "some_value"}
```


Discussion / hints:

Fix patch:
diff --git a/fractal/client/cmd/_workflow.py b/fractal/client/cmd/_workflow.py
index dd12de8e3..06ed095b4 100644
--- a/fractal/client/cmd/_workflow.py
+++ b/fractal/client/cmd/_workflow.py
@@ -1,4 +1,6 @@
+import json
 import logging
+from pathlib import Path
 from typing import Optional
 
 from ..authclient import AuthClient
@@ -67,14 +69,19 @@ async def workflow_add_task(
     id: int,
     task_id: int,
     order: int = None,
+    args_file: Optional[str] = None,
     **kwargs,
 ) -> RichJsonInterface:
     workflow_task = WorkflowTaskCreate(task_id=task_id, order=order)
+    if args_file:
+        with Path(args_file).open("r") as f:
+            args = json.load(f)
+            workflow_task.args = args
+
     res = await client.post(
         f"{settings.BASE_URL}/workflow/{id}/add-task/",
         json=workflow_task.dict(),
     )
-
     workflow_task = check_response(
         res, expected_status_code=201, coerce=WorkflowRead
     )
diff --git a/fractal/client/parser.py b/fractal/client/parser.py
index c75af9401..5a0421a19 100644
--- a/fractal/client/parser.py
+++ b/fractal/client/parser.py
@@ -322,7 +322,16 @@
     "task_id",
     help="Task id",
 )
-workflow_add_task_parser.add_argument("--order", help="")
+workflow_add_task_parser.add_argument(
+    "--order", help="Order of this task within the workflow's task list"
+)
+workflow_add_task_parser.add_argument(
+    "--args-file",
+    help=(
+        "Path to a json serialised file containing the arguments "
+        "ovverrides of the task"
+    ),
+)
 
 # workflow remove task
 workflow_remove_task_parser = workflow_subparsers.add_parser(
diff --git a/poetry.lock b/poetry.lock
index 6105b97e0..7fa455093 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -299,7 +299,7 @@ sqlmodel = "*"
 
 [[package]]
 name = "fractal-server"
-version = "1.0.0a0"
+version = "1.0.0a1"
 description = "Server component of the Fractal analytics platform"
 category = "dev"
 optional = false
@@ -322,7 +322,7 @@ tasks-core = ["fractal-tasks-core (<1.0.0)"]
 
 [package.source]
 type = "url"
-url = "https://github.com/fractal-analytics-platform/fractal-server/releases/download/1.0.0a0/fractal_server-1.0.0a0-py3-none-any.whl"
+url = "https://github.com/fractal-analytics-platform/fractal-server/releases/download/1.0.0a1/fractal_server-1.0.0a1-py3-none-any.whl"
 [[package]]
 name = "greenlet"
 version = "2.0.1"
@@ -860,7 +860,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "48e3d3fe0c5350fb2006177cc37632e3f076bc41b18a160b4b217830df23b18c"
+content-hash = "a2580360718035285399b15817b3ecaa25fe5a6c02a0ce54608b7b95bdc385c8"
 
 [metadata.files]
 aiosqlite = [
diff --git a/pyproject.toml b/pyproject.toml
index 34c8052fa..f1ae46e62 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ pytest = "^7.1.2"
 bumpver = "^2022.1118"
 pytest-asyncio = "^0.19.0"
 # fractal-server = "^1.0.0"
-fractal-server = {url = "https://github.com/fractal-analytics-platform/fractal-server/releases/download/1.0.0a0/fractal_server-1.0.0a0-py3-none-any.whl"}
+fractal-server = {url = "https://github.com/fractal-analytics-platform/fractal-server/releases/download/1.0.0a1/fractal_server-1.0.0a1-py3-none-any.whl"}
 coverage = {extras = ["toml"], version = "^6.5.0"}
 
 [build-system]


Test patch:
diff --git a/tests/fixtures_testserver.py b/tests/fixtures_testserver.py
index 0daa0dcc6..c84997bc3 100644
--- a/tests/fixtures_testserver.py
+++ b/tests/fixtures_testserver.py
@@ -77,6 +77,82 @@ def run_server():
     proc.kill()
 
 
+@pytest.fixture
+async def db(testserver):
+    """
+    NOTE: Only use this fixture within other fixtures!!!
+    """
+    from fractal_server.app.db import get_db
+
+    async for db in get_db():
+        yield db
+
+
+@pytest.fixture
+async def task_factory(db):
+    from fractal_server.app.models.task import Task
+
+    async def _task_factory(**task_args_override):
+        task_args = dict(
+            name="test_task",
+            command="cmd",
+            source="source",
+            input_type="Any",
+            output_type="Any",
+        )
+        task_args.update(task_args_override)
+        t = Task(**task_args)
+        db.add(t)
+        await db.commit()
+        await db.refresh(t)
+        return t
+
+    return _task_factory
+
+
+@pytest.fixture
+async def project_factory(db):
+    from fractal_server.app.models.project import Project
+
+    async def _project_factory(user_id=None, **project_args_override):
+        project_args = dict(name="name", project_dir="project/dir")
+        project_args.update(project_args_override)
+        p = Project(**project_args)
+        if user_id:
+            from fractal_server.app.security import User
+
+            user = await db.get(User, user_id)
+            p.user_member_list.append(user)
+        db.add(p)
+        await db.commit()
+        await db.refresh(p)
+        return p
+
+    return _project_factory
+
+
+@pytest.fixture
+async def workflow_factory(db, project_factory, register_user):
+    from fractal_server.app.models.workflow import Workflow
+
+    async def _workflow_factory(**wf_args_override):
+        if "project_id" not in wf_args_override:
+            p = await project_factory(user_id=register_user["id"])
+            wf_args_override["project_id"] = p.id
+
+        wf_args = dict(
+            name="name",
+        )
+        wf_args.update(wf_args_override)
+        wf = Workflow(**wf_args)
+        db.add(wf)
+        await db.commit()
+        await db.refresh(wf)
+        return wf
+
+    return _workflow_factory
+
+
 @pytest.fixture()
 async def user_factory(client, testserver):
     async def __register_user(email: str, password: str, slurm_user: str):
diff --git a/tests/test_workflow.py b/tests/test_workflow.py
index 0217f8bb2..5c4777a25 100644
--- a/tests/test_workflow.py
+++ b/tests/test_workflow.py
@@ -1,4 +1,7 @@
+import json
+
 import pytest  # noqa F401
+from devtools import debug
 
 
 async def test_workflow_new(clear_db, testserver, register_user, invoke):
@@ -14,3 +17,35 @@ async def test_workflow_new(clear_db, testserver, register_user, invoke):
     assert res_wf.retcode == 0
     assert res_wf.data["name"] == WORKFLOW_NAME
     assert res_wf.data["project_id"] == res_pj.data["id"]
+
+
+async def test_add_task(
+    clear_db,
+    testserver,
+    invoke,
+    register_user,
+    task_factory,
+    workflow_factory,
+    tmp_path,
+):
+    """
+    GIVEN a workflow
+    WHEN the client is invoked to add a task, including custom args
+    THEN
+        the WorkflowTask is correctly registered in the db, including custom
+        gargs
+    """
+    wf = await workflow_factory()
+    t = await task_factory()
+
+    custom_args = dict(custom="args")
+    args_file = tmp_path / "args_file.json"
+    with args_file.open("w") as f:
+        json.dump(custom_args, f)
+
+    cmd = f"workflow add-task {wf.id} {t.id} --args-file {args_file}"
+    debug(cmd)
+    res = await invoke(cmd)
+    assert res.retcode == 0
+    debug(res.data)
+    assert res.data["task_list"][0]["args"] == custom_args