From 5d7ed643bd879b2314ea550034f2b22406560e3b Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 2 Feb 2017 10:16:10 -0600 Subject: [PATCH] Check for required args first, and check for them all at once. This is necessary for versions of Python <3.6, where dictionaries are unordered by default, meaning we can't ensure the order in which fields are checked. --- README.md | 2 +- stix2/__init__.py | 11 +++++++---- stix2/test/test_stix2.py | 35 ++++++++++++++++------------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index bc6436c..722cf7b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ will result in an error: ```python >>> indicator = Indicator() -ValueError: Missing required field for Indicator: 'labels' +ValueError: Missing required field(s) for Indicator: (labels, pattern). ``` However, the required `valid_from` attribute on Indicators will be set to the diff --git a/stix2/__init__.py b/stix2/__init__.py index c9628c2..6e0d09a 100644 --- a/stix2/__init__.py +++ b/stix2/__init__.py @@ -74,12 +74,15 @@ class _STIXBase(collections.Mapping): if extra_kwargs: raise TypeError("unexpected keyword arguments: " + str(extra_kwargs)) + required_fields = [k for k, v in cls._properties.items() if v.get('required')] + missing_kwargs = set(required_fields) - set(kwargs) + if missing_kwargs: + msg = "Missing required field(s) for {type}: ({fields})." + field_list = ", ".join(x for x in sorted(list(missing_kwargs))) + raise ValueError(msg.format(type=class_name, fields=field_list)) + for prop_name, prop_metadata in cls._properties.items(): if prop_name not in kwargs: - if prop_metadata.get('required'): - msg = "Missing required field for {type}: '{field}'." - raise ValueError(msg.format(type=class_name, - field=prop_name)) if prop_metadata.get('default'): default = prop_metadata['default'] if default == NOW: diff --git a/stix2/test/test_stix2.py b/stix2/test/test_stix2.py index a748acd..e33b28e 100644 --- a/stix2/test/test_stix2.py +++ b/stix2/test/test_stix2.py @@ -150,29 +150,28 @@ def test_indicator_autogenerated_fields(indicator): def test_indicator_type_must_be_indicator(): with pytest.raises(ValueError) as excinfo: - indicator = stix2.Indicator(type='xxx') + indicator = stix2.Indicator(type='xxx', **INDICATOR_KWARGS) assert "Indicator must have type='indicator'." in str(excinfo) def test_indicator_id_must_start_with_indicator(): with pytest.raises(ValueError) as excinfo: - indicator = stix2.Indicator(id='my-prefix--') + indicator = stix2.Indicator(id='my-prefix--', **INDICATOR_KWARGS) assert "Indicator id values must begin with 'indicator--'." in str(excinfo) -def test_indicator_required_field_labels(): +def test_indicator_required_fields(): with pytest.raises(ValueError) as excinfo: indicator = stix2.Indicator() - assert "Missing required field for Indicator: 'labels'." in str(excinfo) + assert "Missing required field(s) for Indicator: (labels, pattern)." in str(excinfo) def test_indicator_required_field_pattern(): with pytest.raises(ValueError) as excinfo: - # Label is checked first, so make sure that is provided indicator = stix2.Indicator(labels=['malicious-activity']) - assert "Missing required field for Indicator: 'pattern'." in str(excinfo) + assert "Missing required field(s) for Indicator: (pattern)." in str(excinfo) def test_cannot_assign_to_indicator_attributes(indicator): @@ -240,29 +239,28 @@ def test_malware_autogenerated_fields(malware): def test_malware_type_must_be_malware(): with pytest.raises(ValueError) as excinfo: - malware = stix2.Malware(type='xxx') + malware = stix2.Malware(type='xxx', **MALWARE_KWARGS) assert "Malware must have type='malware'." in str(excinfo) def test_malware_id_must_start_with_malware(): with pytest.raises(ValueError) as excinfo: - malware = stix2.Malware(id='my-prefix--') + malware = stix2.Malware(id='my-prefix--', **MALWARE_KWARGS) assert "Malware id values must begin with 'malware--'." in str(excinfo) -def test_malware_required_field_labels(): +def test_malware_required_fields(): with pytest.raises(ValueError) as excinfo: malware = stix2.Malware() - assert "Missing required field for Malware: 'labels'." in str(excinfo) + assert "Missing required field(s) for Malware: (labels, name)." in str(excinfo) def test_malware_required_field_name(): with pytest.raises(ValueError) as excinfo: - # Label is checked first, so make sure that is provided malware = stix2.Malware(labels=['ransomware']) - assert "Missing required field for Malware: 'name'." in str(excinfo) + assert "Missing required field(s) for Malware: (name)." in str(excinfo) def test_cannot_assign_to_malware_attributes(malware): @@ -324,14 +322,14 @@ def test_relationship_autogenerated_fields(relationship): def test_relationship_type_must_be_relationship(): with pytest.raises(ValueError) as excinfo: - relationship = stix2.Relationship(type='xxx') + relationship = stix2.Relationship(type='xxx', **RELATIONSHIP_KWARGS) assert "Relationship must have type='relationship'." in str(excinfo) def test_relationship_id_must_start_with_relationship(): with pytest.raises(ValueError) as excinfo: - relationship = stix2.Relationship(id='my-prefix--') + relationship = stix2.Relationship(id='my-prefix--', **RELATIONSHIP_KWARGS) assert "Relationship id values must begin with 'relationship--'." in str(excinfo) @@ -339,24 +337,23 @@ def test_relationship_id_must_start_with_relationship(): def test_relationship_required_field_relationship_type(): with pytest.raises(ValueError) as excinfo: relationship = stix2.Relationship() - assert "Missing required field for Relationship: 'relationship_type'." in str(excinfo) + assert "Missing required field(s) for Relationship: (relationship_type, source_ref, target_ref)." in str(excinfo) -def test_relationship_required_field_source_ref(): +def test_relationship_missing_some_required_fields(): with pytest.raises(ValueError) as excinfo: # relationship_type is checked first, so make sure that is provided relationship = stix2.Relationship(relationship_type='indicates') - assert "Missing required field for Relationship: 'source_ref'." in str(excinfo) + assert "Missing required field(s) for Relationship: (source_ref, target_ref)." in str(excinfo) def test_relationship_required_field_target_ref(): with pytest.raises(ValueError) as excinfo: - # relationship_type and source_ref are checked first, so make sure those are provided relationship = stix2.Relationship( relationship_type='indicates', source_ref=INDICATOR_ID ) - assert "Missing required field for Relationship: 'target_ref'." in str(excinfo) + assert "Missing required field(s) for Relationship: (target_ref)." in str(excinfo) def test_cannot_assign_to_relationship_attributes(relationship):