Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hermes.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
sources = ["cff"]

[curate]
method = "software_card"
plugin = "software_card"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ classifiers = [
]
requires-python = ">=3.11"
dependencies = [
"hermes @ git+https://github.com/softwarepub/hermes.git@develop",
"hermes @ git+https://github.com/softwarepub/hermes.git@refactor/data-model",
"software-card-policies @ git+https://github.com/softwarepub/software-card-policies.git",
]

Expand Down
58 changes: 41 additions & 17 deletions src/hermes_plugin_software_card/curate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"""Module containing the Software CaRD curation plugin for HERMES."""

import json
from pathlib import Path

from hermes.commands.curate.base import BaseCuratePlugin
from hermes.commands.base import HermesCommand
from hermes.commands.curate.base import HermesCuratePlugin
from hermes.model import SoftwareMetadata
from hermes.model.hermes_cache import HermesCacheManager
from software_card_policies.config import Config
from software_card_policies.data_model import (
make_shacl_graph,
Expand All @@ -20,12 +22,12 @@
from hermes_plugin_software_card import environment


class SoftwareCaRDCuratePlugin(BaseCuratePlugin):
class SoftwareCaRDCuratePlugin(HermesCuratePlugin):
"""Software CaRD curation plugin."""

def __init__(self, command, ctx):
def __init__(self):
"""Initialize the plugin."""
super().__init__(command, ctx)
super().__init__()
self._data_graph = None
self._shacl_graph = None
self._conforms = False
Expand Down Expand Up @@ -65,14 +67,34 @@
}
}

def prepare(self):
def __call__(
self, command: HermesCommand, metadata: SoftwareMetadata

Check failure on line 71 in src/hermes_plugin_software_card/curate.py

View workflow job for this annotation

GitHub Actions / ruff-check-and-format-check

ruff (ARG002)

src/hermes_plugin_software_card/curate.py:71:15: ARG002 Unused method argument: `command`

Check failure on line 71 in src/hermes_plugin_software_card/curate.py

View workflow job for this annotation

GitHub Actions / ruff-check-and-format-check

ruff (ARG002)

src/hermes_plugin_software_card/curate.py:71:15: ARG002 Unused method argument: `command`
Comment thread
zyzzyxdonta marked this conversation as resolved.
Outdated
) -> SoftwareMetadata:
"""Entry point of the callable.

This method runs the main logic of the plugin. It calls the other methods of the
object in the correct order. Depending on the result of
``is_publication_approved`` either the valid metadata or a new, empty
``SoftwareMetadata`` object is returned.
"""
self.prepare(metadata)
self.validate()
self.create_report(metadata)

if not self.is_publication_approved():
return SoftwareMetadata()

return metadata

def prepare(self, metadata: SoftwareMetadata):
"""Prepare the validation.

The metadata given in the context is parsed as an RDF graph and then validated
using the Software CaRD validation.
"""
text = json.dumps(self.ctx.get_data()["curate"])
self._data_graph = read_rdf_resource(format="json-ld", data=text)
self._data_graph = read_rdf_resource(
format="json-ld", data=json.dumps(metadata.ld_value)
)
self._shacl_graph = make_shacl_graph(Config.from_dict(self._validation_config))

def validate(self):
Expand All @@ -81,8 +103,17 @@
self._conforms = conforms
self._validation_graph = validation_graph

def create_report(self):
"""Create basic text report."""
def create_report(self, metadata: SoftwareMetadata):

Check failure on line 106 in src/hermes_plugin_software_card/curate.py

View workflow job for this annotation

GitHub Actions / ruff-check-and-format-check

ruff (ARG002)

src/hermes_plugin_software_card/curate.py:106:29: ARG002 Unused method argument: `metadata`

Check failure on line 106 in src/hermes_plugin_software_card/curate.py

View workflow job for this annotation

GitHub Actions / ruff-check-and-format-check

ruff (ARG002)

src/hermes_plugin_software_card/curate.py:106:29: ARG002 Unused method argument: `metadata`
Comment thread
zyzzyxdonta marked this conversation as resolved.
Outdated
"""Create validation report.

This creates the report both as a machine-readble JSON-LD file, and prints the
URL to the Software CaRD web app to the screen.
"""
ctx = HermesCacheManager()
validation_file = ctx.cache_dir / "curate" / "validation.json"
validation_file.parent.mkdir(exist_ok=True, parents=True)
self._validation_graph.serialize(validation_file, format="json-ld")
Comment on lines +111 to +114

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the HermesCacheManager the way it was intended to be used?
(validation_file would have to be moved, but is there another reason?)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the intended way? Do you mean with a SoftwareMetadata object?

We tried that but failed because the validation graph is a list and SoftwareMetadata can only be initialized with a dict. It looks like this:

validation.json.txt

The interesting bit in this case is the validation report in ll. 443 - 454. The rest is just SHACL copied into the graph.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried working around it, taking a concise bounded description:

        report_refs = list(
            self._validation_graph.subjects(
                predicate=RDF.type, object=SH.ValidationReport
            )
        )
        assert len(report_refs) == 1
        report_ref = report_refs[0]
        self._validation_graph.cbd(report_ref).serialize(
            validation_file, format="json-ld"
        )

But this also creates a list:

[
  {
    "@id": "_:Nd5d1172d2d2d46829819e6fdce9722a9",
    "@type": [
      "http://www.w3.org/ns/shacl#ValidationReport"
    ],
    "http://www.w3.org/ns/shacl#conforms": [
      {
        "@type": "http://www.w3.org/2001/XMLSchema#boolean",
        "@value": true
      }
    ]
  }
]

Maybe the JSON-LD serializer in RDFlib always does this.

I could return the single element from that list. But that won't work for failed validations because in those cases, the report is split into multiple objects rather than nested:

validation-failed.json.txt


self._report = create_report(self._validation_graph)
Comment thread
zyzzyxdonta marked this conversation as resolved.
Outdated
if self._environment is None:
print("Software CaRD plugin not running in CI environment.")
Expand All @@ -95,10 +126,3 @@
def is_publication_approved(self) -> bool:
"""Decide whether the publication of the software is approved."""
return self._conforms

def process_decision_positive(self):
"""Write the given metadata into the curate directory."""
curate_output = Path(self.ctx.get_cache("curate", self.ctx.hermes_name))
Path.mkdir(curate_output.parent)
with open(curate_output, "w") as curate_output_fh:
json.dump(self.ctx.get_data(), curate_output_fh)
Loading