Skip to content

Commit

Permalink
Merge pull request #182 from PixelgenTechnologies/feature/exe-1954
Browse files Browse the repository at this point in the history
Add support for command group aliases
  • Loading branch information
fbdtemme authored Aug 23, 2024
2 parents 9d624d0 + 88657ac commit 6b21513
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 91 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Improved memory usage when aggregating pixel files with precomputed layouts.
- Improved memory usage when aggregating pixel files with precomputed layouts.

### Added

Expand Down
2 changes: 1 addition & 1 deletion src/pixelator/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Copyright © 2023 Pixelgen Technologies AB.
"""

from .main import main_cli, single_cell # noqa: F401
from .main import main_cli, single_cell_mpx # noqa: F401
44 changes: 43 additions & 1 deletion src/pixelator/cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
logger = logging.getLogger("pixelator.cli")


# code snipped obtained from
# code snippet obtained from
# https://stackoverflow.com/questions/47972638/how-can-i-define-the-order-of-click-sub-commands-in-help
# the purpose is to order subcommands in order of addition
class OrderedGroup(click.Group):
Expand All @@ -38,6 +38,48 @@ def list_commands( # type: ignore
return self.commands


# snippet adapted from
# https://stackoverflow.com/questions/46765803/python-click-multiple-group-names
class AliasedOrderedGroup(OrderedGroup):
"""Custom click.Group that supports aliases.
Currently only supports aliases for subgroups.
"""

def group(self, *args, **kwargs):
"""Attach a click group that supports aliases."""

def decorator(f):
aliased_group = []
aliases = kwargs.pop("aliases", [])
main_group = super(AliasedOrderedGroup, self).group(*args, **kwargs)(f)

for alias in aliases:
grp_kwargs = kwargs.copy()
del grp_kwargs["name"]
grp = super(AliasedOrderedGroup, self).group(
alias, *args[1:], **grp_kwargs
)(f)
grp.short_help = "Alias for '{}'".format(main_group.name)
aliased_group.append(grp)

# for all the aliased groups, link to all attributes from the main group
for aliased in aliased_group:
aliased.commands = main_group.commands
aliased.params = main_group.params
aliased.callback = main_group.callback
aliased.epilog = main_group.epilog
aliased.options_metavar = main_group.options_metavar
aliased.add_help_option = main_group.add_help_option
aliased.no_args_is_help = main_group.no_args_is_help
aliased.hidden = main_group.hidden
aliased.deprecated = main_group.deprecated

return main_group

return decorator


def output_option(func):
"""Wrap a Click entrypoint to add the --output option."""

Expand Down
30 changes: 15 additions & 15 deletions src/pixelator/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pixelator.cli.analysis import analysis
from pixelator.cli.annotate import annotate
from pixelator.cli.collapse import collapse
from pixelator.cli.common import OrderedGroup, logger
from pixelator.cli.common import AliasedOrderedGroup, logger
from pixelator.cli.demux import demux
from pixelator.cli.graph import graph
from pixelator.cli.layout import layout
Expand All @@ -28,7 +28,7 @@
from pixelator.utils import click_echo


@click.group(cls=OrderedGroup, name="pixelator")
@click.group(cls=AliasedOrderedGroup, name="pixelator")
@click.version_option(__version__)
@click.option(
"--verbose",
Expand Down Expand Up @@ -96,7 +96,7 @@ def exit():
return 0


@click.group()
@main_cli.group(name="single-cell")
@click.option(
"--list-designs",
is_flag=True,
Expand All @@ -117,24 +117,24 @@ def exit():
callback=list_single_cell_panels,
help="List available panels and exit.",
)
def single_cell():
def single_cell_mpx():
"""Build the click group for single-cell commands."""


# Add single-cell top level command to cli
main_cli.add_command(single_cell)
main_cli.add_command(single_cell_mpx)

# Add single-cell commands
single_cell.add_command(amplicon)
single_cell.add_command(preqc)
single_cell.add_command(adapterqc)
single_cell.add_command(demux)
single_cell.add_command(collapse)
single_cell.add_command(graph)
single_cell.add_command(annotate)
single_cell.add_command(layout)
single_cell.add_command(analysis)
single_cell.add_command(report)
single_cell_mpx.add_command(amplicon)
single_cell_mpx.add_command(preqc)
single_cell_mpx.add_command(adapterqc)
single_cell_mpx.add_command(demux)
single_cell_mpx.add_command(collapse)
single_cell_mpx.add_command(graph)
single_cell_mpx.add_command(annotate)
single_cell_mpx.add_command(layout)
single_cell_mpx.add_command(analysis)
single_cell_mpx.add_command(report)

# Add cli plugins as commands on top level
add_cli_plugins(main_cli)
Expand Down
2 changes: 1 addition & 1 deletion src/pixelator/collapse/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def get_collapsed_fragments_for_component( # noqa: DOC402,DOC404
yield CollapsedFragment(representative_frag, collapsed_fragments, total_reads)


# This code snipped has been obtained from:
# This code snippet has been obtained from:
# https://github.com/CGATOxford/umi-tools
def get_connected_components(
graph: dict[UniqueFragment, list[UniqueFragment]], counts: dict[UniqueFragment, int]
Expand Down
64 changes: 51 additions & 13 deletions src/pixelator/report/common/cli_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,34 @@
dict[str, CommandInfo], dict[str, CommandOptionDict]
]

_SINGLE_CELL_STAGES_TO_CACHE_KEY_MAPPING: dict[SingleCellStageLiteral, str] = {
"amplicon": "pixelator single-cell amplicon",
"preqc": "pixelator single-cell preqc",
"adapterqc": "pixelator single-cell adapterqc",
"demux": "pixelator single-cell demux",
"collapse": "pixelator single-cell collapse",
"graph": "pixelator single-cell graph",
"annotate": "pixelator single-cell annotate",
"layout": "pixelator single-cell layout",
"analysis": "pixelator single-cell analysis",
"report": "pixelator single-cell report",
_SINGLE_CELL_STAGES_TO_CACHE_KEY_MAPPING: dict[
SingleCellStageLiteral, str | list[str]
] = {
"amplicon": [
"pixelator single-cell-mpx amplicon",
"pixelator single-cell amplicon",
],
"preqc": ["pixelator single-cell-mpx preqc", "pixelator single-cell preqc"],
"adapterqc": [
"pixelator single-cell-mpx adapterqc",
"pixelator single-cell adapterqc",
],
"demux": ["pixelator single-cell-mpx demux", "pixelator single-cell demux"],
"collapse": [
"pixelator single-cell-mpx collapse",
"pixelator single-cell collapse",
],
"graph": ["pixelator single-cell-mpx graph", "pixelator single-cell graph"],
"annotate": [
"pixelator single-cell-mpx annotate",
"pixelator single-cell annotate",
],
"layout": ["pixelator single-cell-mpx layout", "pixelator single-cell layout"],
"analysis": [
"pixelator single-cell-mpx analysis",
"pixelator single-cell analysis",
],
"report": ["pixelator single-cell-mpx report", "pixelator single-cell report"],
}


Expand Down Expand Up @@ -88,7 +105,17 @@ def get_stage(
"""
stage = stage.value if isinstance(stage, enum.Enum) else stage
stage_key = _SINGLE_CELL_STAGES_TO_CACHE_KEY_MAPPING[stage]
return self._commands_index.get(stage_key)
res = None

if isinstance(stage_key, list):
for key in stage_key:
res = self._commands_index.get(key)
if res:
break
else:
res = self._commands_index.get(stage_key)

return res

def get_option(
self, stage: SingleCellStage | SingleCellStageLiteral, option: str
Expand All @@ -104,8 +131,19 @@ def get_option(
"""
stage = stage.value if isinstance(stage, enum.Enum) else stage
stage_key = _SINGLE_CELL_STAGES_TO_CACHE_KEY_MAPPING[stage]
stage_dict = self._options_index.get(stage_key)
stage_dict = None

if isinstance(stage_key, list):
for key in stage_key:
stage_dict = self._options_index.get(key)
if stage_dict:
break

else:
stage_dict = self._options_index.get(stage_key)

if stage_dict is None:
stage_key = stage_key[0] if isinstance(stage_key, list) else stage_key
raise KeyError(f"No commandline metadata found for stage: {stage_key}")

return stage_dict[option]
72 changes: 36 additions & 36 deletions src/pixelator/report/common/workdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,33 @@ class SingleCellStage(enum.Enum):
# Duplicating the keys is unavoidable here
WorkdirCacheKey: typing.TypeAlias = typing.Literal[
"metadata",
"single-cell amplicon",
"single-cell preqc",
"single-cell adapterqc",
"single-cell demux",
"single-cell collapse",
"single-cell graph",
"single-cell annotate",
"single-cell annotate dataset",
"single-cell annotate raw_components_metrics",
"single-cell layout",
"single-cell analysis",
"single-cell report",
"single-cell-mpx amplicon",
"single-cell-mpx preqc",
"single-cell-mpx adapterqc",
"single-cell-mpx demux",
"single-cell-mpx collapse",
"single-cell-mpx graph",
"single-cell-mpx annotate",
"single-cell-mpx annotate dataset",
"single-cell-mpx annotate raw_components_metrics",
"single-cell-mpx layout",
"single-cell-mpx analysis",
"single-cell-mpx report",
]

SINGLE_CELL_STAGES_TO_CACHE_KEY_MAPPING: dict[
SingleCellStageLiteral, WorkdirCacheKey
] = {
"amplicon": "single-cell amplicon",
"preqc": "single-cell preqc",
"adapterqc": "single-cell adapterqc",
"demux": "single-cell demux",
"collapse": "single-cell collapse",
"graph": "single-cell graph",
"annotate": "single-cell annotate",
"layout": "single-cell layout",
"analysis": "single-cell analysis",
"report": "single-cell report",
"amplicon": "single-cell-mpx amplicon",
"preqc": "single-cell-mpx preqc",
"adapterqc": "single-cell-mpx adapterqc",
"demux": "single-cell-mpx demux",
"collapse": "single-cell-mpx collapse",
"graph": "single-cell-mpx graph",
"annotate": "single-cell-mpx annotate",
"layout": "single-cell-mpx layout",
"analysis": "single-cell-mpx analysis",
"report": "single-cell-mpx report",
}


Expand Down Expand Up @@ -121,18 +121,18 @@ class PixelatorWorkdir:
# that will be used to search for report files in the workdir folder.
_SEARCH_PATTERNS: Dict[WorkdirCacheKey, str] = {
"metadata": "**/*.meta.json",
"single-cell amplicon": "amplicon/*.report.json",
"single-cell preqc": "preqc/*.report.json",
"single-cell adapterqc": "adapterqc/*.report.json",
"single-cell demux": "demux/*.report.json",
"single-cell collapse": "collapse/*.report.json",
"single-cell graph": "graph/*.report.json",
"single-cell annotate": "annotate/*.report.json",
"single-cell annotate dataset": "annotate/*.dataset.pxl",
"single-cell annotate raw_components_metrics": "annotate/*.raw_components_metrics.csv.gz",
"single-cell layout": "layout/*.report.json",
"single-cell analysis": "analysis/*.report.json",
"single-cell report": "report/*.report.json",
"single-cell-mpx amplicon": "amplicon/*.report.json",
"single-cell-mpx preqc": "preqc/*.report.json",
"single-cell-mpx adapterqc": "adapterqc/*.report.json",
"single-cell-mpx demux": "demux/*.report.json",
"single-cell-mpx collapse": "collapse/*.report.json",
"single-cell-mpx graph": "graph/*.report.json",
"single-cell-mpx annotate": "annotate/*.report.json",
"single-cell-mpx annotate dataset": "annotate/*.dataset.pxl",
"single-cell-mpx annotate raw_components_metrics": "annotate/*.raw_components_metrics.csv.gz",
"single-cell-mpx layout": "layout/*.report.json",
"single-cell-mpx analysis": "analysis/*.report.json",
"single-cell-mpx report": "report/*.report.json",
}

_SEARCH_ANNOTATE_DATASET = "annotate/*.dataset.pxl"
Expand Down Expand Up @@ -412,7 +412,7 @@ def filtered_dataset(
:param cache: Whether to return cached result if available
:return: The path to the filtered dataset if a sample is given, otherwise a list of all datasets
"""
cache_key: WorkdirCacheKey = "single-cell annotate dataset"
cache_key: WorkdirCacheKey = "single-cell-mpx annotate dataset"
func = functools.partial(self._collect_output_files, cache_key)
return self._cached_reports_implementation(cache_key, func, sample, cache)

Expand Down Expand Up @@ -441,6 +441,6 @@ def raw_component_metrics(
:param cache: Whether to return cached results if available
:return: The path to a raw_component_metrics file if a sample is given, otherwise a list of all files
"""
cache_key: WorkdirCacheKey = "single-cell annotate raw_components_metrics"
cache_key: WorkdirCacheKey = "single-cell-mpx annotate raw_components_metrics"
func = functools.partial(self._collect_output_files, cache_key)
return self._cached_reports_implementation(cache_key, func, sample, cache)
5 changes: 0 additions & 5 deletions tests/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ tasks:
PIPELINE_SOURCE_DIR: '{{ .PIPELINE_SOURCE_DIR | default "../nf-core-pixelator" }}'
PIPELINE_RESULTS_DIR: '{{ .PIPELINE_RESULTS_DIR | default "results" }}'
RESUME: '{{ .RESUME | default "false" }}'
requires:
vars: ["PIPELINE_SOURCE_DIR"]
cmds:
- nf_options=(' --save_all ');
[[ {{ .RESUME }} == 'true' ]] && nf_options+=('-resume ');
Expand Down Expand Up @@ -144,9 +142,6 @@ tasks:
PIPELINE_SOURCE_DIR: '{{ .PIPELINE_SOURCE_DIR | default "../nf-core-pixelator" }}'
PIPELINE_BRANCH: '{{ .PIPELINE_BRANCH | default "pixelator-next" }}'
PIPELINE_RESULTS_DIR: '{{ .PIPELINE_RESULTS_DIR | default "results" }}'
requires:
vars:
- PIPELINE_SOURCE_DIR
cmds:
- task: pull-nf-core-pixelator
- task: run-nf-core-pixelator-test-profile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"--output": "."
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"--output": "."
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@
"pixel_count": 6137.0,
"fraction_reads_in_aggregates": 1.0,
"fraction_molecules_in_aggregates": 1.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@
"pixel_count": 7978.0,
"fraction_reads_in_aggregates": 1.0,
"fraction_molecules_in_aggregates": 1.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2628,4 +2628,4 @@
"poly_a_trimmed_read1": null,
"poly_a_trimmed_read2": null,
"sample_id": "pbmcs_unstimulated"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2628,4 +2628,4 @@
"poly_a_trimmed_read1": null,
"poly_a_trimmed_read2": null,
"sample_id": "uropod_control"
}
}
Loading

0 comments on commit 6b21513

Please sign in to comment.