{"action":"created","comment":{"url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/comments/214320211","pull_request_review_id":151352069,"id":214320211,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNDMyMDIxMQ==","diff_hunk":"@@ -0,0 +1,233 @@\n+\"\"\"\n+Logic for transferring a subunit (aka XBlock, XModule) from Open edX to a Blockstore Bundle.\n+\"\"\"\n+from __future__ import absolute_import, print_function, unicode_literals\n+\n+import json\n+import logging\n+import os\n+from xml.etree import ElementTree\n+\n+import requests\n+from django.conf import settings\n+from fs.errors import ResourceNotFound\n+from fs.memoryfs import MemoryFS\n+from fs.wrapfs import WrapFS\n+from future.moves.urllib.parse import urljoin\n+\n+from . import compat\n+\n+LOG = logging.getLogger(__name__)\n+\n+\n+def transfer_to_blockstore(block_key, bundle_uuid):\n+    \"\"\"\n+    Transfer the given block (and its children) to the given Blockstore bundle.\n+    \"\"\"\n+    transfer_obj = TransferBlock(block_key, bundle_uuid)\n+    transfer_obj.transfer_block_to_bundle()\n+\n+\n+class TransferBlock(object):\n+    \"\"\"\n+    Transfers a single XBlock and its related files from Open edX to Blockstore.\n+\n+    Uploads the sharable interface of the Bundle in a manifest bundle.json file.\n+    \"\"\"\n+\n+    BUNDLE_VERSION = 1\n+\n+    def __init__(self, block_key, bundle_uuid):\n+        \"\"\"\n+        Store the block_key (UsageKey) and bundle_uuid (UUID).\n+        \"\"\"\n+        super(TransferBlock, self).__init__()\n+        self.block_key = block_key\n+        self.bundle_uuid = bundle_uuid\n+        self.bundle_files_url = urljoin(settings.BLOCKSTORE_API_URL,\n+                                        '/'.join(['bundles', str(self.bundle_uuid), 'files', '']))\n+        self.manifest = {\n+            'meta': {'version': self.BUNDLE_VERSION},\n+            'type': self.olx_type(self.block_key.block_type),\n+            'assets': [],\n+            'components': [],\n+            'dependencies': [],\n+        }\n+        self.assets = []\n+\n+        # Store the block_keys we've processed before, to prevent infinite recursion on Directed Acyclic Graphs (DAGs)\n+        self._block_olx_ok = {}\n+\n+    @staticmethod\n+    def olx_type(block_type):\n+        \"\"\"\n+        Return the content_type to set for the given block_key.\n+        \"\"\"\n+        return 'olx/{}'.format(block_type)\n+\n+    def transfer_block_to_bundle(self):\n+        \"\"\"\n+        Upload block-related files to the Bundle.\n+        \"\"\"\n+        LOG.debug('Transfer %s to Bundle <%s>', self.block_key, self.bundle_uuid)\n+        block = compat.modulestore().get_item(self.block_key)\n+        self.upload_olx(block)\n+        self.upload_assets()\n+        self.upload_manifest()\n+\n+    def upload_olx(self, block):\n+        \"\"\"\n+        Upload the OLX for the given block definition, recursing through children.\n+        \"\"\"\n+        block_key = block.location\n+        if block_key in self._block_olx_ok:\n+            # Prevent infinite recursion: don't re-create OLX for blocks that we've already done.\n+            return None\n+\n+        block_type = block_key.block_type\n+        if block_type in ('course', 'chapter', 'sequential', 'vertical'):\n+            # These blocks' definition_to_xml methods require a runtime, so we avoid calling them.\n+            olx_node = ElementTree.Element(block_type)\n+        else:\n+            try:\n+                filesystem = WrapFS(MemoryFS())\n+                olx_node = block.definition_to_xml(filesystem)\n+                self._upload_related_files(filesystem)\n+            except (NotImplementedError, AttributeError):\n+                LOG.warning(\"Block type not supported, skipping (%s)\", block_key)\n+                self._block_olx_ok[block_key] = False\n+                return None\n+            except ResourceNotFound as exc:\n+                LOG.error(\"Error fetching block OLX (%s): %s\", block_key, exc)\n+                self._block_olx_ok[block_key] = False\n+                return None\n+\n+        for child in block.get_children():\n+            LOG.debug('Add child %s', child.location)\n+            self.upload_olx(child)\n+            if self._block_olx_ok[child.location]:\n+                # If we've successfully processed this child's OLX,\n+                # include a reference to the child in the parent block.\n+                child_ref = ElementTree.Element(child.location.block_type)\n+                child_ref.set('url_name', child.url_name)\n+                olx_node.append(child_ref)\n+\n+        self._upload_olx(olx_node, block_key)\n+        self._block_olx_ok[block_key] = True\n+        return olx_node\n+\n+    def upload_assets(self):\n+        \"\"\"\n+        Upload the collected static asset files to the bundle.\n+        \"\"\"\n+        for (asset_path, asset_uri) in self.assets:\n+            content = compat.get_asset_content_from_path(self.block_key.course_key, asset_path)\n+            if content is None:\n+                LOG.error(\"Static asset not found: (%s, %s)\", asset_path, asset_uri)\n+            else:\n+                self._post_bundle_file(\n+                    path=asset_path,\n+                    data=content.data,\n+                    content_type=content.content_type,\n+                )","path":"openedx_blockstore_relay/transfer_data.py","position":null,"original_position":132,"commit_id":"5245ba8ba77c348976bf5b97c7ff675bd64bcf0c","original_commit_id":"7e1b50f9477cacda713f33a4c2e7295e498d172a","user":{"login":"pomegranited","id":7556571,"node_id":"MDQ6VXNlcjc1NTY1NzE=","avatar_url":"https://avatars3.githubusercontent.com/u/7556571?v=4","gravatar_id":"","url":"https://api.github.com/users/pomegranited","html_url":"https://github.com/pomegranited","followers_url":"https://api.github.com/users/pomegranited/followers","following_url":"https://api.github.com/users/pomegranited/following{/other_user}","gists_url":"https://api.github.com/users/pomegranited/gists{/gist_id}","starred_url":"https://api.github.com/users/pomegranited/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pomegranited/subscriptions","organizations_url":"https://api.github.com/users/pomegranited/orgs","repos_url":"https://api.github.com/users/pomegranited/repos","events_url":"https://api.github.com/users/pomegranited/events{/privacy}","received_events_url":"https://api.github.com/users/pomegranited/received_events","type":"User","site_admin":false},"body":"Done, cf d5eb6e4 .\r\n@symbolist Can you confirm that I understood this correctly?","created_at":"2018-08-31T11:10:42Z","updated_at":"2018-08-31T11:10:42Z","html_url":"https://github.com/open-craft/openedx-blockstore-relay/pull/1#discussion_r214320211","pull_request_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/comments/214320211"},"html":{"href":"https://github.com/open-craft/openedx-blockstore-relay/pull/1#discussion_r214320211"},"pull_request":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1"}},"in_reply_to_id":214111042},"pull_request":{"url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1","id":211292030,"node_id":"MDExOlB1bGxSZXF1ZXN0MjExMjkyMDMw","html_url":"https://github.com/open-craft/openedx-blockstore-relay/pull/1","diff_url":"https://github.com/open-craft/openedx-blockstore-relay/pull/1.diff","patch_url":"https://github.com/open-craft/openedx-blockstore-relay/pull/1.patch","issue_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/1","number":1,"state":"open","locked":false,"title":"LX-24 Adds management command to copy Units from Open edX to Blockstore ","user":{"login":"pomegranited","id":7556571,"node_id":"MDQ6VXNlcjc1NTY1NzE=","avatar_url":"https://avatars3.githubusercontent.com/u/7556571?v=4","gravatar_id":"","url":"https://api.github.com/users/pomegranited","html_url":"https://github.com/pomegranited","followers_url":"https://api.github.com/users/pomegranited/followers","following_url":"https://api.github.com/users/pomegranited/following{/other_user}","gists_url":"https://api.github.com/users/pomegranited/gists{/gist_id}","starred_url":"https://api.github.com/users/pomegranited/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pomegranited/subscriptions","organizations_url":"https://api.github.com/users/pomegranited/orgs","repos_url":"https://api.github.com/users/pomegranited/repos","events_url":"https://api.github.com/users/pomegranited/events{/privacy}","received_events_url":"https://api.github.com/users/pomegranited/received_events","type":"User","site_admin":false},"body":"**Description:** Adds a Open edX Django plugin application for transferring data between Open edX and Blockstore.\r\n\r\nInitial commit provides a management command which transfers a Unit (vertical) to a Blockstore Bundle.\r\n\r\n**JIRA:** LX-24, OC-5133\r\n\r\n**Dependencies:**\r\n\r\n* https://github.com/edx/edx-platform/pull/18855 -- for static asset parsing\r\n* https://github.com/open-craft/blockstore/pull/4 -- for API\r\n* https://github.com/open-craft/blockstore/pull/5 -- for bundle structure\r\n\r\n**Installation instructions:**\r\n\r\n1. Install this branch in the CMS shell.\r\n1. Run the Blockstore service so the CMS shell can access its REST API.\r\n1. Ensure that the URL for the Blockstore is present in `settings.BLOCKSTORE_API_URL` (see [`openedx_blockstore_relay.settings`](https://github.com/open-craft/openedx-blockstore-relay/tree/jill/transfer-unit/openedx_blockstore_relay/settings)).\r\n\r\n**Testing instructions:**\r\n\r\n1. Using the Blockstore REST API, create a Collection, and a Bundle.\r\n1. Test uploading the various supported types of blocks to the Bundle: `course`, `chapter`, `sequential`, `vertical`, `problem`, `video`, `html`.\r\n   Unsupported block types (e.g. `discussion`) are skipped, and show a warning.\r\n   Usage example:\r\n   ```\r\n   ./manage.py cms transfer_to_blockstore --settings=devstack \\\r\n      --block-key \"block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0\" \\\r\n      --bundle-uuid \"dddddddd-dddd-dddd-dddd-dddddddddddd\"\r\n   ```\r\n1. Visit the Blockstore Bundle API to see the updated bundles.\r\n   Each transfer should store in the Bundle:\r\n   * An `.olx` file for the top-level block and each child block\r\n   * All static course assets referenced by the block and its children.\r\n   * A `bundle.json` with:\r\n      * The `components` list will contain only the top-level `.olx` path.\r\n      * The `type` field will reflect the type of the top-level block.\r\n      * The `assets` and `dependencies` lists will be empty.\r\n\r\n**Author Notes & Concerns:**\r\n\r\n1. Because we have to upload each block file individually to the bundle, a full snapshot of the Bundle is made at each upload step.\r\n   It would be nice to be able to limit the number of snapshots taken, since these intermediate snapshots will be inherently incomplete and may be invalid.\r\n1. This data transfer process doesn't delete any existing files on the target Bundle.\r\n   The BundleFiles POST endpoint simply appends/updates files, and there's currently no way to do an atomic \"replace\".\r\n   To address this, the data transfer function would need to read the current BundleFiles list and track which files are unused after the bundle update, so that they can be deleted one by one.  This will  result in snapshots taken at every delete step, which is also undesirable.\r\n1. Would like to inline the related HTML referenced by HTML blocks, instead of storing separate `.olx` and a `.html` files.\r\n\r\n**Reviewers:**\r\n- [ ] @symbolist\r\n\r\nCC @ormsbee @bradenmacdonald \r\n\r\n**Merge checklist:**\r\n- [ ] All reviewers approved\r\n- [ ] CI build is green\r\n- [ ] Version bumped\r\n- [ ] Changelog record added\r\n- [ ] Documentation updated (not only docstrings)\r\n- [ ] Commits are squashed\r\n- [ ] PR author is listed in AUTHORS\r\n\r\n**Post merge:**\r\n- [ ] Create a tag\r\n- [ ] Check new version is pushed to PyPi after tag-triggered build is \r\n      finished.\r\n- [ ] Delete working branch (if not needed anymore)","created_at":"2018-08-28T03:37:36Z","updated_at":"2018-08-31T11:10:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e8ad87dd61e122546c7e495b631633b14b049f37","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1/commits","review_comments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1/comments","review_comment_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/comments{/number}","comments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/1/comments","statuses_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/statuses/5245ba8ba77c348976bf5b97c7ff675bd64bcf0c","head":{"label":"open-craft:jill/transfer-unit","ref":"jill/transfer-unit","sha":"5245ba8ba77c348976bf5b97c7ff675bd64bcf0c","user":{"login":"open-craft","id":7414786,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc0MTQ3ODY=","avatar_url":"https://avatars0.githubusercontent.com/u/7414786?v=4","gravatar_id":"","url":"https://api.github.com/users/open-craft","html_url":"https://github.com/open-craft","followers_url":"https://api.github.com/users/open-craft/followers","following_url":"https://api.github.com/users/open-craft/following{/other_user}","gists_url":"https://api.github.com/users/open-craft/gists{/gist_id}","starred_url":"https://api.github.com/users/open-craft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/open-craft/subscriptions","organizations_url":"https://api.github.com/users/open-craft/orgs","repos_url":"https://api.github.com/users/open-craft/repos","events_url":"https://api.github.com/users/open-craft/events{/privacy}","received_events_url":"https://api.github.com/users/open-craft/received_events","type":"Organization","site_admin":false},"repo":{"id":146386699,"node_id":"MDEwOlJlcG9zaXRvcnkxNDYzODY2OTk=","name":"openedx-blockstore-relay","full_name":"open-craft/openedx-blockstore-relay","owner":{"login":"open-craft","id":7414786,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc0MTQ3ODY=","avatar_url":"https://avatars0.githubusercontent.com/u/7414786?v=4","gravatar_id":"","url":"https://api.github.com/users/open-craft","html_url":"https://github.com/open-craft","followers_url":"https://api.github.com/users/open-craft/followers","following_url":"https://api.github.com/users/open-craft/following{/other_user}","gists_url":"https://api.github.com/users/open-craft/gists{/gist_id}","starred_url":"https://api.github.com/users/open-craft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/open-craft/subscriptions","organizations_url":"https://api.github.com/users/open-craft/orgs","repos_url":"https://api.github.com/users/open-craft/repos","events_url":"https://api.github.com/users/open-craft/events{/privacy}","received_events_url":"https://api.github.com/users/open-craft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/open-craft/openedx-blockstore-relay","description":"Django plugin application providing a data and signal relay between Open edX and Blockstore.","fork":false,"url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay","forks_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/forks","keys_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/keys{/key_id}","collaborators_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/teams","hooks_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/hooks","issue_events_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/events{/number}","events_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/events","assignees_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/assignees{/user}","branches_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/branches{/branch}","tags_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/tags","blobs_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/refs{/sha}","trees_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/trees{/sha}","statuses_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/statuses/{sha}","languages_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/languages","stargazers_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/stargazers","contributors_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/contributors","subscribers_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/subscribers","subscription_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/subscription","commits_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/commits{/sha}","git_commits_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/commits{/sha}","comments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/comments{/number}","issue_comment_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/comments{/number}","contents_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/contents/{+path}","compare_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/compare/{base}...{head}","merges_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/merges","archive_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/downloads","issues_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues{/number}","pulls_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls{/number}","milestones_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/milestones{/number}","notifications_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/labels{/name}","releases_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/releases{/id}","deployments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/deployments","created_at":"2018-08-28T03:26:24Z","updated_at":"2018-08-28T03:26:59Z","pushed_at":"2018-08-31T11:07:57Z","git_url":"git://github.com/open-craft/openedx-blockstore-relay.git","ssh_url":"git@github.com:open-craft/openedx-blockstore-relay.git","clone_url":"https://github.com/open-craft/openedx-blockstore-relay.git","svn_url":"https://github.com/open-craft/openedx-blockstore-relay","homepage":null,"size":90,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":1,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"open-craft:master","ref":"master","sha":"9a089e72388dea58bcb77c79c030dff4d525e4a5","user":{"login":"open-craft","id":7414786,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc0MTQ3ODY=","avatar_url":"https://avatars0.githubusercontent.com/u/7414786?v=4","gravatar_id":"","url":"https://api.github.com/users/open-craft","html_url":"https://github.com/open-craft","followers_url":"https://api.github.com/users/open-craft/followers","following_url":"https://api.github.com/users/open-craft/following{/other_user}","gists_url":"https://api.github.com/users/open-craft/gists{/gist_id}","starred_url":"https://api.github.com/users/open-craft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/open-craft/subscriptions","organizations_url":"https://api.github.com/users/open-craft/orgs","repos_url":"https://api.github.com/users/open-craft/repos","events_url":"https://api.github.com/users/open-craft/events{/privacy}","received_events_url":"https://api.github.com/users/open-craft/received_events","type":"Organization","site_admin":false},"repo":{"id":146386699,"node_id":"MDEwOlJlcG9zaXRvcnkxNDYzODY2OTk=","name":"openedx-blockstore-relay","full_name":"open-craft/openedx-blockstore-relay","owner":{"login":"open-craft","id":7414786,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc0MTQ3ODY=","avatar_url":"https://avatars0.githubusercontent.com/u/7414786?v=4","gravatar_id":"","url":"https://api.github.com/users/open-craft","html_url":"https://github.com/open-craft","followers_url":"https://api.github.com/users/open-craft/followers","following_url":"https://api.github.com/users/open-craft/following{/other_user}","gists_url":"https://api.github.com/users/open-craft/gists{/gist_id}","starred_url":"https://api.github.com/users/open-craft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/open-craft/subscriptions","organizations_url":"https://api.github.com/users/open-craft/orgs","repos_url":"https://api.github.com/users/open-craft/repos","events_url":"https://api.github.com/users/open-craft/events{/privacy}","received_events_url":"https://api.github.com/users/open-craft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/open-craft/openedx-blockstore-relay","description":"Django plugin application providing a data and signal relay between Open edX and Blockstore.","fork":false,"url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay","forks_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/forks","keys_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/keys{/key_id}","collaborators_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/teams","hooks_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/hooks","issue_events_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/events{/number}","events_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/events","assignees_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/assignees{/user}","branches_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/branches{/branch}","tags_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/tags","blobs_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/refs{/sha}","trees_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/trees{/sha}","statuses_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/statuses/{sha}","languages_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/languages","stargazers_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/stargazers","contributors_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/contributors","subscribers_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/subscribers","subscription_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/subscription","commits_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/commits{/sha}","git_commits_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/git/commits{/sha}","comments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/comments{/number}","issue_comment_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/comments{/number}","contents_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/contents/{+path}","compare_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/compare/{base}...{head}","merges_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/merges","archive_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/downloads","issues_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues{/number}","pulls_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls{/number}","milestones_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/milestones{/number}","notifications_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/labels{/name}","releases_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/releases{/id}","deployments_url":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/deployments","created_at":"2018-08-28T03:26:24Z","updated_at":"2018-08-28T03:26:59Z","pushed_at":"2018-08-31T11:07:57Z","git_url":"git://github.com/open-craft/openedx-blockstore-relay.git","ssh_url":"git@github.com:open-craft/openedx-blockstore-relay.git","clone_url":"https://github.com/open-craft/openedx-blockstore-relay.git","svn_url":"https://github.com/open-craft/openedx-blockstore-relay","homepage":null,"size":90,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":1,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0","node_id":"MDc6TGljZW5zZTE="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1"},"html":{"href":"https://github.com/open-craft/openedx-blockstore-relay/pull/1"},"issue":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/1"},"comments":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/open-craft/openedx-blockstore-relay/statuses/5245ba8ba77c348976bf5b97c7ff675bd64bcf0c"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"pomegranited"}}