Repository: oasis-open/cti-python-stix2

Issue:
Forgot to instantiate ImmutableError
I'm guessing [this line](https://github.com/oasis-open/cti-python-stix2/blob/f937e2bb3f104e3cdf0578ed8323b2f7e39119a4/stix2/base.py#L99) should raise an instance of the exception class, not the class itself?

Discussion:

Patch:
diff --git a/stix2/base.py b/stix2/base.py
index cde8bb6..3f696fd 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -69,22 +69,19 @@ class _STIXBase(collections.Mapping):
         if list_of_properties and (not list_of_properties_populated or list_of_properties_populated == set(["extensions"])):
             raise AtLeastOnePropertyError(self.__class__, list_of_properties)
 
-    def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties, values=[]):
+    def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties):
         failed_dependency_pairs = []
-        current_properties = self.properties_populated()
         for p in list_of_properties:
-            v = values.pop() if values else None
             for dp in list_of_dependent_properties:
-                if dp in current_properties and (p not in current_properties or (v and not current_properties(p) == v)):
+                if not self.__getattr__(p) and self.__getattr__(dp):
                     failed_dependency_pairs.append((p, dp))
         if failed_dependency_pairs:
             raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
 
     def _check_object_constraints(self):
-        if self.granular_markings:
-            for m in self.granular_markings:
-                # TODO: check selectors
-                pass
+        for m in self.get("granular_markings", []):
+            # TODO: check selectors
+            pass
 
     def __init__(self, **kwargs):
         cls = self.__class__
@@ -100,7 +97,7 @@ class _STIXBase(collections.Mapping):
         # Remove any keyword arguments whose value is None
         setting_kwargs = {}
         for prop_name, prop_value in kwargs.items():
-            if prop_value:
+            if prop_value is not None:
                 setting_kwargs[prop_name] = prop_value
 
         # Detect any missing required properties
@@ -127,11 +124,20 @@ class _STIXBase(collections.Mapping):
 
     # Handle attribute access just like key access
     def __getattr__(self, name):
-        return self.get(name)
+        try:
+            # Return attribute value.
+            return self.__getitem__(name)
+        except KeyError:
+            # If attribute not found, check if its a property of the object.
+            if name in self._properties:
+                return None
+
+            raise AttributeError("'%s' object has no attribute '%s'" %
+                                 (self.__class__.__name__, name))
 
     def __setattr__(self, name, value):
         if name != '_inner' and not name.startswith("_STIXBase__"):
-            raise ImmutableError
+            raise ImmutableError(self.__class__, name)
         super(_STIXBase, self).__setattr__(name, value)
 
     def __str__(self):
diff --git a/stix2/exceptions.py b/stix2/exceptions.py
index 5cbaae9..3043047 100644
--- a/stix2/exceptions.py
+++ b/stix2/exceptions.py
@@ -47,8 +47,14 @@ class ExtraPropertiesError(STIXError, TypeError):
 class ImmutableError(STIXError, ValueError):
     """Attempted to modify an object after creation"""
 
-    def __init__(self):
-        super(ImmutableError, self).__init__("Cannot modify properties after creation.")
+    def __init__(self, cls, key):
+        super(ImmutableError, self).__init__()
+        self.cls = cls
+        self.key = key
+
+    def __str__(self):
+        msg = "Cannot modify '{0.key}' property in '{0.cls.__name__}' after creation."
+        return msg.format(self)
 
 
 class DictionaryKeyError(STIXError, ValueError):


Test patch:
diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py
index 99d34d7..5db50e6 100644
--- a/stix2/test/test_indicator.py
+++ b/stix2/test/test_indicator.py
@@ -129,7 +129,7 @@ def test_cannot_assign_to_indicator_attributes(indicator):
     with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
         indicator.valid_from = dt.datetime.now()
 
-    assert str(excinfo.value) == "Cannot modify properties after creation."
+    assert str(excinfo.value) == "Cannot modify 'valid_from' property in 'Indicator' after creation."
 
 
 def test_invalid_kwarg_to_indicator():
diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py
index 8952769..266d012 100644
--- a/stix2/test/test_malware.py
+++ b/stix2/test/test_malware.py
@@ -92,7 +92,7 @@ def test_cannot_assign_to_malware_attributes(malware):
     with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
         malware.name = "Cryptolocker II"
 
-    assert str(excinfo.value) == "Cannot modify properties after creation."
+    assert str(excinfo.value) == "Cannot modify 'name' property in 'Malware' after creation."
 
 
 def test_invalid_kwarg_to_malware():
diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py
index c47338f..f221494 100644
--- a/stix2/test/test_observed_data.py
+++ b/stix2/test/test_observed_data.py
@@ -764,8 +764,7 @@ def test_file_example_encryption_error():
     with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
         stix2.File(name="qwerty.dll",
                    is_encrypted=False,
-                   encryption_algorithm="AES128-CBC"
-                   )
+                   encryption_algorithm="AES128-CBC")
 
     assert excinfo.value.cls == stix2.File
     assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")]
diff --git a/stix2/test/test_relationship.py b/stix2/test/test_relationship.py
index 1e799bf..1ad792c 100644
--- a/stix2/test/test_relationship.py
+++ b/stix2/test/test_relationship.py
@@ -103,7 +103,7 @@ def test_cannot_assign_to_relationship_attributes(relationship):
     with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
         relationship.relationship_type = "derived-from"
 
-    assert str(excinfo.value) == "Cannot modify properties after creation."
+    assert str(excinfo.value) == "Cannot modify 'relationship_type' property in 'Relationship' after creation."
 
 
 def test_invalid_kwarg_to_relationship():