Skip to content

Commit c99659f

Browse files
authored
feat(cli): add custom metadata to Plan (#2929)
1 parent e9f0a00 commit c99659f

File tree

10 files changed

+121
-57
lines changed

10 files changed

+121
-57
lines changed

renku/command/schema/plan.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
import marshmallow
2323

24-
from renku.command.schema.calamus import JsonLDSchema, Nested, fields, prov, renku, schema
24+
from renku.command.schema.annotation import AnnotationSchema
25+
from renku.command.schema.calamus import JsonLDSchema, Nested, fields, oa, prov, renku, schema
2526
from renku.command.schema.parameter import CommandInputSchema, CommandOutputSchema, CommandParameterSchema
2627
from renku.domain_model.workflow.plan import Plan
2728

@@ -51,6 +52,7 @@ class Meta:
5152
outputs = Nested(renku.hasOutputs, CommandOutputSchema, many=True, missing=None)
5253
parameters = Nested(renku.hasArguments, CommandParameterSchema, many=True, missing=None)
5354
success_codes = fields.List(renku.successCodes, fields.Integer(), missing=[0])
55+
annotations = Nested(oa.hasTarget, AnnotationSchema, reverse=True, many=True)
5456

5557
@marshmallow.pre_dump
5658
def _pre_dump(self, in_data, **kwargs):

renku/command/view_model/plan.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# limitations under the License.
1818
"""Plan view model."""
1919

20+
from __future__ import annotations
21+
22+
import json
2023
from typing import List, Optional, Union
2124

2225
from renku.domain_model.workflow.composite_plan import CompositePlan
@@ -153,6 +156,7 @@ def __init__(
153156
parameters: List[CommandParameterViewModel],
154157
description: Optional[str] = None,
155158
success_codes: Optional[str] = None,
159+
annotations: Optional[str] = None,
156160
):
157161
self.id = id
158162
self.name = name
@@ -162,6 +166,7 @@ def __init__(
162166
self.inputs = inputs
163167
self.outputs = outputs
164168
self.parameters = parameters
169+
self.annotations = annotations
165170

166171
@classmethod
167172
def from_plan(cls, plan: Plan):
@@ -182,6 +187,9 @@ def from_plan(cls, plan: Plan):
182187
inputs=[CommandInputViewModel.from_input(input) for input in plan.inputs],
183188
outputs=[CommandOutputViewModel.from_output(output) for output in plan.outputs],
184189
parameters=[CommandParameterViewModel.from_parameter(param) for param in plan.parameters],
190+
annotations=json.dumps([{"id": a.id, "body": a.body, "source": a.source} for a in plan.annotations])
191+
if plan.annotations
192+
else None,
185193
)
186194

187195

renku/command/workflow.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from pathlib import Path
2626
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
2727

28+
from renku.domain_model.provenance.annotation import Annotation
29+
2830
if TYPE_CHECKING:
2931
from networkx import DiGraph
3032

@@ -334,6 +336,7 @@ def _edit_workflow(
334336
rename_params: List[str],
335337
describe_params: List[str],
336338
plan_gateway: IPlanGateway,
339+
custom_metadata: Optional[Dict] = None,
337340
):
338341
"""Edits a workflow details.
339342
@@ -346,6 +349,7 @@ def _edit_workflow(
346349
rename_params(List[str]): New names for parameters.
347350
describe_params(List[str]): New descriptions for parameters.
348351
plan_gateway(IPlanGateway): Injected plan gateway.
352+
custom_metadata(Dict, optional): Custom JSON-LD metadata (Default value = None).
349353
350354
Returns:
351355
Details of the modified Plan.
@@ -359,6 +363,13 @@ def _edit_workflow(
359363
workflow.description = description
360364

361365
if isinstance(workflow, Plan):
366+
if custom_metadata:
367+
existing_metadata = [a for a in workflow.annotations if a.source != "renku"]
368+
369+
existing_metadata.append(Annotation(id=Annotation.generate_id(), body=custom_metadata, source="renku"))
370+
371+
workflow.annotations = existing_metadata
372+
362373
workflow.set_parameters_from_strings(set_params)
363374

364375
def _mod_params(workflow, changed_params, attr):

renku/core/plugin/run.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222

2323

2424
@hookspec
25-
def process_run_annotations(run):
26-
"""Plugin Hook to add ``Annotation`` entry list to a ``Activity``.
25+
def plan_annotations(plan):
26+
"""Plugin Hook to add ``Annotation`` entry list to a ``Plan``.
27+
28+
Run when a Plan is created by ``renku run``.
2729
2830
Args:
29-
run: A ``Activity`` object to get annotations for.
31+
plan: A ``Plan`` object to get annotations for.
3032
3133
Returns:
3234
A list of ``renku.domain_model.provenance.annotation.Annotation``
@@ -40,23 +42,10 @@ def process_run_annotations(run):
4042
def activity_annotations(activity):
4143
"""Plugin Hook to add ``Annotation`` entry list to a ``Activity``.
4244
43-
Args:
44-
activity: An ``Activity`` object to get annotations for.
45-
46-
Returns:
47-
A list of ``renku.domain_model.provenance.annotation.Annotation``
48-
objects.
49-
50-
"""
51-
pass
52-
53-
54-
@hookspec
55-
def cmdline_tool_annotations(tool):
56-
"""Plugin Hook to add ``Annotation`` entry list to a ``WorkflowTool``.
45+
Run when creating an activity from a ``renku run``.
5746
5847
Args:
59-
tool:
48+
activity: An ``Activity`` object to get annotations for.
6049
6150
Returns:
6251
A list of ``renku.domain_model.provenance.annotation.Annotation``

renku/core/workflow/plan_factory.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from renku.core.constant import RENKU_HOME, RENKU_TMP
3535
from renku.core.interface.client_dispatcher import IClientDispatcher
3636
from renku.core.interface.project_gateway import IProjectGateway
37+
from renku.core.plugin.pluginmanager import get_plugin_manager
3738
from renku.core.util.git import is_path_safe
3839
from renku.core.util.metadata import is_external_file
3940
from renku.core.util.os import get_absolute_path, get_relative_path, is_subpath
@@ -648,9 +649,6 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
648649

649650
client.repository.add(*output_paths)
650651

651-
results = pm.hook.cmdline_tool_annotations(tool=self)
652-
self.annotations = [a for r in results for a in r]
653-
654652
def _path_relative_to_root(self, path) -> str:
655653
"""Make a potentially relative path in a subdirectory relative to the root of the repository."""
656654
absolute_path = get_absolute_path(path, base=self.directory)
@@ -704,7 +702,7 @@ def to_plan(
704702
keywords: Optional[List[str]] = None,
705703
) -> Plan:
706704
"""Return an instance of ``Plan`` based on this factory."""
707-
return Plan(
705+
plan = Plan(
708706
id=self.plan_id,
709707
name=name,
710708
description=description,
@@ -717,6 +715,15 @@ def to_plan(
717715
success_codes=self.success_codes,
718716
)
719717

718+
pm = get_plugin_manager()
719+
720+
plugin_annotations = list(chain.from_iterable(pm.hook.plan_annotations(plan=plan)))
721+
722+
if plugin_annotations:
723+
plan.annotations.extend(plugin_annotations)
724+
725+
return plan
726+
720727

721728
def read_files_list(files_list: Path):
722729
"""Read files list yaml containing name:path pairs."""

renku/domain_model/workflow/plan.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
from renku.core import errors
3232
from renku.core.util.datetime8601 import local_now
33+
from renku.domain_model.provenance.annotation import Annotation
3334
from renku.domain_model.workflow.parameter import CommandInput, CommandOutput, CommandParameter, CommandParameterBase
3435
from renku.infrastructure.database import Persistent
3536

@@ -135,6 +136,8 @@ def is_derivation(self) -> bool:
135136
class Plan(AbstractPlan):
136137
"""Represent a `renku run` execution template."""
137138

139+
annotations: List[Annotation] = list()
140+
138141
def __init__(
139142
self,
140143
*,
@@ -151,12 +154,14 @@ def __init__(
151154
project_id: Optional[str] = None,
152155
outputs: Optional[List[CommandOutput]] = None,
153156
success_codes: Optional[List[int]] = None,
157+
annotations: Optional[List[Annotation]] = None,
154158
):
155159
self.command: str = command
156160
self.inputs: List[CommandInput] = inputs or []
157161
self.outputs: List[CommandOutput] = outputs or []
158162
self.parameters: List[CommandParameter] = parameters or []
159163
self.success_codes: List[int] = success_codes or []
164+
self.annotations: List[Annotation] = annotations or []
160165
super().__init__(
161166
id=id,
162167
description=description,

renku/ui/cli/utils/terminal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def print_description(description):
7070
print_key_value("Command: ", plan.full_command)
7171
print_key_value("Success Codes: ", plan.success_codes)
7272

73+
if plan.annotations:
74+
print_key_value("Annotations:\n", plan.annotations)
75+
7376
if plan.inputs:
7477
print_key("Inputs:")
7578
for run_input in plan.inputs:

renku/ui/cli/workflow.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,30 @@
445445
rename its parameter ``input-1`` to ``my-input`` and set the default of this
446446
parameter to ``other-file.txt`` and set its description.
447447
448+
+-----------------------+------------------------------------------------------+
449+
| Option | Description |
450+
+=======================+======================================================+
451+
| ``-n, --name`` | Plan's name |
452+
+-----------------------+------------------------------------------------------+
453+
| ``-d, --description`` | Plan's description. |
454+
+-----------------------+------------------------------------------------------+
455+
| ``-s, --set`` | Set default value for a parameter. |
456+
| | Accepted format is '<name>=<value>' |
457+
+-----------------------+------------------------------------------------------+
458+
| ``-m, --map`` | Add a new mapping on the Plan. |
459+
| | Accepted format is '<name>=<name or expression>' |
460+
+-----------------------+------------------------------------------------------+
461+
| ``-r, --rename-param``| Rename a parameter. |
462+
| | Accepted format is '<name>="new name"' |
463+
+-----------------------+------------------------------------------------------+
464+
| ``-d,`` | Add a description for a parameter. |
465+
| ``--describe-param`` | Accepted format is '<name>="description"' |
466+
+-----------------------+------------------------------------------------------+
467+
| ``-m, --metadata`` | Path to file containing custom JSON-LD metadata to |
468+
| | be added to the dataset. |
469+
+-----------------------+------------------------------------------------------+
470+
471+
448472
.. cheatsheet::
449473
:group: Workflows
450474
:command: $ renku workflow edit <plan>
@@ -636,7 +660,7 @@
636660

637661
import renku.ui.cli.utils.color as color
638662
from renku.command.echo import ERROR
639-
from renku.command.format.workflow import WORKFLOW_COLUMNS, WORKFLOW_FORMATS
663+
from renku.command.format.workflow import WORKFLOW_COLUMNS, WORKFLOW_FORMATS, json
640664
from renku.command.view_model.activity_graph import ACTIVITY_GRAPH_COLUMNS
641665
from renku.core import errors
642666
from renku.ui.cli.utils.callback import ClickCallback
@@ -837,23 +861,26 @@ def compose(
837861

838862
@workflow.command()
839863
@click.argument("workflow_name", metavar="<name or uuid>", shell_complete=_complete_workflows)
840-
@click.option("--name", metavar="<new name>", help="New name of the workflow")
841-
@click.option("--description", metavar="<new desc>", help="New description of the workflow")
864+
@click.option("-n", "--name", metavar="<new name>", help="New name of the workflow")
865+
@click.option("-d", "--description", metavar="<new desc>", help="New description of the workflow")
842866
@click.option(
867+
"-s",
843868
"--set",
844869
"set_params",
845870
multiple=True,
846871
metavar="<parameter>=<value>",
847872
help="Set default <value> for a <parameter>/add new parameter",
848873
)
849874
@click.option(
875+
"-m",
850876
"--map",
851877
"map_params",
852878
multiple=True,
853879
metavar="<parameter>=<parameter or expression>",
854880
help="New mapping on the workflow",
855881
)
856882
@click.option(
883+
"-r",
857884
"--rename-param",
858885
"rename_params",
859886
multiple=True,
@@ -867,12 +894,24 @@ def compose(
867894
metavar='<parameter>="description"',
868895
help="New description of the workflow",
869896
)
870-
def edit(workflow_name, name, description, set_params, map_params, rename_params, describe_params):
897+
@click.option(
898+
"-m",
899+
"--metadata",
900+
default=None,
901+
type=click.Path(exists=True, dir_okay=False),
902+
help="Custom metadata to be associated with the workflow.",
903+
)
904+
def edit(workflow_name, name, description, set_params, map_params, rename_params, describe_params, metadata):
871905
"""Edit workflow details."""
872906
from renku.command.view_model.plan import PlanViewModel
873907
from renku.command.workflow import edit_workflow_command
874908
from renku.ui.cli.utils.terminal import print_plan
875909

910+
custom_metadata = None
911+
912+
if metadata:
913+
custom_metadata = json.loads(Path(metadata).read_text())
914+
876915
result = (
877916
edit_workflow_command()
878917
.build()
@@ -884,6 +923,7 @@ def edit(workflow_name, name, description, set_params, map_params, rename_params
884923
map_params=map_params,
885924
rename_params=rename_params,
886925
describe_params=describe_params,
926+
custom_metadata=custom_metadata,
887927
)
888928
)
889929
if not result.error:

tests/core/fixtures/core_plugins.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,52 +26,54 @@
2626

2727

2828
@pytest.fixture
29-
def dummy_run_plugin_hook():
29+
def dummy_pre_run_plugin_hook():
3030
"""A dummy hook to be used with the renku run plugin."""
3131
from renku.core.plugin import hookimpl
3232

33-
class _CmdlineToolAnnotations(object):
33+
class _PreRun(object):
3434
"""CmdlineTool Hook implementation namespace."""
3535

36-
@hookimpl
37-
def cmdline_tool_annotations(self, tool):
38-
"""``cmdline_tool_annotations`` hook implementation."""
39-
from renku.domain_model.provenance.annotation import Annotation
36+
called = 0
4037

41-
return [Annotation(id="_:annotation", source="Dummy Cmdline Hook", body="dummy cmdline hook body")]
38+
@hookimpl
39+
def pre_run(self, tool):
40+
"""``pre_run`` hook implementation."""
41+
self.called = 1
4242

43-
return _CmdlineToolAnnotations()
43+
return _PreRun()
4444

4545

4646
@pytest.fixture
47-
def dummy_pre_run_plugin_hook():
48-
"""A dummy hook to be used with the renku run plugin."""
47+
def dummy_plan_plugin_hook():
48+
"""A dummy hook to be used with the renku run plugin for plans."""
4949
from renku.core.plugin import hookimpl
5050

51-
class _PreRun(object):
51+
class _PlanPlugin(object):
5252
"""CmdlineTool Hook implementation namespace."""
5353

5454
called = 0
5555

5656
@hookimpl
57-
def pre_run(self, tool):
58-
"""``cmdline_tool_annotations`` hook implementation."""
59-
self.called = 1
57+
def plan_annotations(self, plan):
58+
"""``plan_annotations`` hook implementation."""
59+
from renku.domain_model.provenance.annotation import Annotation
6060

61-
return _PreRun()
61+
return [Annotation(id="_:annotation", source="Dummy Plan Hook", body="dummy Plan hook body")]
62+
63+
return _PlanPlugin()
6264

6365

6466
@pytest.fixture
65-
def dummy_processrun_plugin_hook():
67+
def dummy_activity_plugin_hook():
6668
"""A dummy hook to be used with the renku run plugin."""
6769
from renku.core.plugin import hookimpl
6870

6971
class _ActivityAnnotations(object):
7072
"""CmdlineTool Hook implementation namespace."""
7173

7274
@hookimpl
73-
def process_run_annotations(self, plan):
74-
"""``process_run_annotations`` hook implementation."""
75+
def activity_annotations(self, activity):
76+
"""``activity_annotations`` hook implementation."""
7577
from renku.domain_model.provenance.annotation import Annotation
7678

7779
return [Annotation(id="_:annotation", source="Dummy Activity Hook", body="dummy Activity hook body")]

0 commit comments

Comments
 (0)