Skip to content
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
27 changes: 24 additions & 3 deletions src/cabinetry/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import logging
import pathlib
from typing import Any, List, Optional, Tuple
from typing import Any, List, Literal, Optional, Tuple

import click
import yaml
Expand Down Expand Up @@ -48,15 +48,36 @@ def cabinetry() -> None:
default="uproot",
help="backend for histogram production (default: uproot)",
)
def templates(config: io.TextIOWrapper, method: str) -> None:
@click.option(
"--input_type",
type=click.Choice(["ntuple", "histogram"]),
help="type of input files (overrides automatic detection)",
)
def templates(
config: io.TextIOWrapper,
method: str,
input_type: Optional[Literal["ntuple", "histogram"]],
) -> None:
"""Produces template histograms.

CONFIG: path to cabinetry configuration file
"""
_set_logging()
cabinetry_config = yaml.safe_load(config)
cabinetry_configuration.validate(cabinetry_config)
cabinetry_templates.build(cabinetry_config, method=method)

if input_type is None:
# detect input type
input_type = (
"ntuple"
if cabinetry_configuration._input_is_ntuple(cabinetry_config)
else "histogram"
)

if input_type == "ntuple":
cabinetry_templates.build(cabinetry_config, method=method)
else:
cabinetry_templates.collect(cabinetry_config, method=method)


@click.command()
Expand Down
16 changes: 16 additions & 0 deletions src/cabinetry/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,19 @@ def region_dict(config: Dict[str, Any], region_name: str) -> Dict[str, Any]:
if len(regions) > 1:
log.error(f"found more than one region with name {region_name}")
return regions[0]


def _input_is_ntuple(config: Dict[str, Any]) -> bool:
"""Checks if input files for given config are ntuples or histograms.

This relies on the "Tree" property of samples, which is required for ntuple inputs.
In case this property is used for histogram inputs (where it does nothing), this
function will mistakenly identify this as ntuple input type.

Args:
config (Dict[str, Any]): cabinetry configuration file

Returns:
bool: whether inputs are of ntuple type
"""
return all([sample.get("Tree") is not None for sample in config["Samples"]])
28 changes: 25 additions & 3 deletions tests/cli/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ def test_cabinetry():


# using autospec to catch changes in public API
@mock.patch("cabinetry.templates.collect", autospec=True)
@mock.patch("cabinetry.templates.build", autospec=True)
@mock.patch(
"cabinetry.configuration._input_is_ntuple",
side_effect=[True, False],
autospec=True,
)
@mock.patch("cabinetry.configuration.validate", autospec=True)
def test_templates(mock_validate, mock_create_histograms, cli_helpers, tmp_path):
def test_templates(
mock_validate,
mock_is_ntuple,
mock_create_histograms,
mock_collect_histograms,
cli_helpers,
tmp_path,
):
config = {"General": {"Measurement": "test_config"}}

config_path = str(tmp_path / "config.yml")
Expand All @@ -50,12 +63,21 @@ def test_templates(mock_validate, mock_create_histograms, cli_helpers, tmp_path)
result = runner.invoke(cli.templates, [config_path])
assert result.exit_code == 0
assert mock_validate.call_args_list == [((config,), {})]
assert mock_is_ntuple.call_args_list == [((config,), {})]
assert mock_create_histograms.call_args_list == [((config,), {"method": "uproot"})]
assert mock_collect_histograms.call_count == 0

# different method
# different method, histogram input via side effect
result = runner.invoke(cli.templates, ["--method", "unknown", config_path])
assert result.exit_code == 0
assert mock_create_histograms.call_args == ((config,), {"method": "unknown"})
assert mock_create_histograms.call_count == 1 # no new call
assert mock_collect_histograms.call_args == ((config,), {"method": "unknown"})

# input type specified
result = runner.invoke(cli.templates, ["--input_type", "ntuple", config_path])
assert mock_is_ntuple.call_count == 2 # no new call
assert mock_create_histograms.call_count == 2
assert mock_collect_histograms.call_count == 1 # no new call


@mock.patch("cabinetry.templates.postprocess", autospec=True)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,11 @@ def test_region_dict(caplog):

with pytest.raises(ValueError, match="region abc not found in config"):
configuration.region_dict(config, "abc")


def test__input_is_ntuple():
config_ntuple = {"Samples": [{"Name": "", "Tree": ""}]}
config_histogram = {"Samples": [{"Name": ""}]}

assert configuration._input_is_ntuple(config_ntuple) is True
assert configuration._input_is_ntuple(config_histogram) is False