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): 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():