From 41cfb4d3821a17b347e964d1a1bb386c7f5ceb6c Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 12 Jun 2020 09:52:43 -0400 Subject: [PATCH 1/3] Drop support for Python versions older than 3.5 --- .travis.yml | 6 ++---- setup.py | 4 ---- tox.ini | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81141d5..5e0e522 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: python cache: pip dist: xenial python: - - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" @@ -13,9 +11,9 @@ install: - pip install -U pip setuptools - pip install tox-travis - pip install codecov - - if [[ $TRAVIS_PYTHON_VERSION != 3.4 ]]; then pip install pre-commit; fi + - pip install pre-commit script: - tox - - if [[ $TRAVIS_PYTHON_VERSION != 3.4 ]]; then pre-commit run --all-files; fi + - pre-commit run --all-files after_success: - codecov diff --git a/setup.py b/setup.py index d4daa16..ca63890 100644 --- a/setup.py +++ b/setup.py @@ -39,10 +39,7 @@ setup( 'Intended Audience :: Developers', 'Topic :: Security', 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', @@ -51,7 +48,6 @@ setup( keywords='stix stix2 json cti cyber threat intelligence', packages=find_packages(exclude=['*.test', '*.test.*']), install_requires=[ - 'enum34 ; python_version<"3.4"', 'pytz', 'requests', 'simplejson', diff --git a/tox.ini b/tox.ini index d34aac1..328c03a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,py36,py37,py38,style,isort-check,packaging +envlist = py35,py36,py37,py38,style,isort-check,packaging [testenv] deps = @@ -42,8 +42,6 @@ commands = [travis] python = - 2.7: py27, style - 3.4: py34 3.5: py35 3.6: py36 3.7: py37 From bc51cd47bd3fa94bee8ceafdb185c0a6d46afe7d Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Wed, 17 Jun 2020 16:11:30 -0400 Subject: [PATCH 2/3] Revamp ListProperty so its logic makes more sense. --- stix2/properties.py | 78 +++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/stix2/properties.py b/stix2/properties.py index 956bfce..8bb0636 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -189,15 +189,29 @@ class ListProperty(Property): def __init__(self, contained, **kwargs): """ - ``contained`` should be a function which returns an object from the value. + ``contained`` should be a Property class or instance, or a _STIXBase + subclass. """ - if inspect.isclass(contained) and issubclass(contained, Property): - # If it's a class and not an instance, instantiate it so that - # clean() can be called on it, and ListProperty.clean() will - # use __call__ when it appends the item. - self.contained = contained() - else: + self.contained = None + + if inspect.isclass(contained): + # Property classes are instantiated; _STIXBase subclasses are left + # as-is. + if issubclass(contained, Property): + self.contained = contained() + elif issubclass(contained, _STIXBase): + self.contained = contained + + elif isinstance(contained, Property): self.contained = contained + + if not self.contained: + raise TypeError( + "Invalid list element type: {}".format( + str(contained), + ), + ) + super(ListProperty, self).__init__(**kwargs) def clean(self, value): @@ -209,40 +223,28 @@ class ListProperty(Property): if isinstance(value, (_STIXBase, string_types)): value = [value] - result = [] - for item in value: - try: - valid = self.contained.clean(item) - except ValueError: - raise - except AttributeError: - # type of list has no clean() function (eg. built in Python types) - # TODO Should we raise an error here? - valid = item + if isinstance(self.contained, Property): + result = [ + self.contained.clean(item) + for item in value + ] - if type(self.contained) is EmbeddedObjectProperty: - obj_type = self.contained.type - elif type(self.contained).__name__ == "STIXObjectProperty": - # ^ this way of checking doesn't require a circular import - # valid is already an instance of a python-stix2 class; no need - # to turn it into a dictionary and then pass it to the class - # constructor again - result.append(valid) - continue - elif type(self.contained) is DictionaryProperty: - obj_type = dict - else: - obj_type = self.contained + else: # self.contained must be a _STIXBase subclass + result = [] + for item in value: + if isinstance(item, self.contained): + valid = item + + elif isinstance(item, Mapping): + # attempt a mapping-like usage... + valid = self.contained(**item) - if isinstance(valid, Mapping): - try: - valid._allow_custom - except AttributeError: - result.append(obj_type(**valid)) else: - result.append(obj_type(allow_custom=True, **valid)) - else: - result.append(obj_type(valid)) + raise ValueError("Can't create a {} out of {}".format( + self.contained._type, str(item), + )) + + result.append(valid) # STIX spec forbids empty lists if len(result) < 1: From bf5176f370e80a390dcb92a2c8fd4a5a0dd50a2b Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Wed, 17 Jun 2020 16:31:03 -0400 Subject: [PATCH 3/3] Add some new unit tests corresponding to ListProperty revamp. --- stix2/test/v20/test_properties.py | 86 ++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/stix2/test/v20/test_properties.py b/stix2/test/v20/test_properties.py index 802d865..6810966 100644 --- a/stix2/test/v20/test_properties.py +++ b/stix2/test/v20/test_properties.py @@ -3,8 +3,10 @@ import uuid import pytest import stix2 +import stix2.base from stix2.exceptions import ( AtLeastOnePropertyError, CustomContentError, DictionaryKeyError, + ExtraPropertiesError, ) from stix2.properties import ( BinaryProperty, BooleanProperty, DictionaryProperty, @@ -66,7 +68,7 @@ def test_fixed_property(): assert p.clean(p.default()) -def test_list_property(): +def test_list_property_property_type(): p = ListProperty(StringProperty) assert p.clean(['abc', 'xyz']) @@ -74,6 +76,88 @@ def test_list_property(): p.clean([]) +def test_list_property_property_type_custom(): + class TestObj(stix2.base._STIXBase): + _type = "test" + _properties = { + "foo": StringProperty(), + } + p = ListProperty(EmbeddedObjectProperty(type=TestObj)) + + objs_custom = [ + TestObj(foo="abc", bar=123, allow_custom=True), + TestObj(foo="xyz"), + ] + + assert p.clean(objs_custom) + + dicts_custom = [ + {"foo": "abc", "bar": 123}, + {"foo": "xyz"}, + ] + + # no opportunity to set allow_custom=True when using dicts + with pytest.raises(ExtraPropertiesError): + p.clean(dicts_custom) + + +def test_list_property_object_type(): + class TestObj(stix2.base._STIXBase): + _type = "test" + _properties = { + "foo": StringProperty(), + } + p = ListProperty(TestObj) + + objs = [TestObj(foo="abc"), TestObj(foo="xyz")] + assert p.clean(objs) + + dicts = [{"foo": "abc"}, {"foo": "xyz"}] + assert p.clean(dicts) + + +def test_list_property_object_type_custom(): + class TestObj(stix2.base._STIXBase): + _type = "test" + _properties = { + "foo": StringProperty(), + } + p = ListProperty(TestObj) + + objs_custom = [ + TestObj(foo="abc", bar=123, allow_custom=True), + TestObj(foo="xyz"), + ] + + assert p.clean(objs_custom) + + dicts_custom = [ + {"foo": "abc", "bar": 123}, + {"foo": "xyz"}, + ] + + # no opportunity to set allow_custom=True when using dicts + with pytest.raises(ExtraPropertiesError): + p.clean(dicts_custom) + + +def test_list_property_bad_element_type(): + with pytest.raises(TypeError): + ListProperty(1) + + +def test_list_property_bad_value_type(): + class TestObj(stix2.base._STIXBase): + _type = "test" + _properties = { + "foo": StringProperty(), + } + + list_prop = ListProperty(TestObj) + with pytest.raises(ValueError): + list_prop.clean([1]) + + def test_string_property(): prop = StringProperty()