Skip to content

Commit

Permalink
feat: support for report plugins (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneskoester authored Feb 16, 2024
1 parent f7f562b commit 91da3d3
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 1 deletion.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ cd snakemake-storage-plugin-myfancystorage
# Scaffold the project as a snakemake executor plugin
poetry scaffold-snakemake-storage-plugin

# Next, edit the scaffolded code according to your needs, and publish
# the resulting plugin into a github repository. The scaffold command also
# creates github actions workflows that will immediately start to check and test
# the plugin.
```

## Scaffolding a report plugin

Lets assume that you want to create a snakemake report plugin with the name `snakemake-report-plugin-myfancyreport`.

```bash

# Install poetry plugin via
poetry self add poetry-snakemake-plugin

# Create a new poetry project via
poetry new snakemake-report-plugin-myfancyreport

cd snakemake-storage-plugin-myfancyreport

# Scaffold the project as a snakemake executor plugin
poetry scaffold-snakemake-report-plugin

# Next, edit the scaffolded code according to your needs, and publish
# the resulting plugin into a github repository. The scaffold command also
# creates github actions workflows that will immediately start to check and test
Expand Down
5 changes: 5 additions & 0 deletions poetry_snakemake_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from poetry_snakemake_plugin.executor_plugins import (
ScaffoldSnakemakeExecutorPluginCommand,
)
from poetry_snakemake_plugin.report_plugins import ScaffoldSnakemakeReportPluginCommand
from poetry_snakemake_plugin.storage_plugins import (
ScaffoldSnakemakeStoragePluginCommand,
)
Expand All @@ -18,3 +19,7 @@ def activate(self, application):
ScaffoldSnakemakeStoragePluginCommand.name,
ScaffoldSnakemakeStoragePluginCommand,
)
application.command_loader.register_factory(
ScaffoldSnakemakeReportPluginCommand.name,
ScaffoldSnakemakeReportPluginCommand,
)
21 changes: 21 additions & 0 deletions poetry_snakemake_plugin/report_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pathlib import Path
from typing import List

from poetry_snakemake_plugin.common import ScaffoldSnakemakePluginCommandBase


class ScaffoldSnakemakeReportPluginCommand(ScaffoldSnakemakePluginCommandBase):
name = "scaffold-snakemake-report-plugin"
description = (
"Scaffolds a snakemake report plugin by adding recommended "
"dependencies and code snippets."
)

def get_templates(self, module_path: Path, tests_path: Path) -> List[str]:
return [
("report-plugins/init.py", module_path / "__init__.py"),
("report-plugins/tests.py", tests_path / "tests.py"),
]

def get_plugin_type(self) -> str:
return "report"
56 changes: 56 additions & 0 deletions poetry_snakemake_plugin/templates/report-plugins/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from dataclasses import dataclass, field
from typing import Optional

from snakemake_interface_common.exceptions import WorkflowError # noqa: F401
from snakemake_interface_report_plugins.reporter import ReporterBase
from snakemake_interface_report_plugins.settings import ReportSettingsBase


# Optional:
# Define additional settings for your reporter.
# They will occur in the Snakemake CLI as --report-<reporter-name>-<param-name>
# Omit this class if you don't need any.
# Make sure that all defined fields are Optional (or bool) and specify a default value
# of None (or False) or anything else that makes sense in your case.
@dataclass
class ReportSettings(ReportSettingsBase):
myparam: Optional[int] = field(
default=None,
metadata={
"help": "Some help text",
# Optionally request that setting is also available for specification
# via an environment variable. The variable will be named automatically as
# SNAKEMAKE_REPORT_<reporter-name>_<param-name>, all upper case.
# This mechanism should ONLY be used for passwords and usernames.
# For other items, we rather recommend to let people use a profile
# for setting defaults
# (https://snakemake.readthedocs.io/en/stable/executing/cli.html#profiles).
"env_var": False,
# Optionally specify a function that parses the value given by the user.
# This is useful to create complex types from the user input.
"parse_func": ...,
# If a parse_func is specified, you also have to specify an unparse_func
# that converts the parsed value back to a string.
"unparse_func": ...,
# Optionally specify that setting is required when the reporter is in use.
"required": True,
},
)


# Required:
# Implementation of your reporter
class Reporter(ReporterBase):
def __post_init__(self):
# initialize additional attributes
# Do not overwrite the __init__ method as this is kept in control of the base
# class in order to simplify the update process.
# See https://github.com/snakemake/snakemake-interface-report-plugins/snakemake_interface_report_plugins/reporter.py # noqa: E501
# for attributes of the base class.
# In particular, the settings of above ReportSettings class are accessible via
# self.settings.
...

def render(self):
# Render the report, using attributes of the base class.
...
16 changes: 16 additions & 0 deletions poetry_snakemake_plugin/templates/report-plugins/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Optional
import snakemake.common.tests
from snakemake_interface_report_plugins.settings import ReportSettingsBase


# Check out the base classes found here for all possible options and methods:
# https://github.com/snakemake/snakemake/blob/main/snakemake/common/tests/__init__.py
class TestWorkflowsBase(snakemake.common.tests.TestReportBase):
__test__ = True

def get_reporter(self) -> str:
return "{{plugin_name}}"

def get_report_settings(self) -> Optional[ReportSettingsBase]:
# instantiate ReportSettings of this plugin as appropriate
...
1 change: 1 addition & 0 deletions poetry_snakemake_plugin/templates/storage-plugins/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def get_query(self, tmp_path) -> str:
...

def get_query_not_existing(self, tmp_path) -> str:
# Return a query that is not present in the storage.
...

def get_storage_provider_cls(self) -> Type[StorageProviderBase]:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ build-backend = "poetry.core.masonry.api"


[tool.poetry.plugins."poetry.application.plugin"]
scaffold-snakemake-executor-plugin = "poetry_snakemake_plugin:ScaffoldSnakemakeExecutorPlugin"
scaffold-snakemake-plugin = "poetry_snakemake_plugin:ScaffoldSnakemakeExecutorPlugin"
13 changes: 13 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ def test_new_snakemake_storage_plugin(tmp_path):
os.chdir(orig_dir)


def test_new_snakemake_report_plugin(tmp_path):
orig_dir = os.getcwd()
os.chdir(tmp_path)

run_subcommand("new", "snakemake-report-plugin-test")
os.chdir("snakemake-report-plugin-test")
run_subcommand("scaffold-snakemake-report-plugin")

run_subcommand("run", "black", "--check", "--diff", ".")
run_subcommand("run", "flake8")
os.chdir(orig_dir)


def run_subcommand(*args):
cmd = ["poetry"]
cmd.extend(args)
Expand Down

0 comments on commit 91da3d3

Please sign in to comment.