Repository: zooniverse/panoptes-python-client

Issue:
Cannot create and save new subject set since 1.1
This [script](https://github.com/miclaraia/muon_analysis/blob/master/muon/scripts/test_panoptes_connection.py) illustrates the problem. The script fails on `subject_set.save()`. Mimics the instructions in the tutorial detailed in the [docs](https://panoptes-python-client.readthedocs.io/en/v1.1/user_guide.html#tutorial-creating-a-new-project).

Tries to get a project, create a subject set, link the subject set to the project, then save the subject set. The following trace is shown on `subject_set.save()`:
```
  File "test_panoptes_connection.py", line 23, in main
    subject_set.save()
  File ".../venv/lib/python3.6/site-packages/panoptes_client/panoptes.py", line 815, in save
    etag=self.etag
  File ".../venv/lib/python3.6/site-packages/panoptes_client/panoptes.py", line 404, in post
    retry=retry,
  File ".../venv/lib/python3.6/site-packages/panoptes_client/panoptes.py", line 281, in json_request
    json_response['errors']
panoptes_client.panoptes.PanoptesAPIException: {"schema"=>"did not contain a required property of 'links'"}
```

Discussion:

Patch:
diff --git a/panoptes_client/panoptes.py b/panoptes_client/panoptes.py
index 3f59a9b..463751c 100644
--- a/panoptes_client/panoptes.py
+++ b/panoptes_client/panoptes.py
@@ -935,7 +935,7 @@ class LinkResolver(object):
 
     def __setattr__(self, name, value):
         reserved_names = ('raw', 'parent')
-        if name not in reserved_names and name in self.parent.raw['links']:
+        if name not in reserved_names and name not in dir(self):
             if not self.parent._loaded:
                 self.parent.reload()
             if isinstance(value, PanoptesObject):


Test patch:
diff --git a/panoptes_client/tests/test_linkresolver.py b/panoptes_client/tests/test_linkresolver.py
new file mode 100644
index 0000000..72555c6
--- /dev/null
+++ b/panoptes_client/tests/test_linkresolver.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+import unittest
+import sys
+
+if sys.version_info <= (3, 0):
+    from mock import Mock
+else:
+    from unittest.mock import Mock
+
+from panoptes_client.panoptes import LinkResolver
+
+
+class TestLinkResolver(unittest.TestCase):
+    def test_set_new_link(self):
+        parent = Mock()
+        parent.raw = {'links': {}}
+
+        target = Mock()
+
+        resolver = LinkResolver(parent)
+        resolver.newlink = target
+        self.assertEqual(parent.raw['links'].get('newlink', None), target)
diff --git a/panoptes_client/tests/test_subject_set.py b/panoptes_client/tests/test_subject_set.py
new file mode 100644
index 0000000..97d33cd
--- /dev/null
+++ b/panoptes_client/tests/test_subject_set.py
@@ -0,0 +1,42 @@
+from __future__ import absolute_import, division, print_function
+
+import unittest
+import sys
+
+if sys.version_info <= (3, 0):
+    from mock import patch, Mock
+else:
+    from unittest.mock import patch, Mock
+
+from panoptes_client.subject_set import SubjectSet
+
+
+class TestSubjectSet(unittest.TestCase):
+    def test_create(self):
+        with patch('panoptes_client.panoptes.Panoptes') as pc:
+            pc.client().post = Mock(return_value=(
+                {
+                    'subject_sets': [{
+                        'id': 0,
+                        'display_name': '',
+                    }],
+                },
+                '',
+            ))
+            subject_set = SubjectSet()
+            subject_set.links.project = 1234
+            subject_set.display_name = 'Name'
+            subject_set.save()
+
+            pc.client().post.assert_called_with(
+                '/subject_sets',
+                json={
+                    'subject_sets': {
+                        'display_name': 'Name',
+                        'links': {
+                            'project': 1234,
+                        }
+                    }
+                },
+                etag=None,
+            )