Repository: oasis-open/cti-python-stix2
Pull request #418 (2020-06-24 13:57:30)

Drop support for Python versions older than 3.5


Patch:
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/stix2/properties.py b/stix2/properties.py
index 8bb0636..956bfce 100644
--- a/stix2/properties.py
+++ b/stix2/properties.py
@@ -189,29 +189,15 @@ class ListProperty(Property):
 
     def __init__(self, contained, **kwargs):
         """
-        ``contained`` should be a Property class or instance, or a _STIXBase
-        subclass.
+        ``contained`` should be a function which returns an object from the value.
         """
-        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):
+        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 = contained
-
-        if not self.contained:
-            raise TypeError(
-                "Invalid list element type: {}".format(
-                    str(contained),
-                ),
-            )
-
         super(ListProperty, self).__init__(**kwargs)
 
     def clean(self, value):
@@ -223,28 +209,40 @@ class ListProperty(Property):
         if isinstance(value, (_STIXBase, string_types)):
             value = [value]
 
-        if isinstance(self.contained, Property):
-            result = [
-                self.contained.clean(item)
-                for item in value
-            ]
-
-        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)
+        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 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
 
+            if isinstance(valid, Mapping):
+                try:
+                    valid._allow_custom
+                except AttributeError:
+                    result.append(obj_type(**valid))
                 else:
-                    raise ValueError("Can't create a {} out of {}".format(
-                        self.contained._type, str(item),
-                    ))
-
-                result.append(valid)
+                    result.append(obj_type(allow_custom=True, **valid))
+            else:
+                result.append(obj_type(valid))
 
         # STIX spec forbids empty lists
         if len(result) < 1:
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


Test patch:
diff --git a/stix2/test/v20/test_properties.py b/stix2/test/v20/test_properties.py
index 6810966..802d865 100644
--- a/stix2/test/v20/test_properties.py
+++ b/stix2/test/v20/test_properties.py
@@ -3,10 +3,8 @@ import uuid
 import pytest
 
 import stix2
-import stix2.base
 from stix2.exceptions import (
     AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
-    ExtraPropertiesError,
 )
 from stix2.properties import (
     BinaryProperty, BooleanProperty, DictionaryProperty,
@@ -68,7 +66,7 @@ def test_fixed_property():
     assert p.clean(p.default())
 
 
-def test_list_property_property_type():
+def test_list_property():
     p = ListProperty(StringProperty)
 
     assert p.clean(['abc', 'xyz'])
@@ -76,88 +74,6 @@ def test_list_property_property_type():
         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()