Skip to content
Draft
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
52 changes: 52 additions & 0 deletions src/ctapipe/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
__all__ = [
"CTAPipeException",
"TooFewEvents",
"OptionalDependencyMissing",
"InputMissing",
"MockOptionalDecorator",
]


class CTAPipeException(Exception):
pass

Expand All @@ -17,3 +26,46 @@ def __init__(self, module):

class InputMissing(ValueError):
"""Raised in case an input was not specified."""


class MockOptionalDecorator:
"""A decorator that can be used in-place of an imported decorator.

Will throw the corresponding OptionalDependencyMissing exception when
the decorated function is called.

Examples
--------
You might want to use this class for optional dependencies that provide
decorators. With decorators, it is hard to defer import of the dependency
to runtime. See this example of how one might make numba with njit optional:

from unittest import MagicMock
from ctapipe.exceptions import MockOptionalDecorator
try:
import numba
except ModuleNotFoundError:
numba = MagicMock()
numba.njit = MockOptionalDecorator("numba")

@numba.njit(cache=True)
def example(x):
return 5 * x
"""

def __init__(self, module):
self.module = module

def __call__(self, *args, **kwargs):
def _raise(*args, **kwargs):
raise OptionalDependencyMissing(self.module)

# decorator called as @decorator without arguments
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
return _raise

# decorator called as @decorator(*args, **kwargs)
def wrapper(func):
return _raise

return wrapper
35 changes: 35 additions & 0 deletions src/ctapipe/tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from unittest.mock import MagicMock

import pytest


def test_optional_decorator_no_args():
from ctapipe.exceptions import MockOptionalDecorator, OptionalDependencyMissing

dummy_opt_module = MagicMock()
dummy_opt_module.some_decorator = MockOptionalDecorator("dummy")

# defining the function should not throw
@dummy_opt_module.some_decorator
def func():
pass

with pytest.raises(OptionalDependencyMissing, match="'dummy' is required"):
# calling the function should raise
func()


def test_optional_decorator_with_args():
from ctapipe.exceptions import MockOptionalDecorator, OptionalDependencyMissing

dummy_opt_module = MagicMock()
dummy_opt_module.some_decorator = MockOptionalDecorator("dummy")

# defining the function should not throw
@dummy_opt_module.some_decorator(foo=5)
def func():
pass

with pytest.raises(OptionalDependencyMissing, match="'dummy' is required"):
# calling the function should raise
func()
Loading