{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/comments/38341586","id":38341586,"diff_hunk":"@@ -0,0 +1,312 @@\n+\"\"\"\n+Loads the internal reporting course table into the warehouse through the pipeline via Hive.\n+\n+On the roadmap is to write a task that runs validation queries on the aggregated Hive data pre-load.\n+\n+\"\"\"\n+import json\n+import datetime\n+import luigi\n+import requests\n+import ciso8601\n+\n+from edx.analytics.tasks.url import get_target_from_url\n+from edx.analytics.tasks.url import url_path_join\n+from edx.analytics.tasks.util.overwrite import OverwriteOutputMixin\n+from edx.analytics.tasks.vertica_load import VerticaCopyTask\n+from edx.analytics.tasks.database_imports import ImportStudentCourseEnrollmentTask, ImportIntoHiveTableTask\n+from edx.analytics.tasks.util.hive import HiveTableTask\n+from edx.analytics.tasks.util.hive import HiveTableFromQueryTask, WarehouseMixin, HivePartition\n+\n+# pylint: disable-msg=anomalous-unicode-escape-in-string\n+# To ensure the API call pulls all courses, set this to be larger than the number of courses\n+PAGE_SIZE = 10000\n+\n+\n+class LoadInternalReportingCourseMixin(WarehouseMixin, OverwriteOutputMixin):\n+    \"\"\"\n+    Mixin to handle parameters common to the tasks involved in loading the internal reporting course table,\n+    including calling the course structure API.\n+    \"\"\"\n+    run_date = luigi.DateParameter()\n+    api_root_url = luigi.Parameter(\n+        config_path={'section': 'course-structure', 'name': 'api_root_url'}\n+    )\n+    api_access_token = luigi.Parameter(\n+        config_path={'section': 'course-structure', 'name': 'access_token'}, significant=False\n+    )\n+\n+\n+class PullCourseStructureAPIData(LoadInternalReportingCourseMixin, luigi.Task):\n+    \"\"\"Call the course structure API and place the resulting JSON on S3.\"\"\"\n+\n+    def run(self):\n+        self.remove_output_on_overwrite()\n+        headers = {'authorization': ('Bearer ' + self.api_access_token), 'accept': 'application/json'}\n+        api_url = url_path_join(self.api_root_url, 'api', 'course_structure', 'v0', 'courses',\n+                                '?page_size={size}'.format(size=PAGE_SIZE))\n+        response = requests.get(url=api_url, headers=headers)\n+        if response.status_code != requests.codes.ok:  # pylint: disable=no-member\n+            msg = \"Encountered status {} on request to API for {}\".format(response.status_code, self.run_date)\n+            raise Exception(msg)\n+        with self.output().open('w') as output_file:\n+            output_file.write(response.content)\n+\n+    def output(self):\n+        \"\"\"Output is in the form {warehouse_path}/courses_raw/dt={CCYY-MM-DD}/course_structure.json\"\"\"\n+        date_string = \"dt=\" + self.run_date.strftime('%Y-%m-%d')  # pylint: disable=no-member\n+        url_with_filename = url_path_join(self.warehouse_path, 'courses_raw', date_string,\n+                                          \"course_structure.json\")\n+        return get_target_from_url(url_with_filename)\n+\n+\n+class ProcessCourseStructureAPIData(LoadInternalReportingCourseMixin, luigi.Task):\n+    \"\"\"Process the information from the course structure API and write it to a tsv.\"\"\"\n+    run_date = luigi.DateParameter()\n+\n+    def requires(self):\n+        kwargs = {\n+            'run_date': self.run_date,\n+            'warehouse_path': self.warehouse_path,\n+        }\n+        return PullCourseStructureAPIData(**kwargs)\n+\n+    def run(self):\n+        self.remove_output_on_overwrite()\n+        with self.input().open('r') as input_file:\n+            course_structure = json.load(input_file)\n+            with self.output().open('w') as output_file:\n+                courses_list = course_structure.get('results')\n+                if not courses_list:  # If there are no courses, or 'results' is not a key in the json, output nothing.\n+                    return\n+                for course in courses_list:\n+                    # To maintain robustness, ignore any non-dictionary data that finds its way into the API response.\n+                    try:\n+                        start_string = course.get('start')\n+                        end_string = course.get('end')\n+                        if start_string is None:\n+                            cleaned_start_string = '\\N'\n+                        else:\n+                            cleaned_start_string = ciso8601.parse_datetime(start_string)\n+                        if end_string is None:\n+                            cleaned_end_string = '\\N'\n+                        else:\n+                            cleaned_end_string = ciso8601.parse_datetime(end_string)\n+                        line = [\n+                            course.get('id', '\\N'),\n+                            course.get('org', '\\N'),\n+                            course.get('course', '\\N'),\n+                            course.get('run', '\\N'),\n+                            coerce_timestamp_for_hive(cleaned_start_string),\n+                            coerce_timestamp_for_hive(cleaned_end_string),\n+                            course.get('name', '\\N')\n+                        ]\n+                        output_file.write('\\t'.join([v.encode('utf-8') for v in line]))\n+                        output_file.write('\\n')\n+                    except AttributeError:  # If the course is not a dictionary, move on to the next one.\n+                        continue\n+\n+    def output(self):\n+        \"\"\"\n+        Output is set up so that it can be read as a Hive table with partitions.\n+\n+        The form is {warehouse_path}/course_structure/dt={CCYY-mm-dd}/courses.tsv.\n+        \"\"\"\n+        date_string = self.run_date.strftime('%Y-%m-%d')  # pylint: disable=no-member\n+        partition_path_spec = HivePartition('dt', date_string).path_spec\n+        url_with_filename = url_path_join(self.warehouse_path, \"course_structure\",\n+                                          partition_path_spec, \"courses.tsv\")\n+        return get_target_from_url(url_with_filename)\n+\n+\n+class LoadCourseStructureAPIDataIntoHive(LoadInternalReportingCourseMixin, ImportIntoHiveTableTask):\n+    \"\"\"Load the processed course structure API data into Hive.\"\"\"\n+    run_date = luigi.Parameter()\n+\n+    def requires(self):\n+        kwargs = {\n+            'run_date': self.run_date,\n+            'warehouse_path': self.warehouse_path,\n+            'overwrite': self.overwrite\n+        }\n+        return ProcessCourseStructureAPIData(**kwargs)\n+\n+    @property\n+    def table_name(self):\n+        return 'course_structure'\n+\n+    @property\n+    def table_format(self):\n+        return \"ROW FORMAT DELIMITED FIELDS TERMINATED BY '\\\\t'\"\n+\n+    @property\n+    def table_location(self):\n+        return url_path_join(self.warehouse_path, self.table_name)\n+\n+    @property\n+    def partition_date(self):\n+        return self.run_date.isoformat()\n+\n+    # @property\n+    # def partition(self):\n+    #     return HivePartition('dt', self.run_date.isoformat())  # pylint: disable=no-member\n+\n+    @property\n+    def columns(self):\n+        return [\n+            ('course_id', 'STRING'),\n+            ('course_org_id', 'STRING'),\n+            ('course_number', 'STRING'),\n+            ('course_run', 'STRING'),\n+            ('course_start', 'STRING'),\n+            ('course_end', 'STRING'),\n+            ('course_name', 'STRING')\n+        ]\n+\n+\n+class GetCoursesFromStudentCourseEnrollmentTask(LoadInternalReportingCourseMixin, HiveTableFromQueryTask):\n+    \"\"\"\n+    Finds all courses from the LMS enrollments table to make sure we haven't left out any courses with enrollments\n+    which, for whatever reason, can't be found in the course structure API.\n+    \"\"\"\n+\n+    n_reduce_tasks = luigi.Parameter()\n+\n+    def requires(self):\n+        return ImportStudentCourseEnrollmentTask(import_date=self.run_date, destination=self.warehouse_path)\n+\n+    @property\n+    def table(self):\n+        return 'courses_with_enrollments'\n+\n+    @property\n+    def columns(self):\n+        return [\n+            ('course_id', 'STRING'),\n+        ]\n+\n+    @property\n+    def partition(self):\n+        return HivePartition('dt', self.run_date.isoformat())  # pylint: disable=no-member\n+\n+    @property\n+    def insert_query(self):\n+        return \"\"\"\n+            SELECT DISTINCT course_id\n+            FROM student_courseenrollment SORT BY course_id ASC\n+            \"\"\"\n+\n+\n+class AggregateInternalReportingCourseTableHive(LoadInternalReportingCourseMixin, HiveTableFromQueryTask):\n+    \"\"\"Aggregate the internal reporting course table in Hive.\"\"\"\n+    n_reduce_tasks = luigi.Parameter()\n+\n+    def requires(self):\n+        \"\"\"\n+        This task reads from student_coursenrollment as well as course structure API results, so require that they be\n+        loaded into Hive (via MySQL loads into Hive or via the pipeline as needed).\n+        \"\"\"\n+        return [GetCoursesFromStudentCourseEnrollmentTask(n_reduce_tasks=self.n_reduce_tasks,\n+                                                          warehouse_path=self.warehouse_path, run_date=self.run_date),\n+                LoadCourseStructureAPIDataIntoHive(run_date=self.run_date, warehouse_path=self.warehouse_path,\n+                                                   overwrite=True)]\n+\n+    @property\n+    def table(self):\n+        return 'internal_reporting_course'\n+\n+    @property\n+    def columns(self):\n+        return [\n+            ('course_id', 'STRING'),\n+            ('course_org_id', 'STRING'),\n+            ('course_number', 'STRING'),\n+            ('course_run', 'STRING'),\n+            ('course_start', 'STRING'),\n+            ('course_end', 'STRING'),\n+            ('course_name', 'STRING'),\n+        ]\n+\n+    @property\n+    def partition(self):\n+        return HivePartition('dt', self.run_date.isoformat())  # pylint: disable=no-member\n+\n+    @property\n+    def insert_query(self):\n+        return \"\"\"\n+            SELECT\n+              sce.course_id\n+            , cs.course_org_id\n+            , cs.course_number\n+            , cs.course_run\n+            , cs.course_start\n+            , cs.course_end\n+            , cs.course_name\n+            FROM courses_with_enrollments sce\n+            LEFT OUTER JOIN course_structure cs ON sce.course_id = cs.course_id\n+            SORT BY sce.course_id ASC\n+            \"\"\"\n+\n+\n+class LoadInternalReportingCourseToWarehouse(LoadInternalReportingCourseMixin, VerticaCopyTask):\n+    \"\"\"\n+    Loads the course table from Hive into the Vertica data warehouse.\n+    \"\"\"\n+    n_reduce_tasks = luigi.Parameter()\n+    overwrite = luigi.BooleanParameter(default=False)\n+\n+    @property\n+    def partition(self):\n+        \"\"\"The table is partitioned by date.\"\"\"\n+        return HivePartition('dt', self.run_date.isoformat())  # pylint: disable=no-member\n+\n+    @property\n+    def insert_source_task(self):\n+        return (\n+            # Get the location of the Hive table, so it can be opened and read.\n+            AggregateInternalReportingCourseTableHive(\n+                warehouse_path=self.warehouse_path,\n+                overwrite=self.overwrite,\n+                n_reduce_tasks=self.n_reduce_tasks,\n+                run_date=self.run_date\n+            )\n+        )\n+\n+    @property\n+    def table(self):\n+        return 'd_course'\n+\n+    @property\n+    def default_columns(self):\n+        \"\"\"List of tuples defining name and definition of automatically-filled columns.\"\"\"\n+        return None\n+\n+    @property\n+    def auto_primary_key(self):\n+        \"\"\"No automatic primary key is needed; course id is a unique identifier.\"\"\"\n+        return None\n+\n+    @property\n+    def columns(self):\n+        return [\n+            ('course_id', 'VARCHAR(200) PRIMARY KEY'),","path":"edx/analytics/tasks/load_internal_reporting_course.py","position":292,"original_position":292,"commit_id":"e44c6440cfbc0a3f3457b0e72b6d46ab7c9a46f3","original_commit_id":"e44c6440cfbc0a3f3457b0e72b6d46ab7c9a46f3","user":{"login":"brianhw","id":2534095,"avatar_url":"https://avatars.githubusercontent.com/u/2534095?v=3","gravatar_id":"","url":"https://api.github.com/users/brianhw","html_url":"https://github.com/brianhw","followers_url":"https://api.github.com/users/brianhw/followers","following_url":"https://api.github.com/users/brianhw/following{/other_user}","gists_url":"https://api.github.com/users/brianhw/gists{/gist_id}","starred_url":"https://api.github.com/users/brianhw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brianhw/subscriptions","organizations_url":"https://api.github.com/users/brianhw/orgs","repos_url":"https://api.github.com/users/brianhw/repos","events_url":"https://api.github.com/users/brianhw/events{/privacy}","received_events_url":"https://api.github.com/users/brianhw/received_events","type":"User","site_admin":false},"body":"We should be consistent with sizes, if we can.  I thought we were changing course_id to be 255 or 256....\r\n","created_at":"2015-08-31T18:18:07Z","updated_at":"2015-08-31T18:18:07Z","html_url":"https://github.com/edx/edx-analytics-pipeline/pull/150#discussion_r38341586","pull_request_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/comments/38341586"},"html":{"href":"https://github.com/edx/edx-analytics-pipeline/pull/150#discussion_r38341586"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150","id":43220255,"html_url":"https://github.com/edx/edx-analytics-pipeline/pull/150","diff_url":"https://github.com/edx/edx-analytics-pipeline/pull/150.diff","patch_url":"https://github.com/edx/edx-analytics-pipeline/pull/150.patch","issue_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/150","number":150,"state":"open","locked":false,"title":"Pipeline tasks to load the internal reporting course information table","user":{"login":"jrowan","id":5571464,"avatar_url":"https://avatars.githubusercontent.com/u/5571464?v=3","gravatar_id":"","url":"https://api.github.com/users/jrowan","html_url":"https://github.com/jrowan","followers_url":"https://api.github.com/users/jrowan/followers","following_url":"https://api.github.com/users/jrowan/following{/other_user}","gists_url":"https://api.github.com/users/jrowan/gists{/gist_id}","starred_url":"https://api.github.com/users/jrowan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrowan/subscriptions","organizations_url":"https://api.github.com/users/jrowan/orgs","repos_url":"https://api.github.com/users/jrowan/repos","events_url":"https://api.github.com/users/jrowan/events{/privacy}","received_events_url":"https://api.github.com/users/jrowan/received_events","type":"User","site_admin":false},"body":"This workflow replaces the Pentaho Kettle transform Load_Corse_With_Structure.ktr and provides a workflow for building the internal reporting course information table (d_course) purely through the pipeline.  In addition to acceptance and unit tests, I've compared the results of running this workflow on the live data to the output of the existing pentaho job, and they are nearly identical (there are a few changes to courses, but those seem to be from people editing their courses between the times the two jobs were run).\r\n\r\n@mulby @jab5569","created_at":"2015-08-24T20:52:09Z","updated_at":"2015-08-31T18:18:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b99e0f4ea26d0b45980ec91e62f7694a83e8c35b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150/commits","review_comments_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150/comments","review_comment_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/150/comments","statuses_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/statuses/e44c6440cfbc0a3f3457b0e72b6d46ab7c9a46f3","head":{"label":"edx:jamesrowan/retire-pentaho-d-course","ref":"jamesrowan/retire-pentaho-d-course","sha":"e44c6440cfbc0a3f3457b0e72b6d46ab7c9a46f3","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":22929261,"name":"edx-analytics-pipeline","full_name":"edx/edx-analytics-pipeline","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-analytics-pipeline","description":"","fork":false,"url":"https://api.github.com/repos/edx/edx-analytics-pipeline","forks_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/forks","keys_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/teams","hooks_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/events","assignees_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/tags","blobs_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/languages","stargazers_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/subscription","commits_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/merges","archive_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/downloads","issues_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/releases{/id}","created_at":"2014-08-13T19:57:17Z","updated_at":"2015-08-27T14:10:40Z","pushed_at":"2015-08-31T16:28:02Z","git_url":"git://github.com/edx/edx-analytics-pipeline.git","ssh_url":"git@github.com:edx/edx-analytics-pipeline.git","clone_url":"https://github.com/edx/edx-analytics-pipeline.git","svn_url":"https://github.com/edx/edx-analytics-pipeline","homepage":null,"size":6896,"stargazers_count":10,"watchers_count":10,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"open_issues_count":11,"forks":22,"open_issues":11,"watchers":10,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"6222e7c685c7da97e1f10e9dc74a85fd1af36cf1","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":22929261,"name":"edx-analytics-pipeline","full_name":"edx/edx-analytics-pipeline","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-analytics-pipeline","description":"","fork":false,"url":"https://api.github.com/repos/edx/edx-analytics-pipeline","forks_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/forks","keys_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/teams","hooks_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/events","assignees_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/tags","blobs_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/languages","stargazers_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/subscription","commits_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/merges","archive_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/downloads","issues_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-analytics-pipeline/releases{/id}","created_at":"2014-08-13T19:57:17Z","updated_at":"2015-08-27T14:10:40Z","pushed_at":"2015-08-31T16:28:02Z","git_url":"git://github.com/edx/edx-analytics-pipeline.git","ssh_url":"git@github.com:edx/edx-analytics-pipeline.git","clone_url":"https://github.com/edx/edx-analytics-pipeline.git","svn_url":"https://github.com/edx/edx-analytics-pipeline","homepage":null,"size":6896,"stargazers_count":10,"watchers_count":10,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"open_issues_count":11,"forks":22,"open_issues":11,"watchers":10,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150"},"html":{"href":"https://github.com/edx/edx-analytics-pipeline/pull/150"},"issue":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/150"},"comments":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/issues/150/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/pulls/150/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-analytics-pipeline/statuses/e44c6440cfbc0a3f3457b0e72b6d46ab7c9a46f3"}}}}