Repository: scikit-hep/pyhf

Issue:
Add exceptions module for common semantic exceptions
# Description

Need to have `pyhf/exceptions` which contains various exception classes such as `InvalidModifier` and so on.


Discussion:

Resolving patch:
diff --git a/pyhf/__init__.py b/pyhf/__init__.py
--- a/pyhf/__init__.py
+++ b/pyhf/__init__.py
@@ -1,6 +1,7 @@
 import logging
 import pyhf.optimize as optimize
 import pyhf.tensor as tensor
+from . import exceptions
 
 log = logging.getLogger(__name__)
 tensorlib = tensor.numpy_backend()
@@ -147,7 +148,7 @@ def add_or_get_modifier(self, channel, sample, modifier_def):
             modifier_cls = modifiers.registry[modifier_def['type']]
         except KeyError:
             log.exception('Modifier type not implemented yet (processing {0:s}). Current modifier types: {1}'.format(modifier_def['type'], modifiers.registry.keys()))
-            raise modifiers.InvalidModifier()
+            raise exceptions.InvalidModifier()
 
         # if modifier is shared, check if it already exists and use it
         if modifier_cls.is_shared and modifier_def['name'] in self.par_map:
diff --git a/pyhf/exceptions/__init__.py b/pyhf/exceptions/__init__.py
new file mode 100644
--- /dev/null
+++ b/pyhf/exceptions/__init__.py
@@ -0,0 +1,9 @@
+"""
+InvalidModifier is raised when an invalid modifier is requested. This includes:
+
+    - creating a custom modifier with the wrong structure
+    - initializing a modifier that does not exist, or has not been loaded
+
+"""
+class InvalidModifier(Exception):
+    pass
diff --git a/pyhf/modifiers/__init__.py b/pyhf/modifiers/__init__.py
--- a/pyhf/modifiers/__init__.py
+++ b/pyhf/modifiers/__init__.py
@@ -2,10 +2,9 @@
 import logging
 log = logging.getLogger(__name__)
 
-registry = {}
+from .. import exceptions
 
-class InvalidModifier(Exception):
-  pass
+registry = {}
 
 '''
 Check if given object contains the right structure for constrained and unconstrained modifiers
@@ -16,7 +15,7 @@ def validate_modifier_structure(modifier, constrained):
 
     for method in required_methods + required_constrained_methods*constrained:
         if not hasattr(modifier, method):
-          raise InvalidModifier('Expected {0:s} method on {1:s}constrained modifier {2:s}'.format(method, '' if constrained else 'un', modifier.__name__))
+          raise exceptions.InvalidModifier('Expected {0:s} method on {1:s}constrained modifier {2:s}'.format(method, '' if constrained else 'un', modifier.__name__))
     return True
 
 '''
@@ -51,7 +50,7 @@ def add_to_registry(cls, cls_name=None, constrained=False, shared=False):
 Raises:
     ValueError: too many keyword arguments, or too many arguments, or wrong arguments
     TypeError: provided name is not a string
-    InvalidModifier: object does not have necessary modifier structure
+    pyhf.exceptions.InvalidModifier: object does not have necessary modifier structure
 
 Examples:
 
@@ -83,7 +82,7 @@ def add_to_registry(cls, cls_name=None, constrained=False, shared=False):
   >>> ...   def __init__(self): pass
   >>> ...   def add_sample(self): pass
   >>>
-  InvalidModifier: Expected alphas method on constrained modifier myCustomModifier
+  pyhf.exceptions.InvalidModifier: Expected alphas method on constrained modifier myCustomModifier
 '''
 def modifier(*args, **kwargs):
     name = kwargs.pop('name', None)


Test patch:
diff --git a/tests/test_modifiers.py b/tests/test_modifiers.py
--- a/tests/test_modifiers.py
+++ b/tests/test_modifiers.py
@@ -19,7 +19,7 @@ def test_import_default_modifiers(test_modifier):
 
 # we make sure modifiers have right structure
 def test_modifiers_structure():
-    from pyhf.modifiers import modifier, InvalidModifier
+    from pyhf.modifiers import modifier
 
     @modifier(name='myUnconstrainedModifier')
     class myCustomModifier(object):
@@ -59,17 +59,17 @@ def expected_data(self): pass
     assert pyhf.modifiers.registry['myConstrainedModifier'].is_shared == False
     del pyhf.modifiers.registry['myConstrainedModifier']
 
-    with pytest.raises(InvalidModifier):
+    with pytest.raises(pyhf.exceptions.InvalidModifier):
         @modifier
         class myCustomModifier(object):
             pass
 
-    with pytest.raises(InvalidModifier):
+    with pytest.raises(pyhf.exceptions.InvalidModifier):
         @modifier(constrained=True)
         class myCustomModifier(object):
             pass
 
-    with pytest.raises(InvalidModifier):
+    with pytest.raises(pyhf.exceptions.InvalidModifier):
         @modifier(name='myConstrainedModifier', constrained=True)
         class myCustomModifier(object):
             def __init__(self): pass
diff --git a/tests/test_pdf.py b/tests/test_pdf.py
--- a/tests/test_pdf.py
+++ b/tests/test_pdf.py
@@ -1,7 +1,6 @@
 import pyhf
 import pytest
 import pyhf.simplemodels
-import pyhf.modifiers
 import numpy as np
 import json
 import jsonschema
@@ -92,7 +91,7 @@ def test_add_unknown_modifier():
             }
         ]
     }
-    with pytest.raises(pyhf.modifiers.InvalidModifier):
+    with pytest.raises(pyhf.exceptions.InvalidModifier):
         pyhf.hfpdf(spec)