Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into main
commit
fc270711a8
|
@ -3,8 +3,6 @@ language: python
|
||||||
cache: pip
|
cache: pip
|
||||||
dist: xenial
|
dist: xenial
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
|
||||||
- "3.4"
|
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
@ -13,9 +11,9 @@ install:
|
||||||
- pip install -U pip setuptools
|
- pip install -U pip setuptools
|
||||||
- pip install tox-travis
|
- pip install tox-travis
|
||||||
- pip install codecov
|
- pip install codecov
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION != 3.4 ]]; then pip install pre-commit; fi
|
- pip install pre-commit
|
||||||
script:
|
script:
|
||||||
- tox
|
- tox
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION != 3.4 ]]; then pre-commit run --all-files; fi
|
- pre-commit run --all-files
|
||||||
after_success:
|
after_success:
|
||||||
- codecov
|
- codecov
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -39,10 +39,7 @@ setup(
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Topic :: Security',
|
'Topic :: Security',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Programming Language :: Python :: 2',
|
|
||||||
'Programming Language :: Python :: 2.7',
|
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.4',
|
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
@ -51,7 +48,6 @@ setup(
|
||||||
keywords='stix stix2 json cti cyber threat intelligence',
|
keywords='stix stix2 json cti cyber threat intelligence',
|
||||||
packages=find_packages(exclude=['*.test', '*.test.*']),
|
packages=find_packages(exclude=['*.test', '*.test.*']),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'enum34 ; python_version<"3.4"',
|
|
||||||
'pytz',
|
'pytz',
|
||||||
'requests',
|
'requests',
|
||||||
'simplejson',
|
'simplejson',
|
||||||
|
|
|
@ -198,15 +198,29 @@ class ListProperty(Property):
|
||||||
|
|
||||||
def __init__(self, contained, **kwargs):
|
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):
|
self.contained = None
|
||||||
# If it's a class and not an instance, instantiate it so that
|
|
||||||
# clean() can be called on it, and ListProperty.clean() will
|
if inspect.isclass(contained):
|
||||||
# use __call__ when it appends the item.
|
# Property classes are instantiated; _STIXBase subclasses are left
|
||||||
self.contained = contained()
|
# as-is.
|
||||||
else:
|
if issubclass(contained, Property):
|
||||||
|
self.contained = contained()
|
||||||
|
elif issubclass(contained, _STIXBase):
|
||||||
|
self.contained = contained
|
||||||
|
|
||||||
|
elif isinstance(contained, Property):
|
||||||
self.contained = contained
|
self.contained = contained
|
||||||
|
|
||||||
|
if not self.contained:
|
||||||
|
raise TypeError(
|
||||||
|
"Invalid list element type: {}".format(
|
||||||
|
str(contained),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
super(ListProperty, self).__init__(**kwargs)
|
super(ListProperty, self).__init__(**kwargs)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
|
@ -218,40 +232,28 @@ class ListProperty(Property):
|
||||||
if isinstance(value, (_STIXBase, string_types)):
|
if isinstance(value, (_STIXBase, string_types)):
|
||||||
value = [value]
|
value = [value]
|
||||||
|
|
||||||
result = []
|
if isinstance(self.contained, Property):
|
||||||
for item in value:
|
result = [
|
||||||
try:
|
self.contained.clean(item)
|
||||||
valid = self.contained.clean(item)
|
for item in value
|
||||||
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:
|
else: # self.contained must be a _STIXBase subclass
|
||||||
obj_type = self.contained.type
|
result = []
|
||||||
elif type(self.contained).__name__ == "STIXObjectProperty":
|
for item in value:
|
||||||
# ^ this way of checking doesn't require a circular import
|
if isinstance(item, self.contained):
|
||||||
# valid is already an instance of a python-stix2 class; no need
|
valid = item
|
||||||
# to turn it into a dictionary and then pass it to the class
|
|
||||||
# constructor again
|
elif isinstance(item, Mapping):
|
||||||
result.append(valid)
|
# attempt a mapping-like usage...
|
||||||
continue
|
valid = self.contained(**item)
|
||||||
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:
|
else:
|
||||||
result.append(obj_type(allow_custom=True, **valid))
|
raise ValueError("Can't create a {} out of {}".format(
|
||||||
else:
|
self.contained._type, str(item),
|
||||||
result.append(obj_type(valid))
|
))
|
||||||
|
|
||||||
|
result.append(valid)
|
||||||
|
|
||||||
# STIX spec forbids empty lists
|
# STIX spec forbids empty lists
|
||||||
if len(result) < 1:
|
if len(result) < 1:
|
||||||
|
|
|
@ -3,8 +3,10 @@ import uuid
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import stix2
|
import stix2
|
||||||
|
import stix2.base
|
||||||
from stix2.exceptions import (
|
from stix2.exceptions import (
|
||||||
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
|
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
|
||||||
|
ExtraPropertiesError,
|
||||||
)
|
)
|
||||||
from stix2.properties import (
|
from stix2.properties import (
|
||||||
BinaryProperty, BooleanProperty, DictionaryProperty,
|
BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||||
|
@ -66,7 +68,7 @@ def test_fixed_property():
|
||||||
assert p.clean(p.default())
|
assert p.clean(p.default())
|
||||||
|
|
||||||
|
|
||||||
def test_list_property():
|
def test_list_property_property_type():
|
||||||
p = ListProperty(StringProperty)
|
p = ListProperty(StringProperty)
|
||||||
|
|
||||||
assert p.clean(['abc', 'xyz'])
|
assert p.clean(['abc', 'xyz'])
|
||||||
|
@ -74,6 +76,88 @@ def test_list_property():
|
||||||
p.clean([])
|
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():
|
def test_string_property():
|
||||||
prop = StringProperty()
|
prop = StringProperty()
|
||||||
|
|
||||||
|
|
4
tox.ini
4
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27,py34,py35,py36,py37,py38,style,isort-check,packaging
|
envlist = py35,py36,py37,py38,style,isort-check,packaging
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
|
@ -42,8 +42,6 @@ commands =
|
||||||
|
|
||||||
[travis]
|
[travis]
|
||||||
python =
|
python =
|
||||||
2.7: py27, style
|
|
||||||
3.4: py34
|
|
||||||
3.5: py35
|
3.5: py35
|
||||||
3.6: py36
|
3.6: py36
|
||||||
3.7: py37
|
3.7: py37
|
||||||
|
|
Loading…
Reference in New Issue