{"action":"created","comment":{"url":"https://api.github.com/repos/codalab/codalab-cli/pulls/comments/63115224","id":63115224,"diff_hunk":"@@ -0,0 +1,299 @@\n+from collections import defaultdict\n+import urllib2\n+\n+from codalab.client.bundle_client import BundleClient\n+from codalab.client.rest_client import RestClient\n+from codalab.common import http_error_to_exception\n+\n+\n+# TODO(sckoo): deal with more error cases\n+def wrap_exception(f):\n+    def wrapper(*args, **kwargs):\n+        try:\n+            return f(*args, **kwargs)\n+        except urllib2.HTTPError as err:\n+            raise http_error_to_exception(err.code, err.read())\n+    return wrapper\n+\n+\n+def unpack_document(document):\n+    \"\"\"\n+    Unpack a JSON API document into a plain dict, with the relationship keys\n+    wired up to the 'included' resources. Supports multiple levels of\n+    relationships. Relationships that do not have a corresponding object in the\n+    'included' objects will be filled in with a dict that only has the 'id'\n+    key. For example:\n+\n+     >> unpack_document({\n+            'data': {\n+                'id': '123',\n+                'type: 'bundles',\n+                'attributes': {\n+                    'name': 'hello'\n+                },\n+                'relationships': {\n+                    'owner': {\n+                        'data': {\n+                            'id': '345',\n+                            'type': 'users'\n+                        }\n+                    },\n+                    'parent': {\n+                        'data': {\n+                            'id': '567',\n+                            'type': 'bundles'\n+                        }\n+                    }\n+                },\n+            },\n+            'included': [\n+                {\n+                    'type': 'users':\n+                    'id': '345',\n+                    'attributes': {\n+                        'name': 'percy',\n+                        'affiliation': 'stanford'\n+                    }\n+                }\n+            ]\n+        })\n+\n+     => {\n+            'owner': {\n+                'id': '345',\n+                'name': 'percy',\n+                'affiliation: 'stanford'\n+            },\n+            'parent': {\n+                'id': '567'\n+            },\n+            'id': '123',\n+            'name': 'hello'\n+        }\n+\n+    :param document: the JSON-API payload as a dict\n+    :return:\n+    \"\"\"\n+    def unpack_object(obj_data):\n+        obj = obj_data['attributes']\n+        obj['id'] = obj_data['id']\n+\n+        if 'meta' in obj_data:\n+            obj['meta'] = obj_data['meta']\n+\n+        for key, relationship in obj_data.get('relationships', {}).iteritems():\n+            if isinstance(relationship['data'], list):\n+                obj[key] = []\n+                for linkage in relationship['data']:\n+                    if linkage['id'] in included[linkage['type']].keys():\n+                        obj[key].append(unpack_object(included[linkage['type']][linkage['id']]))\n+                    else:\n+                        obj[key].append({'id': linkage['id']})\n+            else:\n+                linkage = relationship['data']\n+                if linkage is None:\n+                    obj[key] = None\n+                elif linkage['id'] in included[linkage['type']]:\n+                    obj[key] = unpack_object(included[linkage['type']][linkage['id']])\n+                else:\n+                    obj[key] = {'id': linkage['id']}\n+\n+        return obj\n+\n+    included = defaultdict(dict)\n+    for resource in document.get('included', []):\n+        included[resource['type']][resource['id']] = resource\n+\n+    data = document.get('data', None)\n+    if isinstance(data, list):\n+        result = [unpack_object(data) for data in document['data']]\n+    elif isinstance(data, dict):\n+        result = unpack_object(document['data'])\n+    else:\n+        result = {}\n+\n+    # Include meta\n+    # (Warning: this may overwrite meta present at the resource object level.)\n+    if 'meta' in document:\n+        result['meta'] = document['meta']\n+\n+    return result\n+\n+\n+def pack_document(objects, type_):\n+    \"\"\"\n+    Pack resource object(s) into a JSON API request document.\n+    References to relationships should be indicated by a placeholder\n+    JsonApiRelationship object. For example:\n+\n+     >> pack_document({\n+            'owner': JsonApiRelationship('users', '345'),\n+            'id': '123',\n+            'name': 'hello'\n+        }, 'bundles')\n+\n+     => {\n+            'data': {\n+                'id': '123',\n+                'type': 'bundles',\n+                'attributes': {\n+                    'name': 'hello'\n+                },\n+                'relationships': {\n+                    'owner': {\n+                        'data': {\n+                            'id': '345',\n+                            'type': 'users'\n+                        }\n+                    }\n+                },\n+            }\n+        }\n+\n+    :param objects: a dict or list of dicts representing resources\n+    :param type_: resource type\n+    :return:\n+    \"\"\"\n+    def pack_object(obj):\n+        packed_obj = {'type': type_}\n+        attributes = {}\n+        relationships = {}\n+        for key, value in obj.iteritems():\n+            if isinstance(value, JsonApiRelationship):\n+                relationships[key] = {\n+                    'data': {\n+                        'id': value.id_,\n+                        'type': value.type_,\n+                    }\n+                }\n+            elif key == 'id':\n+                packed_obj['id'] = value\n+            else:\n+                attributes[key] = value\n+\n+        if attributes:\n+            packed_obj['attributes'] = attributes\n+\n+        if relationships:\n+            packed_obj['relationships'] = relationships\n+\n+        return packed_obj\n+\n+    if isinstance(objects, list):\n+        packed_objects = [pack_object(obj) for obj in objects]\n+    else:\n+        packed_objects = pack_object(objects)\n+\n+    return {\n+        'data': packed_objects\n+    }\n+\n+\n+class JsonApiRelationship(object):\n+    \"\"\"\n+    Placeholder for a relationship to another resource.\n+    Used to build requests to create or update a resource.\n+\n+    For example, to update the owner of a bundle:\n+        client.update('bundles', uuid, data={\n+            'owner': JsonApiRelationship('users', owner_id)\n+        })\n+    \"\"\"\n+    def __init__(self, type_, id_):\n+        self.type_ = type_\n+        self.id_ = id_\n+\n+\n+class JsonApiClient(RestClient, BundleClient):","path":"codalab/client/json_api_client.py","position":207,"original_position":207,"commit_id":"ac669d7efb27b72da785b8547bc71ad55045d928","original_commit_id":"ac669d7efb27b72da785b8547bc71ad55045d928","user":{"login":"klopyrev","id":5249090,"avatar_url":"https://avatars.githubusercontent.com/u/5249090?v=3","gravatar_id":"","url":"https://api.github.com/users/klopyrev","html_url":"https://github.com/klopyrev","followers_url":"https://api.github.com/users/klopyrev/followers","following_url":"https://api.github.com/users/klopyrev/following{/other_user}","gists_url":"https://api.github.com/users/klopyrev/gists{/gist_id}","starred_url":"https://api.github.com/users/klopyrev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klopyrev/subscriptions","organizations_url":"https://api.github.com/users/klopyrev/orgs","repos_url":"https://api.github.com/users/klopyrev/repos","events_url":"https://api.github.com/users/klopyrev/events{/privacy}","received_events_url":"https://api.github.com/users/klopyrev/received_events","type":"User","site_admin":false},"body":"Why is this a BundleClient?","created_at":"2016-05-12T23:43:32Z","updated_at":"2016-05-12T23:43:32Z","html_url":"https://github.com/codalab/codalab-cli/pull/431#discussion_r63115224","pull_request_url":"https://api.github.com/repos/codalab/codalab-cli/pulls/431","_links":{"self":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/comments/63115224"},"html":{"href":"https://github.com/codalab/codalab-cli/pull/431#discussion_r63115224"},"pull_request":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/431"}}},"pull_request":{"url":"https://api.github.com/repos/codalab/codalab-cli/pulls/431","id":69782140,"html_url":"https://github.com/codalab/codalab-cli/pull/431","diff_url":"https://github.com/codalab/codalab-cli/pull/431.diff","patch_url":"https://github.com/codalab/codalab-cli/pull/431.patch","issue_url":"https://api.github.com/repos/codalab/codalab-cli/issues/431","number":431,"state":"open","locked":false,"title":"Add RestClient and JsonApiClient","user":{"login":"kashizui","id":614837,"avatar_url":"https://avatars.githubusercontent.com/u/614837?v=3","gravatar_id":"","url":"https://api.github.com/users/kashizui","html_url":"https://github.com/kashizui","followers_url":"https://api.github.com/users/kashizui/followers","following_url":"https://api.github.com/users/kashizui/following{/other_user}","gists_url":"https://api.github.com/users/kashizui/gists{/gist_id}","starred_url":"https://api.github.com/users/kashizui/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kashizui/subscriptions","organizations_url":"https://api.github.com/users/kashizui/orgs","repos_url":"https://api.github.com/users/kashizui/repos","events_url":"https://api.github.com/users/kashizui/events{/privacy}","received_events_url":"https://api.github.com/users/kashizui/received_events","type":"User","site_admin":false},"body":"Factor out general methods from BundleServiceClient into RestClient and\r\nadd a new JsonApiClient to make calls to JSON API endpoints.\r\n\r\n@klopyrev ","created_at":"2016-05-12T05:41:22Z","updated_at":"2016-05-12T23:43:32Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f82ad678738d4bb47bbb44e4ef7835ac0527f38b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/codalab/codalab-cli/pulls/431/commits","review_comments_url":"https://api.github.com/repos/codalab/codalab-cli/pulls/431/comments","review_comment_url":"https://api.github.com/repos/codalab/codalab-cli/pulls/comments{/number}","comments_url":"https://api.github.com/repos/codalab/codalab-cli/issues/431/comments","statuses_url":"https://api.github.com/repos/codalab/codalab-cli/statuses/ac669d7efb27b72da785b8547bc71ad55045d928","head":{"label":"codalab:kashizui/rest/a","ref":"kashizui/rest/a","sha":"ac669d7efb27b72da785b8547bc71ad55045d928","user":{"login":"codalab","id":4633552,"avatar_url":"https://avatars.githubusercontent.com/u/4633552?v=3","gravatar_id":"","url":"https://api.github.com/users/codalab","html_url":"https://github.com/codalab","followers_url":"https://api.github.com/users/codalab/followers","following_url":"https://api.github.com/users/codalab/following{/other_user}","gists_url":"https://api.github.com/users/codalab/gists{/gist_id}","starred_url":"https://api.github.com/users/codalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codalab/subscriptions","organizations_url":"https://api.github.com/users/codalab/orgs","repos_url":"https://api.github.com/users/codalab/repos","events_url":"https://api.github.com/users/codalab/events{/privacy}","received_events_url":"https://api.github.com/users/codalab/received_events","type":"User","site_admin":false},"repo":{"id":27352490,"name":"codalab-cli","full_name":"codalab/codalab-cli","owner":{"login":"codalab","id":4633552,"avatar_url":"https://avatars.githubusercontent.com/u/4633552?v=3","gravatar_id":"","url":"https://api.github.com/users/codalab","html_url":"https://github.com/codalab","followers_url":"https://api.github.com/users/codalab/followers","following_url":"https://api.github.com/users/codalab/following{/other_user}","gists_url":"https://api.github.com/users/codalab/gists{/gist_id}","starred_url":"https://api.github.com/users/codalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codalab/subscriptions","organizations_url":"https://api.github.com/users/codalab/orgs","repos_url":"https://api.github.com/users/codalab/repos","events_url":"https://api.github.com/users/codalab/events{/privacy}","received_events_url":"https://api.github.com/users/codalab/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/codalab/codalab-cli","description":"Bundle service and command-line interface for supporting worksheets in CodaLab.","fork":false,"url":"https://api.github.com/repos/codalab/codalab-cli","forks_url":"https://api.github.com/repos/codalab/codalab-cli/forks","keys_url":"https://api.github.com/repos/codalab/codalab-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/codalab/codalab-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/codalab/codalab-cli/teams","hooks_url":"https://api.github.com/repos/codalab/codalab-cli/hooks","issue_events_url":"https://api.github.com/repos/codalab/codalab-cli/issues/events{/number}","events_url":"https://api.github.com/repos/codalab/codalab-cli/events","assignees_url":"https://api.github.com/repos/codalab/codalab-cli/assignees{/user}","branches_url":"https://api.github.com/repos/codalab/codalab-cli/branches{/branch}","tags_url":"https://api.github.com/repos/codalab/codalab-cli/tags","blobs_url":"https://api.github.com/repos/codalab/codalab-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/codalab/codalab-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/codalab/codalab-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/codalab/codalab-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/codalab/codalab-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/codalab/codalab-cli/languages","stargazers_url":"https://api.github.com/repos/codalab/codalab-cli/stargazers","contributors_url":"https://api.github.com/repos/codalab/codalab-cli/contributors","subscribers_url":"https://api.github.com/repos/codalab/codalab-cli/subscribers","subscription_url":"https://api.github.com/repos/codalab/codalab-cli/subscription","commits_url":"https://api.github.com/repos/codalab/codalab-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/codalab/codalab-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/codalab/codalab-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/codalab/codalab-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/codalab/codalab-cli/contents/{+path}","compare_url":"https://api.github.com/repos/codalab/codalab-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/codalab/codalab-cli/merges","archive_url":"https://api.github.com/repos/codalab/codalab-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/codalab/codalab-cli/downloads","issues_url":"https://api.github.com/repos/codalab/codalab-cli/issues{/number}","pulls_url":"https://api.github.com/repos/codalab/codalab-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/codalab/codalab-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/codalab/codalab-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/codalab/codalab-cli/labels{/name}","releases_url":"https://api.github.com/repos/codalab/codalab-cli/releases{/id}","deployments_url":"https://api.github.com/repos/codalab/codalab-cli/deployments","created_at":"2014-11-30T22:33:18Z","updated_at":"2016-01-19T21:16:59Z","pushed_at":"2016-05-12T09:05:51Z","git_url":"git://github.com/codalab/codalab-cli.git","ssh_url":"git@github.com:codalab/codalab-cli.git","clone_url":"https://github.com/codalab/codalab-cli.git","svn_url":"https://github.com/codalab/codalab-cli","homepage":null,"size":2928,"stargazers_count":10,"watchers_count":10,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"open_issues_count":56,"forks":13,"open_issues":56,"watchers":10,"default_branch":"master"}},"base":{"label":"codalab:develop","ref":"develop","sha":"70198034a5d43f47d71e1f1ba54f003bb0d253d0","user":{"login":"codalab","id":4633552,"avatar_url":"https://avatars.githubusercontent.com/u/4633552?v=3","gravatar_id":"","url":"https://api.github.com/users/codalab","html_url":"https://github.com/codalab","followers_url":"https://api.github.com/users/codalab/followers","following_url":"https://api.github.com/users/codalab/following{/other_user}","gists_url":"https://api.github.com/users/codalab/gists{/gist_id}","starred_url":"https://api.github.com/users/codalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codalab/subscriptions","organizations_url":"https://api.github.com/users/codalab/orgs","repos_url":"https://api.github.com/users/codalab/repos","events_url":"https://api.github.com/users/codalab/events{/privacy}","received_events_url":"https://api.github.com/users/codalab/received_events","type":"User","site_admin":false},"repo":{"id":27352490,"name":"codalab-cli","full_name":"codalab/codalab-cli","owner":{"login":"codalab","id":4633552,"avatar_url":"https://avatars.githubusercontent.com/u/4633552?v=3","gravatar_id":"","url":"https://api.github.com/users/codalab","html_url":"https://github.com/codalab","followers_url":"https://api.github.com/users/codalab/followers","following_url":"https://api.github.com/users/codalab/following{/other_user}","gists_url":"https://api.github.com/users/codalab/gists{/gist_id}","starred_url":"https://api.github.com/users/codalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codalab/subscriptions","organizations_url":"https://api.github.com/users/codalab/orgs","repos_url":"https://api.github.com/users/codalab/repos","events_url":"https://api.github.com/users/codalab/events{/privacy}","received_events_url":"https://api.github.com/users/codalab/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/codalab/codalab-cli","description":"Bundle service and command-line interface for supporting worksheets in CodaLab.","fork":false,"url":"https://api.github.com/repos/codalab/codalab-cli","forks_url":"https://api.github.com/repos/codalab/codalab-cli/forks","keys_url":"https://api.github.com/repos/codalab/codalab-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/codalab/codalab-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/codalab/codalab-cli/teams","hooks_url":"https://api.github.com/repos/codalab/codalab-cli/hooks","issue_events_url":"https://api.github.com/repos/codalab/codalab-cli/issues/events{/number}","events_url":"https://api.github.com/repos/codalab/codalab-cli/events","assignees_url":"https://api.github.com/repos/codalab/codalab-cli/assignees{/user}","branches_url":"https://api.github.com/repos/codalab/codalab-cli/branches{/branch}","tags_url":"https://api.github.com/repos/codalab/codalab-cli/tags","blobs_url":"https://api.github.com/repos/codalab/codalab-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/codalab/codalab-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/codalab/codalab-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/codalab/codalab-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/codalab/codalab-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/codalab/codalab-cli/languages","stargazers_url":"https://api.github.com/repos/codalab/codalab-cli/stargazers","contributors_url":"https://api.github.com/repos/codalab/codalab-cli/contributors","subscribers_url":"https://api.github.com/repos/codalab/codalab-cli/subscribers","subscription_url":"https://api.github.com/repos/codalab/codalab-cli/subscription","commits_url":"https://api.github.com/repos/codalab/codalab-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/codalab/codalab-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/codalab/codalab-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/codalab/codalab-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/codalab/codalab-cli/contents/{+path}","compare_url":"https://api.github.com/repos/codalab/codalab-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/codalab/codalab-cli/merges","archive_url":"https://api.github.com/repos/codalab/codalab-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/codalab/codalab-cli/downloads","issues_url":"https://api.github.com/repos/codalab/codalab-cli/issues{/number}","pulls_url":"https://api.github.com/repos/codalab/codalab-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/codalab/codalab-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/codalab/codalab-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/codalab/codalab-cli/labels{/name}","releases_url":"https://api.github.com/repos/codalab/codalab-cli/releases{/id}","deployments_url":"https://api.github.com/repos/codalab/codalab-cli/deployments","created_at":"2014-11-30T22:33:18Z","updated_at":"2016-01-19T21:16:59Z","pushed_at":"2016-05-12T09:05:51Z","git_url":"git://github.com/codalab/codalab-cli.git","ssh_url":"git@github.com:codalab/codalab-cli.git","clone_url":"https://github.com/codalab/codalab-cli.git","svn_url":"https://github.com/codalab/codalab-cli","homepage":null,"size":2928,"stargazers_count":10,"watchers_count":10,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"open_issues_count":56,"forks":13,"open_issues":56,"watchers":10,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/431"},"html":{"href":"https://github.com/codalab/codalab-cli/pull/431"},"issue":{"href":"https://api.github.com/repos/codalab/codalab-cli/issues/431"},"comments":{"href":"https://api.github.com/repos/codalab/codalab-cli/issues/431/comments"},"review_comments":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/431/comments"},"review_comment":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/codalab/codalab-cli/pulls/431/commits"},"statuses":{"href":"https://api.github.com/repos/codalab/codalab-cli/statuses/ac669d7efb27b72da785b8547bc71ad55045d928"}}}}