Make object attribute access act like dictionary access, raising an

error for any property (including custom or optional) not set on the object.
stix2.1
clenk 2017-06-07 11:06:20 -04:00
parent 35e973243f
commit 860efcc230
6 changed files with 31 additions and 17 deletions

View File

@ -73,7 +73,8 @@ class _STIXBase(collections.Mapping):
failed_dependency_pairs = []
for p in list_of_properties:
for dp in list_of_dependent_properties:
if not self.__getattr__(p) and self.__getattr__(dp):
if ((not hasattr(self, p) or (hasattr(self, p) and not self.__getattr__(p))) and
hasattr(self, dp) and self.__getattr__(dp)):
failed_dependency_pairs.append((p, dp))
if failed_dependency_pairs:
raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
@ -128,10 +129,6 @@ class _STIXBase(collections.Mapping):
# 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))
@ -163,7 +160,7 @@ class _STIXBase(collections.Mapping):
def new_version(self, **kwargs):
unchangable_properties = []
if self.revoked:
if hasattr(self, 'revoked') and self.revoked:
raise RevokeError("new_version")
new_obj_inner = copy.deepcopy(self._inner)
properties_to_change = kwargs.keys()
@ -183,7 +180,7 @@ class _STIXBase(collections.Mapping):
return cls(**new_obj_inner)
def revoke(self):
if self.revoked:
if hasattr(self, 'revoked') and self.revoked:
raise RevokeError("revoke")
return self.new_version(revoked=True)

View File

@ -443,10 +443,10 @@ class Process(_Observable):
super(Process, self)._check_object_constraints()
try:
self._check_at_least_one_property()
if self.extensions and "windows-process-ext" in self.extensions:
if hasattr(self, 'extensions') and "windows-process-ext" in self.extensions:
self.extensions["windows-process-ext"]._check_at_least_one_property()
except AtLeastOnePropertyError as enclosing_exc:
if not self.extensions:
if not hasattr(self, 'extensions'):
raise enclosing_exc
else:
if "windows-process-ext" in self.extensions:

View File

@ -244,6 +244,8 @@ class ObservableProperty(Property):
dictified = get_dict(value)
except ValueError:
raise ValueError("The observable property must contain a dictionary")
if dictified == {}:
raise ValueError("The dictionary property must contain a non-empty dictionary")
valid_refs = dict((k, v['type']) for (k, v) in dictified.items())
@ -265,6 +267,8 @@ class DictionaryProperty(Property):
dictified = get_dict(value)
except ValueError:
raise ValueError("The dictionary property must contain a dictionary")
if dictified == {}:
raise ValueError("The dictionary property must contain a non-empty dictionary")
for k in dictified.keys():
if len(k) < 3:
@ -418,6 +422,8 @@ class ExtensionsProperty(DictionaryProperty):
dictified = get_dict(value)
except ValueError:
raise ValueError("The extensions property must contain a dictionary")
if dictified == {}:
raise ValueError("The dictionary property must contain a non-empty dictionary")
from .__init__ import EXT_MAP # avoid circular import
if self.enclosing_type in EXT_MAP:

View File

@ -48,7 +48,8 @@ def test_empty_bundle():
assert bundle.type == "bundle"
assert bundle.id.startswith("bundle--")
assert bundle.spec_version == "2.0"
assert bundle.objects is None
with pytest.raises(AttributeError):
assert bundle.objects
def test_bundle_with_wrong_type():

View File

@ -199,6 +199,13 @@ def test_parse_artifact_invalid(data):
stix2.parse(odata_str)
def test_artifact_example_dependency_error():
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
stix2.Artifact(url="http://example.com/sirvizio.exe")
assert excinfo.value.dependencies == [("hashes", "url")]
@pytest.mark.parametrize("data", [
""""0": {
"type": "autonomous-system",
@ -769,6 +776,10 @@ def test_file_example_encryption_error():
assert excinfo.value.cls == stix2.File
assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")]
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
stix2.File(name="qwerty.dll",
encryption_algorithm="AES128-CBC")
def test_ip4_address_example():
ip4 = stix2.IPv4Address(_valid_refs={"4": "mac-addr", "5": "mac-addr"},
@ -916,14 +927,12 @@ def test_process_example_windows_process_ext_empty():
def test_process_example_extensions_empty():
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
stix2.Process(extensions={
})
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
stix2.Process(extensions={})
assert excinfo.value.cls == stix2.Process
properties_of_process = list(stix2.Process._properties.keys())
properties_of_process.remove("type")
assert excinfo.value.properties == sorted(properties_of_process)
assert excinfo.value.prop_name == 'extensions'
assert 'non-empty dictionary' in excinfo.value.reason
def test_process_example_with_WindowsProcessExt_Object():

View File

@ -40,7 +40,8 @@ def test_making_new_version_with_unset():
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
assert campaign_v1.created == campaign_v2.created
assert campaign_v1.name == campaign_v2.name
assert campaign_v2.description is None
with pytest.raises(AttributeError):
assert campaign_v2.description
assert campaign_v1.modified < campaign_v2.modified