Repository: lig/pyventory

Issue:
NotImplemeted as property value
Allow defining `NotImplemented` as Asset property value.
Do not include such a property in group vars if not overridden.
Raise an exception if the value wasn't overridden in the host vars.

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index 80f81e0..04ae275 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,8 @@
 !.gitignore
 *~
 *.pyc
-*.egg-info/
+
+/venv
+/build
+/dist
+/*.egg-info
diff --git a/pyventory/asset.py b/pyventory/asset.py
index d244bb8..03c31ec 100644
--- a/pyventory/asset.py
+++ b/pyventory/asset.py
@@ -2,7 +2,7 @@ from collections import OrderedDict, Mapping, Sequence
 
 import six
 
-from pyventory.errors import ValueSubstitutionError
+from pyventory import errors
 
 
 __all__ = ['Asset']
@@ -33,11 +33,21 @@ class Asset(object):
             if not attr_name.startswith('_'))
 
         for name, value in _vars.copy().items():
+
+            if value is NotImplemented:
+                if strict_format:
+                    raise errors.PropertyIsNotImplementedError(
+                        'Var "{}" is not implemented in "{}" asset instance',
+                        name, obj._name())
+                else:
+                    del _vars[name]
+                    continue
+
             try:
                 _vars[name] = cls.__format_value(value, _vars)
             except KeyError as e:
                 if strict_format:
-                    raise ValueSubstitutionError(
+                    raise errors.ValueSubstitutionError(
                         'Var "{}" must be available for "{}" asset instance',
                         e.args[0], obj._name())
                 else:
diff --git a/pyventory/errors.py b/pyventory/errors.py
index 3616bd4..c83d2cf 100644
--- a/pyventory/errors.py
+++ b/pyventory/errors.py
@@ -12,3 +12,7 @@ class PyventoryError(Exception):
 
 class ValueSubstitutionError(PyventoryError):
     pass
+
+
+class PropertyIsNotImplementedError(PyventoryError):
+    pass


Test patch:
diff --git a/tests/unit/test_inventory.py b/tests/unit/test_inventory.py
index 7bf24ef..356ddbe 100644
--- a/tests/unit/test_inventory.py
+++ b/tests/unit/test_inventory.py
@@ -1,8 +1,7 @@
 import pytest
 import six
 
-from pyventory import Asset, ansible_inventory
-from pyventory.errors import ValueSubstitutionError
+from pyventory import Asset, ansible_inventory, errors
 
 
 def test_allow_mixins_for_inventory_items():
@@ -209,7 +208,7 @@ def test_require_arguments_for_format_strings():
 
     test_asset = TestAsset()
 
-    with pytest.raises(ValueSubstitutionError):
+    with pytest.raises(errors.ValueSubstitutionError):
         ansible_inventory(locals())
 
 
@@ -398,3 +397,57 @@ def test_multiple_children():
         }
     }
 }'''
+
+
+def test_allow_notimplemented_value():
+
+    class BaseTestAsset(Asset):
+        foo = NotImplemented
+
+    class TestAsset(BaseTestAsset):
+        foo = 'bar'
+
+    test_asset = TestAsset()
+
+    result = six.StringIO()
+    ansible_inventory(locals(), out=result, indent=4)
+
+    # hack for py27 `json.dump()` behavior
+    result = '\n'.join([x.rstrip() for x in result.getvalue().split('\n')])
+
+    assert result == '''{
+    "BaseTestAsset": {
+        "children": [
+            "TestAsset"
+        ]
+    },
+    "TestAsset": {
+        "vars": {
+            "foo": "bar"
+        },
+        "hosts": [
+            "test_asset"
+        ]
+    },
+    "_meta": {
+        "hostvars": {
+            "test_asset": {
+                "foo": "bar"
+            }
+        }
+    }
+}'''
+
+
+def test_raise_notimplemented_value_in_host():
+
+    class BaseTestAsset(Asset):
+        foo = NotImplemented
+
+    class TestAsset(BaseTestAsset):
+        pass
+
+    test_asset = TestAsset()
+
+    with pytest.raises(errors.PropertyIsNotImplementedError):
+        ansible_inventory(locals())