Skip to content

Commit

Permalink
Merge pull request #316 from lsst/tickets/DM-48159
Browse files Browse the repository at this point in the history
DM-48159 : Make Coverage import optional
  • Loading branch information
tcjennings authored Dec 16, 2024
2 parents 63cee0f + 7ed95cd commit e4484fe
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 4 deletions.
3 changes: 3 additions & 0 deletions doc/changes/DM-47159.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Makes coverage package optional and lazy-loads it only when needed.
Declares packaging extra for coverage, i.e., `pip install lsst-ctrl-mpexec[coverage]`.
Updates minimum supported Python to 3.11 (dependency-driven change).
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "lsst-ctrl-mpexec"
requires-python = ">=3.10.0"
requires-python = ">=3.11.0"
description = "Pipeline execution infrastructure for the Rubin Observatory LSST Science Pipelines."
license = {text = "BSD 3-Clause License"}
readme = "README.rst"
Expand All @@ -16,8 +16,9 @@ classifiers = [
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Astronomy",
]
keywords = ["lsst"]
Expand All @@ -38,6 +39,7 @@ dynamic = ["version"]
"Homepage" = "https://github.com/lsst/ctrl_mpexec"

[project.optional-dependencies]
coverage = ["coverage"]
test = ["pytest >= 3.2"]

[tool.setuptools.packages.find]
Expand Down
7 changes: 6 additions & 1 deletion python/lsst/ctrl/mpexec/cli/cmd/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
from collections.abc import Iterator, Sequence
from contextlib import contextmanager
from functools import partial
from importlib import import_module
from tempfile import NamedTemporaryFile
from typing import Any

import click
import coverage
import lsst.pipe.base.cli.opt as pipeBaseOpts
from lsst.ctrl.mpexec import Report
from lsst.ctrl.mpexec.showInfo import ShowInfo
Expand Down Expand Up @@ -145,6 +145,11 @@ def coverage_context(kwargs: dict[str, Any]) -> Iterator[None]:
if not kwargs.pop("coverage", False):
yield
return
# Lazily import coverage only when we might need it
try:
coverage = import_module("coverage")
except ModuleNotFoundError:
raise click.ClickException("coverage was requested but the coverage package is not installed.")
with NamedTemporaryFile("w") as rcfile:
rcfile.write(
"""
Expand Down
4 changes: 3 additions & 1 deletion python/lsst/ctrl/mpexec/cli/opt/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
"--debug", help="Enable debugging output using lsstDebug facility (imports debug.py).", is_flag=True
)

coverage_option = MWOptionDecorator("--coverage", help="Enable coverage output.", is_flag=True)
coverage_option = MWOptionDecorator(
"--coverage", help="Enable coverage output (requires coverage package).", is_flag=True
)

coverage_report_option = MWOptionDecorator(
"--cov-report/--no-cov-report",
Expand Down
21 changes: 21 additions & 0 deletions tests/test_cliScript.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
import os
import tempfile
import unittest
import unittest.mock

import click
import lsst.utils.tests
from lsst.ctrl.mpexec.cli import opt, script
from lsst.ctrl.mpexec.cli.cmd.commands import coverage_context
from lsst.ctrl.mpexec.cli.pipetask import cli as pipetaskCli
from lsst.ctrl.mpexec.showInfo import ShowInfo
from lsst.daf.butler.cli.utils import LogCliRunner, clickResultMsg
Expand Down Expand Up @@ -203,6 +205,25 @@ def cli(**kwargs):
self.assertNotEqual(result.exit_code, 0)


class CoverageTestCase(unittest.TestCase):
"""Test coverage context manager."""

@unittest.mock.patch.dict("sys.modules", coverage=unittest.mock.MagicMock())
def testWithCoverage(self):
"""Test that the coverage context manager runs when invoked."""
with coverage_context({"coverage": True}):
self.assertTrue(True)

@unittest.mock.patch("lsst.ctrl.mpexec.cli.cmd.commands.import_module", side_effect=ModuleNotFoundError())
def testWithMissingCoverage(self, mock_import):
"""Test that the coverage context manager complains when coverage is
not available.
"""
with self.assertRaises(click.exceptions.ClickException):
with coverage_context({"coverage": True}):
pass


if __name__ == "__main__":
lsst.utils.tests.init()
unittest.main()

0 comments on commit e4484fe

Please sign in to comment.