-
Notifications
You must be signed in to change notification settings - Fork 8
Create more elaborate base class for curation plugins #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
25a669f
5fd1319
fb03e4e
d8ce5f8
72995e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,9 @@ hermes-marketplace = "hermes.commands.marketplace:main" | |
| cff = "hermes.commands.harvest.cff:CffHarvestPlugin" | ||
| codemeta = "hermes.commands.harvest.codemeta:CodeMetaHarvestPlugin" | ||
|
|
||
| [project.entry-points."hermes.curate"] | ||
| accept = "hermes.commands.curate.accept:AcceptCuratePlugin" | ||
|
Comment on lines
+61
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we've made a decision to let the default plugins for each command live in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean directly in hermes, i.e.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, as a separate repository you mean. I also missed the first "don't" 🤦🏻♂️ |
||
|
|
||
| [project.entry-points."hermes.deposit"] | ||
| file = "hermes.commands.deposit.file:FileDepositPlugin" | ||
| invenio = "hermes.commands.deposit.invenio:InvenioDepositPlugin" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR), 2025 Helmholtz-Zentrum Dresden-Rossendorf (HZDR) | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # SPDX-FileContributor: Michael Meinel | ||
| # SPDX-FileContributor: David Pape | ||
|
|
||
| import os | ||
| import shutil | ||
|
|
||
| from hermes.commands.curate.base import BaseCuratePlugin | ||
|
|
||
|
|
||
| class AcceptCuratePlugin(BaseCuratePlugin): | ||
| """Accept plugin for the curation step. | ||
| This plugin creates a positive curation result, i.e. it accepts the produced | ||
| metadata as correct and lets the execution continue without human intervention. It | ||
| also copies the metadata produced in the process step to the "curate" directory. | ||
| """ | ||
|
Comment on lines
+14
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a plugin and not the default implementation in the base plugin?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me, this felt like the right way to do this. Accepting everything without check is one curation strategy, and this strategy is the default choice.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could make the base class an |
||
|
|
||
| def get_decision(self): | ||
| """Simulate positive curation result.""" | ||
| return True | ||
|
|
||
| def process_decision_positive(self): | ||
| """In case of positive curation result, copy files to next step.""" | ||
| process_output = ( | ||
| self.ctx.hermes_dir / "process" / (self.ctx.hermes_name + ".json") | ||
| ) | ||
|
|
||
| os.makedirs(self.ctx.hermes_dir / "curate", exist_ok=True) | ||
| shutil.copy( | ||
| process_output, | ||
| self.ctx.hermes_dir / "curate" / (self.ctx.hermes_name + ".json"), | ||
| ) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,28 +1,106 @@ | ||||||
| # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR) | ||||||
| # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR), 2025 Helmholtz-Zentrum Dresden-Rossendorf (HZDR) | ||||||
| # | ||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||
|
|
||||||
| # SPDX-FileContributor: Michael Meinel | ||||||
| # SPDX-FileContributor: David Pape | ||||||
|
|
||||||
| import argparse | ||||||
| import os | ||||||
| import shutil | ||||||
| import json | ||||||
| import sys | ||||||
|
|
||||||
| from pydantic import BaseModel | ||||||
|
|
||||||
| from hermes.commands.base import HermesCommand | ||||||
| from hermes.commands.base import HermesCommand, HermesPlugin | ||||||
| from hermes.model.context import CodeMetaContext | ||||||
| from hermes.model.errors import HermesValidationError | ||||||
| from hermes.model.path import ContextPath | ||||||
|
|
||||||
|
|
||||||
| class _CurateSettings(BaseModel): | ||||||
| """Generic deposition settings.""" | ||||||
| """Generic curation settings.""" | ||||||
|
|
||||||
| pass | ||||||
| #: Parameter by which the plugin is selected. By default, the accept plugin is used. | ||||||
| method: str = "accept" | ||||||
|
Comment on lines
+23
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above re plugin. |
||||||
|
|
||||||
|
|
||||||
| class BaseCuratePlugin(HermesPlugin): | ||||||
| """Base class for curation plugins. | ||||||
|
|
||||||
| Objects of this class are callables. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be documented in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I'll move it 😄 |
||||||
| """ | ||||||
|
|
||||||
| def __init__(self, command, ctx): | ||||||
| self.command = command | ||||||
| self.ctx = ctx | ||||||
|
|
||||||
| def __call__(self, command: HermesCommand) -> None: | ||||||
| """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 ``get_decision`` the | ||||||
| corresponding ``process_decision_*()`` method is called, based on the curation | ||||||
| decision. | ||||||
| """ | ||||||
| self.prepare() | ||||||
| self.validate() | ||||||
| self.create_report() | ||||||
| if self.get_decision(): | ||||||
| self.process_decision_positive() | ||||||
| else: | ||||||
| self.process_decision_negative() | ||||||
|
|
||||||
| def prepare(self): | ||||||
| """Prepare the plugin. | ||||||
|
|
||||||
| This method may be used to perform preparatory tasks such as configuration | ||||||
| checks, token permission checks, loading of resources, etc. | ||||||
| """ | ||||||
| pass | ||||||
|
|
||||||
| def validate(self): | ||||||
| """Validate the metadata. | ||||||
|
|
||||||
| This method performs the validation of the metadata from the data model. | ||||||
| """ | ||||||
| pass | ||||||
|
|
||||||
| def create_report(self): | ||||||
| """Create a curation report. | ||||||
|
|
||||||
| This method is responsible for creating any number of reports about the curation | ||||||
| process. These reports may be machine-readable, human-readable, or both. | ||||||
| """ | ||||||
| pass | ||||||
|
|
||||||
| def get_decision(self) -> bool: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Better naming, alternatively |
||||||
| """Return the publication decision made through the curation process. | ||||||
|
|
||||||
| If publication is allowed, this method must return ``True``. By default, | ||||||
| ``False`` is returned. | ||||||
| """ | ||||||
| return False | ||||||
|
|
||||||
| def process_decision_positive(self): | ||||||
| """Process a positive curation decision. | ||||||
|
|
||||||
| This method is called if a positive publication decision was made in the | ||||||
| curation process. | ||||||
| """ | ||||||
| pass | ||||||
|
|
||||||
| def process_decision_negative(self): | ||||||
| """Process a negative curation decision. | ||||||
|
|
||||||
| This method is called if a negative publication decision was made in the | ||||||
| curation process. By default, a ``RuntimeError`` is raised, halting the | ||||||
| execution. | ||||||
| """ | ||||||
| raise RuntimeError("Curation declined further processing") | ||||||
|
|
||||||
|
|
||||||
| class HermesCurateCommand(HermesCommand): | ||||||
| """ Curate the unified metadata before deposition. """ | ||||||
| """Curate the processed metadata before deposition.""" | ||||||
|
|
||||||
| command_name = "curate" | ||||||
| settings_class = _CurateSettings | ||||||
|
|
@@ -31,17 +109,31 @@ def init_command_parser(self, command_parser: argparse.ArgumentParser) -> None: | |||||
| pass | ||||||
|
|
||||||
| def __call__(self, args: argparse.Namespace) -> None: | ||||||
|
|
||||||
| self.log.info("# Metadata curation") | ||||||
| self.args = args | ||||||
| plugin_name = self.settings.method | ||||||
|
|
||||||
| ctx = CodeMetaContext() | ||||||
| process_output = ctx.hermes_dir / 'process' / (ctx.hermes_name + ".json") | ||||||
|
|
||||||
| if not process_output.is_file(): | ||||||
| process_output = ctx.get_cache("process", ctx.hermes_name) | ||||||
| if not process_output.exists(): | ||||||
| self.log.error( | ||||||
| "No processed metadata found. Please run `hermes process` before curation." | ||||||
| ) | ||||||
| sys.exit(1) | ||||||
|
|
||||||
| os.makedirs(ctx.hermes_dir / 'curate', exist_ok=True) | ||||||
| shutil.copy(process_output, ctx.hermes_dir / 'curate' / (ctx.hermes_name + '.json')) | ||||||
| curate_path = ContextPath("curate") | ||||||
| with open(process_output) as process_output_fh: | ||||||
| ctx.update(curate_path, json.load(process_output_fh)) | ||||||
|
|
||||||
| try: | ||||||
| plugin_func = self.plugins[plugin_name](self, ctx) | ||||||
|
|
||||||
| except KeyError as e: | ||||||
| self.log.error("Plugin '%s' not found.", plugin_name) | ||||||
| self.errors.append(e) | ||||||
|
|
||||||
| try: | ||||||
| plugin_func(self) | ||||||
|
|
||||||
| except HermesValidationError as e: | ||||||
| self.log.error("Error while executing %s: %s", plugin_name, e) | ||||||
| self.errors.append(e) | ||||||
Uh oh!
There was an error while loading. Please reload this page.