Repository: oasis-open/cti-python-stix2

Issue:
WindowsRegistryKey fails to print
I am not sure if I am doing something wrong but trying to print a WindowsRegistryKey object always fails, even when using the provided example.

```
def test_key():
    v = stix2.v21.WindowsRegistryValueType(
        name="Foo",
        data="qwerty",
        data_type="REG_SZ",
    )
    w = stix2.v21.WindowsRegistryKey(
        key="hkey_local_machine\\system\\bar\\foo",
        values=[v],
    )
    print(v)
    print(w)
    assert w.key == "hkey_local_machine\\system\\bar\\foo"
    assert w.values[0].name == "Foo"
    assert w.values[0].data == "qwerty"
    assert w.values[0].data_type == "REG_SZ"
```
Output
```
{
    "name": "Foo",
    "data": "qwerty",
    "data_type": "REG_SZ"
}
Traceback (most recent call last):
  File "sandbox_to_stix.py", line 341, in <module>
    test_key()
  File "sandbox_to_stix.py", line 294, in test_key
    print(w)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/stix2/base.py", line 206, in __str__
    return self.serialize(pretty=True)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/stix2/base.py", line 284, in serialize
    return json.dumps(self, cls=STIXJSONEncoder, **kwargs)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/__init__.py", line 399, in dumps
    **kw).encode(obj)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 298, in encode
    chunks = list(chunks)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 717, in _iterencode
    for chunk in _iterencode(o, _current_indent_level):
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 696, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 652, in _iterencode_dict
    for chunk in chunks:
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 531, in _iterencode_list
    for chunk in chunks:
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 717, in _iterencode
    for chunk in _iterencode(o, _current_indent_level):
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 696, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/simplejson/encoder.py", line 602, in _iterencode_dict
    items.sort(key=_item_sort_key)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/stix2/base.py", line 277, in sort_by
    return find_property_index(self, *element)
  File "/home/timngo/venvs/stix/lib/python3.6/site-packages/stix2/utils.py", line 240, in find_property_index
    idx = _find_property_in_seq(obj.values(), search_key, search_value)
TypeError: 'list' object is not callable
```

I noticed that commenting out the values() getter function in stix2/v21/observables.py will get the print working but then the assert fails since there is no getter

```
class WindowsRegistryKey(_Observable):
    # TODO: Add link
    """For more detailed information on this object's properties, see
    `the STIX 2.1 specification <link here>`__.
    """

    _type = 'windows-registry-key'
    _properties = OrderedDict([
        ('type', TypeProperty(_type)),
        ('key', StringProperty()),
        ('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))),
        # this is not the modified timestamps of the object itself
        ('modified', TimestampProperty()),
        ('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')),
        ('number_of_subkeys', IntegerProperty()),
        ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
    ])

    #@property
    #def values(self):
        # Needed because 'values' is a property on collections.Mapping objects
    #    return self._inner['values']
```
New Output
```
{
    "name": "Foo",
    "data": "qwerty",
    "data_type": "REG_SZ"
}
{
    "type": "windows-registry-key",
    "key": "hkey_local_machine\\system\\bar\\foo",
    "values": [
        {
            "name": "Foo",
            "data": "qwerty",
            "data_type": "REG_SZ"
        }
    ]
}
Traceback (most recent call last):
  File "sandbox_to_stix.py", line 341, in <module>
    test_key()
  File "sandbox_to_stix.py", line 296, in test_key
    assert w.values[0].name == "Foo"
TypeError: 'method' object is not subscriptable
```

Comments:

Patch:
diff --git a/stix2/properties.py b/stix2/properties.py
index 24549aa..d6cc624 100644
--- a/stix2/properties.py
+++ b/stix2/properties.py
@@ -11,7 +11,7 @@ import uuid
 from six import string_types, text_type
 from stix2patterns.validator import run_validator
 
-from .base import _STIXBase
+from .base import _Observable, _STIXBase
 from .core import STIX2_OBJ_MAPS, parse, parse_observable
 from .exceptions import CustomContentError, DictionaryKeyError
 from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime
@@ -167,6 +167,19 @@ class ListProperty(Property):
         return result
 
 
+class CallableValues(list):
+    """Wrapper to allow `values()` method on WindowsRegistryKey objects.
+    Needed because `values` is also a property.
+    """
+
+    def __init__(self, parent_instance, *args, **kwargs):
+        self.parent_instance = parent_instance
+        super(CallableValues, self).__init__(*args, **kwargs)
+
+    def __call__(self):
+        return _Observable.values(self.parent_instance)
+
+
 class StringProperty(Property):
 
     def __init__(self, **kwargs):
@@ -291,8 +304,6 @@ 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 self.spec_version == '2.0':
                 if len(k) < 3:
@@ -498,8 +509,6 @@ class ExtensionsProperty(DictionaryProperty):
             dictified = copy.deepcopy(dictified)
         except ValueError:
             raise ValueError("The extensions property must contain a dictionary")
-        if dictified == {}:
-            raise ValueError("The extensions property must contain a non-empty dictionary")
 
         v = 'v' + self.spec_version.replace('.', '')
 
diff --git a/stix2/v20/observables.py b/stix2/v20/observables.py
index 0e7c4a0..55872cd 100644
--- a/stix2/v20/observables.py
+++ b/stix2/v20/observables.py
@@ -12,7 +12,7 @@ from ..base import _Extension, _Observable, _STIXBase
 from ..custom import _custom_extension_builder, _custom_observable_builder
 from ..exceptions import AtLeastOnePropertyError, DependentPropertiesError
 from ..properties import (
-    BinaryProperty, BooleanProperty, DictionaryProperty,
+    BinaryProperty, BooleanProperty, CallableValues, DictionaryProperty,
     EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
     HashesProperty, HexProperty, IntegerProperty, ListProperty,
     ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
@@ -726,7 +726,7 @@ class WindowsRegistryKey(_Observable):
     @property
     def values(self):
         # Needed because 'values' is a property on collections.Mapping objects
-        return self._inner['values']
+        return CallableValues(self, self._inner['values'])
 
 
 class X509V3ExtenstionsType(_STIXBase):
diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py
index 1b2251d..f383899 100644
--- a/stix2/v21/observables.py
+++ b/stix2/v21/observables.py
@@ -12,7 +12,7 @@ from ..base import _Extension, _Observable, _STIXBase
 from ..custom import _custom_extension_builder, _custom_observable_builder
 from ..exceptions import AtLeastOnePropertyError, DependentPropertiesError
 from ..properties import (
-    BinaryProperty, BooleanProperty, DictionaryProperty,
+    BinaryProperty, BooleanProperty, CallableValues, DictionaryProperty,
     EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
     HashesProperty, HexProperty, IntegerProperty, ListProperty,
     ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
@@ -779,7 +779,7 @@ class WindowsRegistryKey(_Observable):
     @property
     def values(self):
         # Needed because 'values' is a property on collections.Mapping objects
-        return self._inner['values']
+        return CallableValues(self, self._inner['values'])
 
 
 class X509V3ExtenstionsType(_STIXBase):


Test patch:
diff --git a/stix2/test/v20/test_observed_data.py b/stix2/test/v20/test_observed_data.py
index 41a80d6..c186361 100644
--- a/stix2/test/v20/test_observed_data.py
+++ b/stix2/test/v20/test_observed_data.py
@@ -1141,12 +1141,13 @@ def test_process_example_windows_process_ext_empty():
 
 
 def test_process_example_extensions_empty():
-    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
-            stix2.v20.Process(extensions={})
+    proc = stix2.v20.Process(
+        pid=314,
+        name="foobar.exe",
+        extensions={},
+    )
 
-    assert excinfo.value.cls == stix2.v20.Process
-    assert excinfo.value.prop_name == 'extensions'
-    assert 'non-empty dictionary' in excinfo.value.reason
+    assert '{}' in str(proc)
 
 
 def test_process_example_with_WindowsProcessExt_Object():
@@ -1290,6 +1291,8 @@ def test_windows_registry_key_example():
     assert w.values[0].name == "Foo"
     assert w.values[0].data == "qwerty"
     assert w.values[0].data_type == "REG_SZ"
+    # ensure no errors in serialization because of 'values'
+    assert "Foo" in str(w)
 
 
 def test_x509_certificate_example():
diff --git a/stix2/test/v20/test_properties.py b/stix2/test/v20/test_properties.py
index 24c1c99..e9a513e 100644
--- a/stix2/test/v20/test_properties.py
+++ b/stix2/test/v20/test_properties.py
@@ -360,7 +360,6 @@ def test_dictionary_property_invalid_key(d):
 
 @pytest.mark.parametrize(
     "d", [
-        ({}, "The dictionary property must contain a non-empty dictionary"),
         # TODO: This error message could be made more helpful. The error is caused
         # because `json.loads()` doesn't like the *single* quotes around the key
         # name, even though they are valid in a Python dictionary. While technically
diff --git a/stix2/test/v21/test_observed_data.py b/stix2/test/v21/test_observed_data.py
index 5a5881a..eb811b2 100644
--- a/stix2/test/v21/test_observed_data.py
+++ b/stix2/test/v21/test_observed_data.py
@@ -1115,12 +1115,12 @@ def test_process_example_windows_process_ext_empty():
 
 
 def test_process_example_extensions_empty():
-    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
-            stix2.v21.Process(extensions={})
+    proc = stix2.v21.Process(
+        pid=314,
+        extensions={},
+    )
 
-    assert excinfo.value.cls == stix2.v21.Process
-    assert excinfo.value.prop_name == 'extensions'
-    assert 'non-empty dictionary' in excinfo.value.reason
+    assert '{}' in str(proc)
 
 
 def test_process_example_with_WindowsProcessExt_Object():
@@ -1264,6 +1264,8 @@ def test_windows_registry_key_example():
     assert w.values[0].name == "Foo"
     assert w.values[0].data == "qwerty"
     assert w.values[0].data_type == "REG_SZ"
+    # ensure no errors in serialization because of 'values'
+    assert "Foo" in str(w)
 
 
 def test_x509_certificate_example():
diff --git a/stix2/test/v21/test_properties.py b/stix2/test/v21/test_properties.py
index 611ec5e..298a8df 100644
--- a/stix2/test/v21/test_properties.py
+++ b/stix2/test/v21/test_properties.py
@@ -369,7 +369,6 @@ def test_dictionary_property_invalid_key(d):
 
 @pytest.mark.parametrize(
     "d", [
-        ({}, "The dictionary property must contain a non-empty dictionary"),
         # TODO: This error message could be made more helpful. The error is caused
         # because `json.loads()` doesn't like the *single* quotes around the key
         # name, even though they are valid in a Python dictionary. While technically