Skip to content
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

Actualize example #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Binary file removed MultiQC_logo.png
Binary file not shown.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ https://gitter.im/ewels/MultiQC

---

![MultiQC](MultiQC_logo.png)
<h1>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/MultiQC/MultiQC/raw/main/docs/images/MultiQC_logo_darkbg.png">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/MultiQC/MultiQC/raw/main/docs/images/MultiQC_logo.png">
<img src="https://github.com/MultiQC/MultiQC/raw/main/docs/images/MultiQC_logo.png" alt="MultiQC">
</picture>
</h1>

---

Expand All @@ -27,8 +33,8 @@ If your module is for something very niche, which no-one else can use, then it's

### Overview of files

* `setup.py`
* Where the `setuptools` plugin hooks are defined. This is where you tell MultiQC where to find your code.
* `pyproject.toml`
* Where the plugin hooks are defined. This is where you tell MultiQC where to find your code.
* This file also defines how your plugin should be installed, including required python packages.
* `example_plugin/`
* Installable Python packages are typically put into a directory with the same name.
Expand All @@ -49,10 +55,10 @@ To use this code, you need to install MultiQC and then your code. For example:

```bash
pip install MultiQC
python setup.py install
pip install .
```

Use `python setup.py develop` if you're actively working on the code - then you don't need to rerun the installation every time you make an edit _(though you still do if you change anything in `setup.py`)_.
Use `pip install -e .` if you're actively working on the code - then you don't need to rerun the installation every time you make an edit _(though you still do if you change anything in `pyproject.toml`)_.

### Disabling the plugin

Expand Down
8 changes: 5 additions & 3 deletions example_plugin/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import click

# Sets config.kwargs['disable_plugin'] to True if specified (will be False otherwise)
disable_plugin = click.option('--disable-example-plugin', 'disable_plugin',
is_flag = True,
help = "Disable the Example MultiQC plugin on this run"
disable_plugin = click.option(
"--disable-example-plugin",
"disable_plugin",
is_flag=True,
help="Disable the Example MultiQC plugin on this run",
)
48 changes: 23 additions & 25 deletions example_plugin/custom_code.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,57 @@
#!/usr/bin/env python
""" MultiQC example plugin functions
"""MultiQC example plugin functions

We can add any custom Python functions here and call them
using the setuptools plugin hooks.
"""

from __future__ import print_function
from pkg_resources import get_distribution
import logging

from multiqc.utils import report, util_functions, config
from multiqc import config, report
import importlib_metadata
import logging

# Initialise the main MultiQC logger
log = logging.getLogger('multiqc')

# Save this plugin's version number (defined in setup.py) to the MultiQC config
config.example_plugin_version = get_distribution("multiqc_example_plugin").version
log = logging.getLogger("multiqc")


# Add default config options for the things that are used in MultiQC_NGI
def example_plugin_execution_start():
""" Code to execute after the config files and
"""Code to execute after the config files and
command line flags have been parsedself.

This setuptools hook is the earliest that will be able
to use custom command line flags.
"""

# Halt execution if we've disabled the plugin
if config.kwargs.get('disable_plugin', True):
if config.kwargs.get("disable_plugin", True):
return None

log.info("Running Example MultiQC Plugin v{}".format(config.example_plugin_version))
# Plugin's version number defined in pyproject.toml:
version = importlib_metadata.version("multiqc_example_plugin")
log.info("Running Example MultiQC Plugin v{}".format(version))

# Add to the main MultiQC config object.
# User config files have already been loaded at this point
# so we check whether the value is already set. This is to avoid
# clobbering values that have been customised by users.

# Add to the search patterns used by modules
if 'my_example/key_value_pairs' not in config.sp:
config.update_dict( config.sp, { 'my_example/key_value_pairs': { 'fn': 'my_plugin_output.tsv' } } )
if 'my_example/plot_data' not in config.sp:
config.update_dict( config.sp, { 'my_example/plot_data': { 'fn': 'my_plugin_plotdata.tsv' } } )
if "my_example/key_value_pairs" not in config.sp:
config.update_dict(config.sp, {"my_example/key_value_pairs": {"fn": "my_plugin_output.tsv"}})
if "my_example/plot_data" not in config.sp:
config.update_dict(config.sp, {"my_example/plot_data": {"fn": "my_plugin_plotdata.tsv"}})

# Some additional filename cleaning
config.fn_clean_exts.extend([
'.my_tool_extension',
'.removeMetoo'
])
config.fn_clean_exts.extend([".my_tool_extension", ".removeMetoo"])

# Ignore some files generated by the custom pipeline
config.fn_ignore_paths.extend([
'*/my_awesome_pipeline/fake_news/*',
'*/my_awesome_pipeline/red_herrings/*',
'*/my_awesome_pipeline/noisy_data/*',
'*/my_awesome_pipeline/rubbish/*'
])
config.fn_ignore_paths.extend(
[
"*/my_awesome_pipeline/fake_news/*",
"*/my_awesome_pipeline/red_herrings/*",
"*/my_awesome_pipeline/noisy_data/*",
"*/my_awesome_pipeline/rubbish/*",
]
)
4 changes: 2 additions & 2 deletions example_plugin/modules/my_example/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import absolute_import

from .my_example import MultiqcModule

__all__ = ["MultiqcModule"]
90 changes: 44 additions & 46 deletions example_plugin/modules/my_example/my_example.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,45 @@
#!/usr/bin/env python
"""MultiQC example plugin module"""

""" MultiQC example plugin module """

from __future__ import print_function
from collections import OrderedDict
import logging

from multiqc import config
from multiqc.plots import linegraph
from multiqc.modules.base_module import BaseMultiqcModule
from multiqc.base_module import BaseMultiqcModule
from multiqc.plots.plotly.line import LinePlotConfig

# Initialise the main MultiQC logger
log = logging.getLogger('multiqc')
log = logging.getLogger("multiqc")

class MultiqcModule(BaseMultiqcModule):

class MultiqcModule(BaseMultiqcModule):
def __init__(self):

# Halt execution if we've disabled the plugin
if config.kwargs.get('disable_plugin', True):
return None
if config.kwargs.get("disable_plugin", True):
return

# Initialise the parent module Class object
super(MultiqcModule, self).__init__(
name = 'My Example',
target = "my_example",
anchor = 'my_example',
href = 'https://github.com/MultiQC/example-plugin',
info = " is an example module to show how the MultQC pluginm system works."
name="My Example",
target="my_example",
anchor="my_example",
href="https://github.com/MultiQC/example-plugin",
info=" is an example module to show how the MultQC plugin system works.",
)

# Find and load any input files for this module
self.my_example_data = dict()
for f in self.find_log_files('my_example/key_value_pairs'):
self.my_example_data[f['s_name']] = dict()
for l in f['f'].splitlines():
key, value = l.split(None, 1)
self.my_example_data[f['s_name']][key] = value
for f in self.find_log_files("my_example/key_value_pairs"):
self.my_example_data[f["s_name"]] = dict()
for line in f["f"].splitlines():
key, value = line.split(None, 1)
self.my_example_data[f["s_name"]][key] = value

self.my_example_plot_data = dict()
for f in self.find_log_files('my_example/plot_data'):
self.my_example_plot_data[f['s_name']] = dict()
for l in f['f'].splitlines():
key, value = l.split(None, 1)
self.my_example_plot_data[f['s_name']][float(key)] = float(value)
for f in self.find_log_files("my_example/plot_data"):
self.my_example_plot_data[f["s_name"]] = dict()
for line in f["f"].splitlines():
key, value = line.split(None, 1)
self.my_example_plot_data[f["s_name"]][float(key)] = float(value)

# Filter out samples matching ignored sample names
self.my_example_data = self.ignore_samples(self.my_example_data)
Expand All @@ -56,32 +52,34 @@ def __init__(self):
log.info("Found {} reports".format(len(self.my_example_data)))

# Write parsed report data to a file
self.write_data_file(self.my_example_data, 'multiqc_my_example')
self.write_data_file(self.my_example_data, "multiqc_my_example")

# Add a number to General Statistics table
headers = OrderedDict()
headers['data_key'] = {
'title': '# Things',
'description': 'An important number showing something useful.',
'min': 0,
'scale': 'RdYlGn-rev',
'format': '{:,.0f}'
headers = {
"data_key": {
"title": "# Things",
"description": "An important number showing something useful.",
"min": 0,
"scale": "RdYlGn-rev",
"format": "{:,.0f}",
}
}
self.general_stats_addcols(self.my_example_data, headers)

# Create line plot
pconfig = {
'id': 'my_example_plot',
'title': 'My Example: An example plot',
'ylab': '# Other Things',
'xlab': 'Some Values'
}
line_plot_html = linegraph.plot(self.my_example_plot_data, pconfig)
line_plot_html = linegraph.plot(
self.my_example_plot_data,
LinePlotConfig(
id="my_example_plot",
title="My Example: An example plot",
ylab="# Other Things",
xlab="Some Values",
),
)

# Add a report section with the line plot
self.add_section(
description = 'This plot shows some numbers, and how they relate.',
helptext = '''
description="This plot shows some numbers, and how they relate.",
helptext="""
This longer description explains what exactly the numbers mean
and supports markdown formatting. This means that we can do _this_:

Expand All @@ -91,6 +89,6 @@ def __init__(self):

Doesn't matter if this is copied from documentation - makes it
easier for people to find quickly.
''',
plot = line_plot_html
""",
plot=line_plot_html,
)
56 changes: 56 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "multiqc_example_plugin"
version = "0.2"
authors = [
{name = "Phil Ewels", email = "[email protected]"},
{name = "Vlad Savelyev", email = "[email protected]"},
]
description = "Example MultiQC plugin"
readme = "README.md"
license = {file = "LICENSE"}
keywords = ["bioinformatics", "biology", "sequencing", "NGS", "next generation sequencing", "quality control"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: Web Environment",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Natural Language :: English",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: JavaScript",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Bio-Informatics",
"Topic :: Scientific/Engineering :: Visualization",
]

[tool.setuptools]
packages = ["example_plugin"]

[project.urls]
Repository = "https://github.com/MultiQC/example-plugin"

[project.entry-points."multiqc.modules.v1"]
my_example = "example_plugin.modules.my_example:MultiqcModule"

[project.entry-points."multiqc.cli_options.v1"]
disable_plugin = "example_plugin.cli:disable_plugin"

[project.entry-points."multiqc.hooks.v1"]
execution_start = "example_plugin.custom_code:example_plugin_execution_start"

[tool.ruff]
line-length = 120
target-version = "py312"
ignore-init-module-imports = true
ignore = ["F401"] # unused-import

[tool.mypy]
check_untyped_defs = true
plugins = ["pydantic.mypy"]
55 changes: 3 additions & 52 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,7 @@
#!/usr/bin/env python
"""
Example plugin for MultiQC, showing how to structure code
and plugin hooks to work effectively with the main MultiQC code.

For more information about MultiQC, see http://multiqc.info
The project is configured in pyproject.toml. This script is left for the editable installation.
"""

from setuptools import setup, find_packages

version = '0.1'
from setuptools import setup # type: ignore

setup(
name = 'multiqc_example_plugin',
version = version,
author = 'Phil Ewels',
author_email = '[email protected]',
description = "Example MultiQC plugin",
long_description = __doc__,
keywords = 'bioinformatics',
url = 'https://github.com/MultiQC/example-plugin',
download_url = 'https://github.com/MultiQC/example-plugin/releases',
license = 'MIT',
packages = find_packages(),
include_package_data = True,
install_requires = [
'multiqc'
],
entry_points = {
'multiqc.modules.v1': [
'my_example = example_plugin.modules.my_example:MultiqcModule',
],
'multiqc.cli_options.v1': [
'disable_plugin = example_plugin.cli:disable_plugin'
],
'multiqc.hooks.v1': [
'execution_start = example_plugin.custom_code:example_plugin_execution_start'
]
},
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Operating System :: Unix',
'Programming Language :: Python',
'Programming Language :: JavaScript',
'Topic :: Scientific/Engineering',
'Topic :: Scientific/Engineering :: Bio-Informatics',
'Topic :: Scientific/Engineering :: Visualization',
],
)
setup()