{"action":"created","comment":{"url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/comments/199689109","pull_request_review_id":133855323,"id":199689109,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5OTY4OTEwOQ==","diff_hunk":"@@ -312,278 +316,177 @@ def test_delete_file__valid_access_token(self):\n class APITestUpload(TestCase):\n     \"\"\"\n     Tests for :func:`upload_file<ohapi.api.upload_file>`.\n+\n+    File and stream upload testing use \"lorem_ipsum.txt\" and other files\n+    in the testing_extra directory.\n     \"\"\"\n \n     def setUp(self):\n         pass\n \n     @my_vcr.use_cassette()\n     def test_upload_valid_file_valid_access_token(self):\n-        with patch('%s.open' % __name__, mock_open(), create=True):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                try:\n-                    filename = 'foo'\n-\n-                    def fake_stat(arg):\n-                        if arg == filename:\n-                            faked = list(orig_os_stat('/tmp'))\n-                            faked[stat.ST_SIZE] = len('some stuff')\n-                            return stat_result(faked)\n-                        else:\n-                            return orig_os_stat(arg)\n-                    orig_os_stat = os.stat\n-                    os.stat = fake_stat\n-                    with open('foo', 'w') as h:\n-                        h.write('some stuff')\n-                    response = upload_file(target_filepath='foo',\n-                                           metadata=FILE_METADATA,\n-                                           access_token=ACCESS_TOKEN,\n-                                           project_member_id=VALID_PMI1)\n-                    self.assertEqual(response.status_code, 201)\n-                    assert response.json() == {\"id\": \"file_id\"}\n-                finally:\n-                    os.stat = orig_os_stat\n+        response = upload_file(\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1)\n+        self.assertEqual(response.status_code, 200)\n+        assert response.json() == {'size': 446, 'status': 'ok'}\n \n     @my_vcr.use_cassette()\n     def test_upload_large_file_valid_access_token(self):\n-        with patch('%s.open' % __name__, mock_open(), create=True):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                try:\n-                    filename = 'foo'\n-\n-                    def fake_stat(arg):\n-                        if arg == filename:\n-                            faked = list(orig_os_stat('/tmp'))\n-                            faked[stat.ST_SIZE] = len('some stuff')\n-                            return stat_result(faked)\n-                        else:\n-                            return orig_os_stat(arg)\n-                    orig_os_stat = os.stat\n-                    os.stat = fake_stat\n-                    with open('foo', 'w') as h:\n-                        h.write('some stuff')\n-                    self.assertRaises(Exception, upload_file,\n-                                      target_filepath='foo',\n-                                      metadata=FILE_METADATA,\n-                                      access_token=ACCESS_TOKEN,\n-                                      project_member_id=VALID_PMI1,\n-                                      max_bytes=MAX_BYTES)\n-                except Exception:\n-                    pass\n-                finally:\n-                    os.stat = orig_os_stat\n+        self.assertRaisesRegexp(\n+            Exception, 'Maximum file size exceeded', upload_file,\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1,\n+            max_bytes=0)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_invalid_access_token(self):\n-        with self.assertRaises(Exception):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                    response = upload_file(\n-                        target_filepath='foo',\n-                        metadata=FILE_METADATA,\n-                        access_token=ACCESS_TOKEN_INVALID,\n-                        project_member_id=VALID_PMI1)\n-                    assert response.json() == {\"detail\": \"Invalid token.\"}\n+        self.assertRaises(\n+            Exception, 'Invalid token', upload_file,\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN_INVALID,\n+            project_member_id=VALID_PMI1)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_expired_access_token(self):\n-        with self.assertRaises(Exception):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                response = upload_file(\n-                    target_filepath='foo',\n-                    metadata=FILE_METADATA,\n-                    access_token=ACCESS_TOKEN_EXPIRED,\n-                    project_member_id=VALID_PMI1)\n-                assert response.json() == {\"detail\": \"Expired token.\"}\n+        self.assertRaisesRegexp(\n+            Exception, 'Expired token', upload_file,\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN_EXPIRED,\n+            project_member_id=VALID_PMI1)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_invalid_metadata_with_description(self):\n-        with self.assertRaises(Exception):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                response = upload_file(\n-                    target_filepath='foo',\n-                    metadata=FILE_METADATA_INVALID_WITH_DESC,\n-                    access_token=ACCESS_TOKEN,\n-                    project_member_id=VALID_PMI1)\n-                assert response.json() == {\n-                    \"metadata\":\n-                    [\"\\\"tags\\\" is a required \" +\n-                     \"field of the metadata\"]}\n+        self.assertRaisesRegexp(\n+            Exception, '\"tags\" is a required field', upload_file,\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA_INVALID_WITH_DESC,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_invalid_metadata_without_description(self):\n-        with self.assertRaises(Exception):\n-            with patch('ohapi.api.open', mock_open(), create=True):\n-                response = upload_file(target_filepath='foo',\n-                                       metadata=FILE_METADATA_INVALID,\n-                                       access_token=ACCESS_TOKEN,\n-                                       project_member_id=VALID_PMI1)\n-                assert response.json() == {\n-                    \"metadata\":\n-                    [\"\\\"description\\\" is a \" +\n-                     \"required field of the metadata\"]}\n+        self.assertRaisesRegexp(\n+            Exception, '\"description\" is a required field of the metadata',\n+            upload_file,\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA_INVALID,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_empty(self):\n-        with self.assertRaises(Exception):\n-            with patch('%s.open' % __name__, mock_open(), create=True):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    try:\n-                        filename = 'foo'\n-\n-                        def fake_stat(arg):\n-                            if arg == filename:\n-                                faked = list(orig_os_stat('/tmp'))\n-                                faked[stat.ST_SIZE] = len('some stuff')\n-                                return stat_result(faked)\n-                            else:\n-                                return orig_os_stat(arg)\n-                        orig_os_stat = os.stat\n-                        os.stat = fake_stat\n-                        with open('foo', 'w') as h:\n-                            h.write('')\n-                        response = upload_file(target_filepath='foo',\n-                                               metadata=FILE_METADATA,\n-                                               access_token=ACCESS_TOKEN,\n-                                               project_member_id=VALID_PMI1)\n-                        assert response.json() == {\n-                            \"data_file\":\n-                            [\"The submitted file is empty.\"]}\n-                    finally:\n-                        os.stat = orig_os_stat\n+        self.assertRaisesRegexp(\n+            Exception, 'The submitted file is empty.',\n+            upload_file,\n+            target_filepath=TARGET_FILEPATH_EMPTY,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1)\n \n     def test_upload_file_remote_info_not_none_valid(self):\n+        \"\"\"\n+        Test assumes remote_file_info['download_url'] matches 'lorum_ipsum.txt'\n+        \"\"\"\n         with my_vcr.use_cassette('ohapi/cassettes/test_upload_file_' +\n                                  'remote_info_not_none_valid.yaml') as cass:\n-            with patch('%s.open' % __name__, mock_open(), create=True):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    try:\n-                        filename = 'foo'\n-\n-                        def fake_stat(arg):\n-                            if arg == filename:\n-                                faked = list(orig_os_stat('/tmp'))\n-                                faked[stat.ST_SIZE] = len('some stuff')\n-                                return stat_result(faked)\n-                            else:\n-                                return orig_os_stat(arg)\n-                        orig_os_stat = os.stat\n-                        os.stat = fake_stat\n-                        with open('foo', 'w') as h:\n-                            h.write('some stuff')\n-                        upload_file(target_filepath='foo',\n-                                    metadata=FILE_METADATA,\n-                                    access_token=ACCESS_TOKEN,\n-                                    project_member_id=VALID_PMI1,\n-                                    remote_file_info=REMOTE_FILE_INFO)\n-                        self.assertEqual(cass.responses[0][\n-                                         \"status\"][\"code\"], 200)\n-                        self.assertEqual(cass.responses[1][\n-                                         \"status\"][\"code\"], 201)\n-                        self.assertEqual(cass.responses[1][\"body\"][\"string\"]\n-                                         .decode('utf-8'),\n-                                         '{\"id\": \"file_id\"}')\n-                    finally:\n-                        os.stat = orig_os_stat\n+            upload_file(target_filepath=TARGET_FILEPATH,\n+                        metadata=FILE_METADATA,\n+                        access_token=ACCESS_TOKEN,\n+                        project_member_id=VALID_PMI1,\n+                        remote_file_info=REMOTE_FILE_INFO)\n+            self.assertEqual(cass.responses[0][\n+                             \"status\"][\"code\"], 200)\n+            self.assertEqual(cass.responses[0][\n+                             \"headers\"][\"Content-Length\"], ['446'])\n \n+    @my_vcr.use_cassette()\n     def test_upload_file_remote_info_not_none_invalid_access_token(self):\n-        with my_vcr.use_cassette('ohapi/cassettes/test_upload_file_remote' +\n-                                 '_info_not_none_invalid_access_' +\n-                                 'token.yaml') as cass:\n-            with self.assertRaises(Exception):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    upload_file(target_filepath='foo',\n-                                metadata=FILE_METADATA,\n-                                access_token=ACCESS_TOKEN_INVALID,\n-                                project_member_id=VALID_PMI1,\n-                                remote_file_info=REMOTE_FILE_INFO)\n-                    self.assertEqual(cass.responses[0][\n-                                     \"status\"][\"code\"], 200)\n-                    self.assertEqual(\n-                        cass.responses[1][\"body\"][\"string\"]\n-                        .decode('utf-8'),\n-                        '{\"detail\": \"Invalid token.\"}')\n+        \"\"\"\n+        Test assumes remote_file_info['download_url'] matches 'lorum_ipsum.txt'\n+        \"\"\"\n+        # Note: alternate file needed to trigger an attempted upload.\n+        self.assertRaisesRegexp(\n+            Exception, 'Invalid token', upload_file,\n+            target_filepath=TARGET_FILEPATH2,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN_INVALID,\n+            project_member_id=VALID_PMI1,\n+            remote_file_info=REMOTE_FILE_INFO)\n \n+    @my_vcr.use_cassette()\n     def test_upload_file_remote_info_not_none_expired_access_token(self):\n-        with my_vcr.use_cassette('ohapi/cassettes/test_upload_file_remote_' +\n-                                 'info_not_none_expired_access_' +\n-                                 'token.yaml') as cass:\n-            with self.assertRaises(Exception):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    upload_file(target_filepath='foo',\n-                                metadata=FILE_METADATA,\n-                                access_token=ACCESS_TOKEN_EXPIRED,\n-                                project_member_id=VALID_PMI1,\n-                                remote_file_info=REMOTE_FILE_INFO)\n-                    self.assertEqual(cass.responses[0][\n-                                     \"status\"][\"code\"], 200)\n-                    self.assertEqual(\n-                        cass.responses[1][\"body\"][\"string\"]\n-                        .decode('utf-8'),\n-                        '{\"detail\": \"Expired token.\"}')\n+        # Note: alternate file needed to trigger an attempted upload.\n+        self.assertRaisesRegexp(\n+            Exception, 'Expired token', upload_file,\n+            target_filepath=TARGET_FILEPATH2,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN_EXPIRED,\n+            project_member_id=VALID_PMI1,\n+            remote_file_info=REMOTE_FILE_INFO)\n \n+    @my_vcr.use_cassette()\n     def test_upload_file_empty_remote_info_not_none(self):\n-        with my_vcr.use_cassette('ohapi/cassettes/test_upload_file_empty_' +\n-                                 'remote_info_not_none.yaml') as cass:\n-            with self.assertRaises(Exception):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    upload_file(target_filepath='foo',\n-                                metadata=FILE_METADATA,\n-                                access_token=ACCESS_TOKEN,\n-                                project_member_id=VALID_PMI1,\n-                                remote_file_info=REMOTE_FILE_INFO)\n-                    self.assertEqual(cass.responses[0][\n-                                     \"status\"][\"code\"], 200)\n-                    self.assertEqual(\n-                        cass.responses[1][\"body\"][\"string\"]\n-                        .decode('utf-8'),\n-                        '{\"data_file\": [\"The submitted file is' +\n-                        ' empty.\"]}')\n+        self.assertRaisesRegexp(\n+            Exception, 'The submitted file is empty.', upload_file,\n+            target_filepath=TARGET_FILEPATH_EMPTY,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1,\n+            remote_file_info=REMOTE_FILE_INFO)\n \n     @my_vcr.use_cassette()\n     def test_upload_file_remote_info_not_none_matching_file_size(self):\n-        with patch('ohapi.api.open', mock_open(), create=True):\n-            self.assertRaises(Exception, upload_file,\n-                              target_filepath='foo',\n-                              metadata=FILE_METADATA,\n-                              access_token=ACCESS_TOKEN,\n-                              project_member_id=VALID_PMI1,\n-                              remote_file_info=REMOTE_FILE_INFO)\n+        result = upload_file(\n+            target_filepath=TARGET_FILEPATH,\n+            metadata=FILE_METADATA,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1,\n+            remote_file_info=REMOTE_FILE_INFO)\n+        self.assertRegexpMatches(\n+            result, 'remote exists with matching file size')\n \n+    @my_vcr.use_cassette()\n     def test_upload_file_remote_info_not_none_invalid_metadata_with_desc(self):\n-        with my_vcr.use_cassette('ohapi/cassettes/test_upload_file_remote_' +\n-                                 'info_not_none_invalid_metadata_with_' +\n-                                 'desc.yaml') as cass:\n-            with self.assertRaises(Exception):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    upload_file(\n-                        target_filepath='foo',\n-                        metadata=FILE_METADATA_INVALID_WITH_DESC,\n-                        access_token=ACCESS_TOKEN,\n-                        project_member_id=VALID_PMI1,\n-                        remote_file_info=REMOTE_FILE_INFO)\n-                    self.assertEqual(cass.responses[0][\n-                                     \"status\"][\"code\"], 200)\n-                    self.assertEqual(\n-                        cass.responses[1][\"body\"][\"string\"]\n-                        .decode('utf-8'),\n-                        '{\"metadata\":[\"\\\\\"tags\\\\\" is a required ' +\n-                        'field of the metadata\"]}')\n+        # Note: alternate file needed to trigger an attempted upload.\n+        self.assertRaisesRegexp(\n+            Exception, '\"tags\" is a required field of the metadata',\n+            upload_file,\n+            target_filepath=TARGET_FILEPATH2,\n+            metadata=FILE_METADATA_INVALID_WITH_DESC,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1,\n+            remote_file_info=REMOTE_FILE_INFO)\n \n+    @my_vcr.use_cassette()\n     def test_upload_file_remote_info_not_none_invalid_metadata(self):\n-        with my_vcr.use_cassette(\n-                'ohapi/cassettes/test_upload_file_remote_in' +\n-                'fo_not_none_invalid_metadata.yaml') as cass:\n-            with self.assertRaises(Exception):\n-                with patch('ohapi.api.open', mock_open(), create=True):\n-                    upload_file(target_filepath='foo',\n-                                metadata=FILE_METADATA_INVALID,\n-                                access_token=ACCESS_TOKEN,\n-                                project_member_id=VALID_PMI1,\n-                                remote_file_info=REMOTE_FILE_INFO)\n-                    self.assertEqual(cass.responses[0][\n-                                     \"status\"][\"code\"], 200)\n-                    self.assertEqual(\n-                        cass.responses[1][\"body\"][\"string\"]\n-                        .decode('utf-8'),\n-                        '{\"metadata\":[\"\\\\\"description\\\\\" is a ' +\n-                        'required field of the metadata\"]}')\n+        self.assertRaisesRegexp(\n+            Exception, '\"description\" is a required field of the metadata',\n+            upload_file,\n+            target_filepath=TARGET_FILEPATH2,\n+            metadata=FILE_METADATA_INVALID,\n+            access_token=ACCESS_TOKEN,\n+            project_member_id=VALID_PMI1,","path":"ohapi/tests/test_api.py","position":449,"original_position":449,"commit_id":"b9b5e9ce862143ebe291f6ff20f6239c6eaf7991","original_commit_id":"b9b5e9ce862143ebe291f6ff20f6239c6eaf7991","user":{"login":"houndci-bot","id":6697940,"node_id":"MDQ6VXNlcjY2OTc5NDA=","avatar_url":"https://avatars0.githubusercontent.com/u/6697940?v=4","gravatar_id":"","url":"https://api.github.com/users/houndci-bot","html_url":"https://github.com/houndci-bot","followers_url":"https://api.github.com/users/houndci-bot/followers","following_url":"https://api.github.com/users/houndci-bot/following{/other_user}","gists_url":"https://api.github.com/users/houndci-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci-bot/subscriptions","organizations_url":"https://api.github.com/users/houndci-bot/orgs","repos_url":"https://api.github.com/users/houndci-bot/repos","events_url":"https://api.github.com/users/houndci-bot/events{/privacy}","received_events_url":"https://api.github.com/users/houndci-bot/received_events","type":"User","site_admin":false},"body":"undefined name 'VALID_PMI1'","created_at":"2018-07-03T05:46:30Z","updated_at":"2018-07-03T05:46:32Z","html_url":"https://github.com/OpenHumans/open-humans-api/pull/89#discussion_r199689109","pull_request_url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/comments/199689109"},"html":{"href":"https://github.com/OpenHumans/open-humans-api/pull/89#discussion_r199689109"},"pull_request":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89"}}},"pull_request":{"url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89","id":198855566,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk4ODU1NTY2","html_url":"https://github.com/OpenHumans/open-humans-api/pull/89","diff_url":"https://github.com/OpenHumans/open-humans-api/pull/89.diff","patch_url":"https://github.com/OpenHumans/open-humans-api/pull/89.patch","issue_url":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/89","number":89,"state":"open","locked":false,"title":"Enable stream upload","user":{"login":"madprime","id":82631,"node_id":"MDQ6VXNlcjgyNjMx","avatar_url":"https://avatars3.githubusercontent.com/u/82631?v=4","gravatar_id":"","url":"https://api.github.com/users/madprime","html_url":"https://github.com/madprime","followers_url":"https://api.github.com/users/madprime/followers","following_url":"https://api.github.com/users/madprime/following{/other_user}","gists_url":"https://api.github.com/users/madprime/gists{/gist_id}","starred_url":"https://api.github.com/users/madprime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madprime/subscriptions","organizations_url":"https://api.github.com/users/madprime/orgs","repos_url":"https://api.github.com/users/madprime/repos","events_url":"https://api.github.com/users/madprime/events{/privacy}","received_events_url":"https://api.github.com/users/madprime/received_events","type":"User","site_admin":false},"body":"## General Checkups\r\n- [x] Have you checked that there aren't other open pull requests for the same issue/update/change?\r\n- [x] If your code includes new features and not just bug fixes/copy editing: Did you include new tests?\r\n\r\n## Description\r\nAdd stream function using the direct-to-S3 upload, which is the more\r\ngenerally usable API endpoint as it can handle large data. Switch all\r\nother file upload functions to act as a wrapper to this. (This change\r\nchanges upload_file to be synonymous to upload_aws.)\r\n    \r\nSwitch file upload testing to use example files included in the repo,\r\nand add test for upload_stream.\r\n\r\n## Related Issue\r\nCloses #87.\r\n\r\n## Example\r\n```\r\nwith open('myfile', 'rb') as testfile:\r\n    testdata = testfile.read()\r\n    stream = io.BytesIO(testdata)\r\nupload_stream(\r\n    stream=stream,\r\n    filename='myfile',\r\n    metadata={'tags': ['testfile'], 'description': 'Test file'},\r\n    access_token=ACCESS_TOKEN,\r\n    project_member_id=PROJ_MEMBER_ID)\r\n```","created_at":"2018-07-03T05:46:20Z","updated_at":"2018-07-03T05:46:32Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89/commits","review_comments_url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89/comments","review_comment_url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/comments{/number}","comments_url":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/89/comments","statuses_url":"https://api.github.com/repos/OpenHumans/open-humans-api/statuses/b9b5e9ce862143ebe291f6ff20f6239c6eaf7991","head":{"label":"madprime:enable-stream-upload","ref":"enable-stream-upload","sha":"b9b5e9ce862143ebe291f6ff20f6239c6eaf7991","user":{"login":"madprime","id":82631,"node_id":"MDQ6VXNlcjgyNjMx","avatar_url":"https://avatars3.githubusercontent.com/u/82631?v=4","gravatar_id":"","url":"https://api.github.com/users/madprime","html_url":"https://github.com/madprime","followers_url":"https://api.github.com/users/madprime/followers","following_url":"https://api.github.com/users/madprime/following{/other_user}","gists_url":"https://api.github.com/users/madprime/gists{/gist_id}","starred_url":"https://api.github.com/users/madprime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madprime/subscriptions","organizations_url":"https://api.github.com/users/madprime/orgs","repos_url":"https://api.github.com/users/madprime/repos","events_url":"https://api.github.com/users/madprime/events{/privacy}","received_events_url":"https://api.github.com/users/madprime/received_events","type":"User","site_admin":false},"repo":{"id":122413337,"node_id":"MDEwOlJlcG9zaXRvcnkxMjI0MTMzMzc=","name":"open-humans-api","full_name":"madprime/open-humans-api","owner":{"login":"madprime","id":82631,"node_id":"MDQ6VXNlcjgyNjMx","avatar_url":"https://avatars3.githubusercontent.com/u/82631?v=4","gravatar_id":"","url":"https://api.github.com/users/madprime","html_url":"https://github.com/madprime","followers_url":"https://api.github.com/users/madprime/followers","following_url":"https://api.github.com/users/madprime/following{/other_user}","gists_url":"https://api.github.com/users/madprime/gists{/gist_id}","starred_url":"https://api.github.com/users/madprime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madprime/subscriptions","organizations_url":"https://api.github.com/users/madprime/orgs","repos_url":"https://api.github.com/users/madprime/repos","events_url":"https://api.github.com/users/madprime/events{/privacy}","received_events_url":"https://api.github.com/users/madprime/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/madprime/open-humans-api","description":"Tools to work with our APIs.","fork":true,"url":"https://api.github.com/repos/madprime/open-humans-api","forks_url":"https://api.github.com/repos/madprime/open-humans-api/forks","keys_url":"https://api.github.com/repos/madprime/open-humans-api/keys{/key_id}","collaborators_url":"https://api.github.com/repos/madprime/open-humans-api/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/madprime/open-humans-api/teams","hooks_url":"https://api.github.com/repos/madprime/open-humans-api/hooks","issue_events_url":"https://api.github.com/repos/madprime/open-humans-api/issues/events{/number}","events_url":"https://api.github.com/repos/madprime/open-humans-api/events","assignees_url":"https://api.github.com/repos/madprime/open-humans-api/assignees{/user}","branches_url":"https://api.github.com/repos/madprime/open-humans-api/branches{/branch}","tags_url":"https://api.github.com/repos/madprime/open-humans-api/tags","blobs_url":"https://api.github.com/repos/madprime/open-humans-api/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/madprime/open-humans-api/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/madprime/open-humans-api/git/refs{/sha}","trees_url":"https://api.github.com/repos/madprime/open-humans-api/git/trees{/sha}","statuses_url":"https://api.github.com/repos/madprime/open-humans-api/statuses/{sha}","languages_url":"https://api.github.com/repos/madprime/open-humans-api/languages","stargazers_url":"https://api.github.com/repos/madprime/open-humans-api/stargazers","contributors_url":"https://api.github.com/repos/madprime/open-humans-api/contributors","subscribers_url":"https://api.github.com/repos/madprime/open-humans-api/subscribers","subscription_url":"https://api.github.com/repos/madprime/open-humans-api/subscription","commits_url":"https://api.github.com/repos/madprime/open-humans-api/commits{/sha}","git_commits_url":"https://api.github.com/repos/madprime/open-humans-api/git/commits{/sha}","comments_url":"https://api.github.com/repos/madprime/open-humans-api/comments{/number}","issue_comment_url":"https://api.github.com/repos/madprime/open-humans-api/issues/comments{/number}","contents_url":"https://api.github.com/repos/madprime/open-humans-api/contents/{+path}","compare_url":"https://api.github.com/repos/madprime/open-humans-api/compare/{base}...{head}","merges_url":"https://api.github.com/repos/madprime/open-humans-api/merges","archive_url":"https://api.github.com/repos/madprime/open-humans-api/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/madprime/open-humans-api/downloads","issues_url":"https://api.github.com/repos/madprime/open-humans-api/issues{/number}","pulls_url":"https://api.github.com/repos/madprime/open-humans-api/pulls{/number}","milestones_url":"https://api.github.com/repos/madprime/open-humans-api/milestones{/number}","notifications_url":"https://api.github.com/repos/madprime/open-humans-api/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/madprime/open-humans-api/labels{/name}","releases_url":"https://api.github.com/repos/madprime/open-humans-api/releases{/id}","deployments_url":"https://api.github.com/repos/madprime/open-humans-api/deployments","created_at":"2018-02-22T00:55:50Z","updated_at":"2018-03-29T22:42:02Z","pushed_at":"2018-07-03T05:34:07Z","git_url":"git://github.com/madprime/open-humans-api.git","ssh_url":"git@github.com:madprime/open-humans-api.git","clone_url":"https://github.com/madprime/open-humans-api.git","svn_url":"https://github.com/madprime/open-humans-api","homepage":null,"size":151,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"OpenHumans:master","ref":"master","sha":"c7b5be6466a41c60f4318613b7467355eba77524","user":{"login":"OpenHumans","id":3341265,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzNDEyNjU=","avatar_url":"https://avatars3.githubusercontent.com/u/3341265?v=4","gravatar_id":"","url":"https://api.github.com/users/OpenHumans","html_url":"https://github.com/OpenHumans","followers_url":"https://api.github.com/users/OpenHumans/followers","following_url":"https://api.github.com/users/OpenHumans/following{/other_user}","gists_url":"https://api.github.com/users/OpenHumans/gists{/gist_id}","starred_url":"https://api.github.com/users/OpenHumans/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OpenHumans/subscriptions","organizations_url":"https://api.github.com/users/OpenHumans/orgs","repos_url":"https://api.github.com/users/OpenHumans/repos","events_url":"https://api.github.com/users/OpenHumans/events{/privacy}","received_events_url":"https://api.github.com/users/OpenHumans/received_events","type":"Organization","site_admin":false},"repo":{"id":65763791,"node_id":"MDEwOlJlcG9zaXRvcnk2NTc2Mzc5MQ==","name":"open-humans-api","full_name":"OpenHumans/open-humans-api","owner":{"login":"OpenHumans","id":3341265,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzNDEyNjU=","avatar_url":"https://avatars3.githubusercontent.com/u/3341265?v=4","gravatar_id":"","url":"https://api.github.com/users/OpenHumans","html_url":"https://github.com/OpenHumans","followers_url":"https://api.github.com/users/OpenHumans/followers","following_url":"https://api.github.com/users/OpenHumans/following{/other_user}","gists_url":"https://api.github.com/users/OpenHumans/gists{/gist_id}","starred_url":"https://api.github.com/users/OpenHumans/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OpenHumans/subscriptions","organizations_url":"https://api.github.com/users/OpenHumans/orgs","repos_url":"https://api.github.com/users/OpenHumans/repos","events_url":"https://api.github.com/users/OpenHumans/events{/privacy}","received_events_url":"https://api.github.com/users/OpenHumans/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/OpenHumans/open-humans-api","description":"Tools to work with our APIs.","fork":false,"url":"https://api.github.com/repos/OpenHumans/open-humans-api","forks_url":"https://api.github.com/repos/OpenHumans/open-humans-api/forks","keys_url":"https://api.github.com/repos/OpenHumans/open-humans-api/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OpenHumans/open-humans-api/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OpenHumans/open-humans-api/teams","hooks_url":"https://api.github.com/repos/OpenHumans/open-humans-api/hooks","issue_events_url":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/events{/number}","events_url":"https://api.github.com/repos/OpenHumans/open-humans-api/events","assignees_url":"https://api.github.com/repos/OpenHumans/open-humans-api/assignees{/user}","branches_url":"https://api.github.com/repos/OpenHumans/open-humans-api/branches{/branch}","tags_url":"https://api.github.com/repos/OpenHumans/open-humans-api/tags","blobs_url":"https://api.github.com/repos/OpenHumans/open-humans-api/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OpenHumans/open-humans-api/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OpenHumans/open-humans-api/git/refs{/sha}","trees_url":"https://api.github.com/repos/OpenHumans/open-humans-api/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OpenHumans/open-humans-api/statuses/{sha}","languages_url":"https://api.github.com/repos/OpenHumans/open-humans-api/languages","stargazers_url":"https://api.github.com/repos/OpenHumans/open-humans-api/stargazers","contributors_url":"https://api.github.com/repos/OpenHumans/open-humans-api/contributors","subscribers_url":"https://api.github.com/repos/OpenHumans/open-humans-api/subscribers","subscription_url":"https://api.github.com/repos/OpenHumans/open-humans-api/subscription","commits_url":"https://api.github.com/repos/OpenHumans/open-humans-api/commits{/sha}","git_commits_url":"https://api.github.com/repos/OpenHumans/open-humans-api/git/commits{/sha}","comments_url":"https://api.github.com/repos/OpenHumans/open-humans-api/comments{/number}","issue_comment_url":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/comments{/number}","contents_url":"https://api.github.com/repos/OpenHumans/open-humans-api/contents/{+path}","compare_url":"https://api.github.com/repos/OpenHumans/open-humans-api/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OpenHumans/open-humans-api/merges","archive_url":"https://api.github.com/repos/OpenHumans/open-humans-api/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OpenHumans/open-humans-api/downloads","issues_url":"https://api.github.com/repos/OpenHumans/open-humans-api/issues{/number}","pulls_url":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls{/number}","milestones_url":"https://api.github.com/repos/OpenHumans/open-humans-api/milestones{/number}","notifications_url":"https://api.github.com/repos/OpenHumans/open-humans-api/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OpenHumans/open-humans-api/labels{/name}","releases_url":"https://api.github.com/repos/OpenHumans/open-humans-api/releases{/id}","deployments_url":"https://api.github.com/repos/OpenHumans/open-humans-api/deployments","created_at":"2016-08-15T20:44:53Z","updated_at":"2018-07-02T21:18:38Z","pushed_at":"2018-07-03T05:46:21Z","git_url":"git://github.com/OpenHumans/open-humans-api.git","ssh_url":"git@github.com:OpenHumans/open-humans-api.git","clone_url":"https://github.com/OpenHumans/open-humans-api.git","svn_url":"https://github.com/OpenHumans/open-humans-api","homepage":"http://open-humans-api.readthedocs.io/","size":171,"stargazers_count":7,"watchers_count":7,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"archived":false,"open_issues_count":6,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":13,"open_issues":6,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89"},"html":{"href":"https://github.com/OpenHumans/open-humans-api/pull/89"},"issue":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/89"},"comments":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/issues/89/comments"},"review_comments":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89/comments"},"review_comment":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/pulls/89/commits"},"statuses":{"href":"https://api.github.com/repos/OpenHumans/open-humans-api/statuses/b9b5e9ce862143ebe291f6ff20f6239c6eaf7991"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"houndci-bot"}}