{"action":"created","comment":{"url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/comments/100385084","pull_request_review_id":21091813,"id":100385084,"diff_hunk":"@@ -0,0 +1,434 @@\n+# Copyright 2017 Google Inc. All Rights Reserved.\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#      http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\"\"\"Runs prediction on a trained model.\"\"\"\n+\n+\n+import argparse\n+import datetime\n+import os\n+import sys\n+\n+import apache_beam as beam\n+\n+\n+def parse_arguments(argv):\n+  \"\"\"Parse command line arguments.\n+\n+  Args:\n+    argv: includes the script's name.\n+\n+  Returns:\n+    argparse object\n+  \"\"\"\n+  parser = argparse.ArgumentParser(\n+      description='Runs Prediction inside a beam or Dataflow job.')\n+  # cloud options\n+  parser.add_argument('--project_id',\n+                      help='The project to which the job will be submitted.')\n+  parser.add_argument('--cloud',\n+                      action='store_true',\n+                      help='Run preprocessing on the cloud.')\n+  parser.add_argument('--job_name',\n+                      default=('structured-data-batch-prediction-'\n+                          + datetime.datetime.now().strftime('%Y%m%d%H%M%S')),\n+                      help='Dataflow job name. Must be unique over all jobs.')\n+\n+  # I/O args\n+  parser.add_argument('--predict_data',\n+                      required=True,\n+                      help='Data to run prediction on')\n+  parser.add_argument('--trained_model_dir',\n+                      required=True,\n+                      help='Usually train_output_path/model.')\n+  parser.add_argument('--output_dir',\n+                      required=True,\n+                      help=('Location to save output.'))\n+\n+  # Other args\n+  parser.add_argument('--batch_size',\n+                      required=False,\n+                      default=1000,\n+                      type=int,\n+                      help=('Batch size. Larger values consumes more memrory '\n+                            'but takes less time to finish.'))\n+  parser.add_argument('--shard_files',\n+                      dest='shard_files',\n+                      action='store_true',\n+                      help='Shard files')\n+  parser.add_argument('--no-shard_files',\n+                      dest='shard_files',\n+                      action='store_false',\n+                      help='Don\\'t shard files')\n+  parser.set_defaults(shard_files=True)\n+  parser.add_argument('--output_format',\n+                      choices=['csv', 'raw_json'],\n+                      default='csv',\n+                      help=\"\"\"\n+      The output results. \n+        raw_json: produces a newline file where each line is json. No \n+            post processing is performed and the output matches what the trained\n+            model produces.\n+        csv: produces a csv file without a header row and a header csv file.\n+            For classification problems, the vector of probabalities for each \n+            target class is split into individual csv columns.\"\"\")\n+\n+  args, _ = parser.parse_known_args(args=argv[1:])\n+\n+  if args.cloud:\n+    if not args.project_id:\n+      raise ValueError('--project_id needed with --cloud')\n+    if not args.trained_model_dir.startswith('gs://'):\n+      raise ValueError('trained_model_dir needs to be a GCS path,')\n+    if not args.output_dir.startswith('gs://'):\n+      raise ValueError('output_dir needs to be a GCS path.')\n+    if not args.predict_data.startswith('gs://'):\n+      raise ValueError('predict_data needs to be a GCS path.')\n+\n+\n+  return args\n+\n+\n+class FixMissingTarget(beam.DoFn):\n+  \"\"\"A DoFn to fix missing target columns.\"\"\"\n+\n+  def __init__(self, trained_model_dir):\n+    \"\"\"Reads the schema file and extracted the expected number of columns.\n+\n+    Args:\n+      trained_model_dir: path to model.\n+\n+    Raises:\n+      ValueError: if schema.json not found in trained_model_dir\n+    \"\"\"\n+    from tensorflow.python.lib.io import file_io\n+    import json\n+    import os\n+\n+    schema_path = os.path.join(trained_model_dir, 'schema.json')\n+    if not file_io.file_exists(schema_path):\n+      raise ValueError('schema.json missing from %s' % schema_path)\n+    schema = json.loads(file_io.read_file_to_string(schema_path))\n+    self._num_expected_columns = len(schema)\n+\n+  def process(self, context):\n+    import logging\n+    import apache_beam as beam\n+\n+    num_columns = len(context.element.split(','))\n+    if num_columns == self._num_expected_columns:\n+      yield context.element\n+    elif num_columns + 1 == self._num_expected_columns:\n+      yield ',' + context.element\n+    else:\n+      logging.error('Got an unexpected number of columns from [%s].' %\n+                    context.element)\n+      yield beam.pvalue.SideOutputValue('errors',\n+                                        ('bad columns', context.element))\n+\n+\n+class EmitAsBatchDoFn(beam.DoFn):\n+  \"\"\"A DoFn that buffers the records and emits them batch by batch.\"\"\"\n+\n+  def __init__(self, batch_size):\n+    \"\"\"Constructor of EmitAsBatchDoFn beam.DoFn class.\n+\n+    Args:\n+      batch_size: the max size we want to buffer the records before emitting.\n+    \"\"\"\n+    self._batch_size = batch_size\n+    self._cached = []\n+\n+  def process(self, context):\n+    self._cached.append(context.element)\n+    if len(self._cached) >= self._batch_size:\n+      emit = self._cached\n+      self._cached = []\n+      yield emit\n+\n+  def finish_bundle(self, context):\n+    if len(self._cached) > 0:  # pylint: disable=g-explicit-length-test\n+      yield self._cached\n+\n+\n+class RunGraphDoFn(beam.DoFn):\n+  \"\"\"A DoFn for running the TF graph.\"\"\"\n+\n+  def __init__(self, trained_model_dir):\n+    self._trained_model_dir = trained_model_dir\n+    self._session = None\n+\n+  def _setup_prediction(self):\n+    from tensorflow.contrib.session_bundle import session_bundle\n+    import json\n+\n+    self._session, _ = session_bundle.load_session_bundle_from_path(\n+        self._trained_model_dir)\n+\n+    # input_alias_map {'input_csv_string': tensor_name}\n+    self._input_alias_map = json.loads(\n+        self._session.graph.get_collection('inputs')[0])\n+\n+    # output_alias_map {'target_from_input': tensor_name, 'key': ...}\n+    self._output_alias_map = json.loads(\n+        self._session.graph.get_collection('outputs')[0])\n+\n+    self._aliases, self._tensor_names = zip(*self._output_alias_map.items())\n+\n+  def process(self, context):\n+    import collections\n+    import logging\n+    import apache_beam as beam   \n+\n+    num_in_batch = 0\n+    try:\n+      if not self._session:\n+        self._setup_prediction()\n+\n+      feed_dict = collections.defaultdict(list)\n+      for line in context.element:\n+        feed_dict[self._input_alias_map.values()[0]].append(line)\n+        num_in_batch += 1\n+\n+      # batch_result is list of numpy arrays with batch_size many rows.\n+      batch_result = self._session.run(fetches=self._tensor_names,\n+                                       feed_dict=feed_dict)\n+\n+      # ex batch_result for batch_size > 1:\n+      # (array([value1, value2, ..., value_batch_size]), \n+      #  array([[a1, b1, c1]], ..., [a_batch_size, b_batch_size, c_batch_size]]),\n+      #  ...)\n+      # ex batch_result for batch_size == 1:\n+      # (value,\n+      #  array([a1, b1, c1]),\n+      #  ...)\n+\n+\n+      # Convert the results into a dict and unbatch the results.\n+      if num_in_batch > 1:\n+        for result in zip(*batch_result):\n+          predictions = {}\n+          for name, value in zip(self._aliases, result):\n+            predictions[name] = (value.tolist() if getattr(value, 'tolist', None)\n+                                else value)\n+          yield predictions\n+      else:\n+        predictions = {}\n+        for i in range(len(self._aliases)):\n+          value = batch_result[i]\n+          value = (value.tolist() if getattr(value, 'tolist', None)\n+                   else value)\n+          predictions[self._aliases[i]] =  value\n+        yield predictions\n+\n+    except Exception as e:  # pylint: disable=broad-except   \n+      logging.error('RunGraphDoFn: Bad input: %s, Error: %s',\n+                    str(context.element), str(e))\n+      yield beam.pvalue.SideOutputValue('errors',\n+                                        (str(e), context.element))\n+\n+\n+class RawJsonCoder(beam.coders.Coder):\n+  \"\"\"Coder for json newline files.\"\"\"\n+\n+  def encode(self, obj):\n+    \"\"\"Encodes a python object into a JSON string.\n+\n+    Args:\n+      obj: python object.\n+\n+    Returns:\n+      JSON string.\n+    \"\"\"\n+    import json\n+    return json.dumps(obj, separators=(',', ': '))\n+\n+\n+# TODO(brandondura): make the model a 'top N' model for prediction, so then this\n+# class would not be needed.\n+class CSVCoder(beam.coders.Coder):\n+  \"\"\"Coder for CSV files containing the ouput of prediction.\"\"\"\n+\n+  def __init__(self, isClassification=False):\n+    self._headers = ['key', 'target_from_input', 'target_score_prediction']\n+\n+    if isClassification:\n+      self._headers.append('target_class_prediction')\n+\n+\n+  def make_header_string(self):\n+    return ','.join(self._headers)\n+\n+  def encode(self, tf_graph_predictions):\n+    \"\"\"Encodes the graph json prediction into csv.\n+\n+    Args:\n+      tf_graph_predictions: python dict.\n+\n+    Returns:\n+      csv string.\n+    \"\"\"\n+    row = [str(tf_graph_predictions['key']),\n+           str(tf_graph_predictions['target_from_input'])]    \n+\n+    if isinstance(tf_graph_predictions['target_score_prediction'], basestring):\n+      score = [tf_graph_predictions['target_score_prediction']]\n+    else:\n+      score = [str(x) for x in tf_graph_predictions['target_score_prediction']]\n+    csv_score = '|'.join(score)\n+    row.append(csv_score)\n+\n+    if 'target_class_prediction' in tf_graph_predictions:\n+      row.append(str(tf_graph_predictions['target_class_prediction']))\n+\n+    return ','.join(row)\n+\n+\n+class FormatAndSave(beam.PTransform):\n+\n+  def __init__(self, args):\n+    self._shard_name_template = None if args.shard_files else ''\n+    self._output_format = args.output_format\n+    self._output_dir = args.output_dir\n+\n+    # See if the target vocab should be loaded.\n+    if self._output_format == 'csv':\n+      from tensorflow.python.lib.io import file_io\n+      from StringIO import StringIO","path":"solutionbox/structured_data/datalab_solutions/structured_data/predict/predict.py","position":307,"original_position":307,"commit_id":"206063ed43739704fa4a5551bae4c22f857f0874","original_commit_id":"206063ed43739704fa4a5551bae4c22f857f0874","user":{"login":"brandondutra","id":21314536,"avatar_url":"https://avatars.githubusercontent.com/u/21314536?v=3","gravatar_id":"","url":"https://api.github.com/users/brandondutra","html_url":"https://github.com/brandondutra","followers_url":"https://api.github.com/users/brandondutra/followers","following_url":"https://api.github.com/users/brandondutra/following{/other_user}","gists_url":"https://api.github.com/users/brandondutra/gists{/gist_id}","starred_url":"https://api.github.com/users/brandondutra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brandondutra/subscriptions","organizations_url":"https://api.github.com/users/brandondutra/orgs","repos_url":"https://api.github.com/users/brandondutra/repos","events_url":"https://api.github.com/users/brandondutra/events{/privacy}","received_events_url":"https://api.github.com/users/brandondutra/received_events","type":"User","site_admin":false},"body":"done","created_at":"2017-02-09T19:05:44Z","updated_at":"2017-02-09T19:05:44Z","html_url":"https://github.com/googledatalab/pydatalab/pull/177#discussion_r100385084","pull_request_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177","_links":{"self":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/comments/100385084"},"html":{"href":"https://github.com/googledatalab/pydatalab/pull/177#discussion_r100385084"},"pull_request":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177"}}},"pull_request":{"url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177","id":105341047,"html_url":"https://github.com/googledatalab/pydatalab/pull/177","diff_url":"https://github.com/googledatalab/pydatalab/pull/177.diff","patch_url":"https://github.com/googledatalab/pydatalab/pull/177.patch","issue_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/177","number":177,"state":"open","locked":false,"title":"Cloudmlsdp","user":{"login":"brandondutra","id":21314536,"avatar_url":"https://avatars.githubusercontent.com/u/21314536?v=3","gravatar_id":"","url":"https://api.github.com/users/brandondutra","html_url":"https://github.com/brandondutra","followers_url":"https://api.github.com/users/brandondutra/followers","following_url":"https://api.github.com/users/brandondutra/following{/other_user}","gists_url":"https://api.github.com/users/brandondutra/gists{/gist_id}","starred_url":"https://api.github.com/users/brandondutra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brandondutra/subscriptions","organizations_url":"https://api.github.com/users/brandondutra/orgs","repos_url":"https://api.github.com/users/brandondutra/repos","events_url":"https://api.github.com/users/brandondutra/events{/privacy}","received_events_url":"https://api.github.com/users/brandondutra/received_events","type":"User","site_admin":false},"body":"added batch prediction df code\r\n\r\n","created_at":"2017-02-09T01:09:33Z","updated_at":"2017-02-09T19:05:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"89adea3f2eb321e41e9c83ccf190d027d6ebd648","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177/commits","review_comments_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177/comments","review_comment_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls/comments{/number}","comments_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/177/comments","statuses_url":"https://api.github.com/repos/googledatalab/pydatalab/statuses/206063ed43739704fa4a5551bae4c22f857f0874","head":{"label":"googledatalab:cloudmlsdp","ref":"cloudmlsdp","sha":"206063ed43739704fa4a5551bae4c22f857f0874","user":{"login":"googledatalab","id":18582876,"avatar_url":"https://avatars.githubusercontent.com/u/18582876?v=3","gravatar_id":"","url":"https://api.github.com/users/googledatalab","html_url":"https://github.com/googledatalab","followers_url":"https://api.github.com/users/googledatalab/followers","following_url":"https://api.github.com/users/googledatalab/following{/other_user}","gists_url":"https://api.github.com/users/googledatalab/gists{/gist_id}","starred_url":"https://api.github.com/users/googledatalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googledatalab/subscriptions","organizations_url":"https://api.github.com/users/googledatalab/orgs","repos_url":"https://api.github.com/users/googledatalab/repos","events_url":"https://api.github.com/users/googledatalab/events{/privacy}","received_events_url":"https://api.github.com/users/googledatalab/received_events","type":"Organization","site_admin":false},"repo":{"id":58776721,"name":"pydatalab","full_name":"googledatalab/pydatalab","owner":{"login":"googledatalab","id":18582876,"avatar_url":"https://avatars.githubusercontent.com/u/18582876?v=3","gravatar_id":"","url":"https://api.github.com/users/googledatalab","html_url":"https://github.com/googledatalab","followers_url":"https://api.github.com/users/googledatalab/followers","following_url":"https://api.github.com/users/googledatalab/following{/other_user}","gists_url":"https://api.github.com/users/googledatalab/gists{/gist_id}","starred_url":"https://api.github.com/users/googledatalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googledatalab/subscriptions","organizations_url":"https://api.github.com/users/googledatalab/orgs","repos_url":"https://api.github.com/users/googledatalab/repos","events_url":"https://api.github.com/users/googledatalab/events{/privacy}","received_events_url":"https://api.github.com/users/googledatalab/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/googledatalab/pydatalab","description":"Google Datalab Library","fork":false,"url":"https://api.github.com/repos/googledatalab/pydatalab","forks_url":"https://api.github.com/repos/googledatalab/pydatalab/forks","keys_url":"https://api.github.com/repos/googledatalab/pydatalab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/googledatalab/pydatalab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/googledatalab/pydatalab/teams","hooks_url":"https://api.github.com/repos/googledatalab/pydatalab/hooks","issue_events_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/events{/number}","events_url":"https://api.github.com/repos/googledatalab/pydatalab/events","assignees_url":"https://api.github.com/repos/googledatalab/pydatalab/assignees{/user}","branches_url":"https://api.github.com/repos/googledatalab/pydatalab/branches{/branch}","tags_url":"https://api.github.com/repos/googledatalab/pydatalab/tags","blobs_url":"https://api.github.com/repos/googledatalab/pydatalab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/googledatalab/pydatalab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/googledatalab/pydatalab/git/refs{/sha}","trees_url":"https://api.github.com/repos/googledatalab/pydatalab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/googledatalab/pydatalab/statuses/{sha}","languages_url":"https://api.github.com/repos/googledatalab/pydatalab/languages","stargazers_url":"https://api.github.com/repos/googledatalab/pydatalab/stargazers","contributors_url":"https://api.github.com/repos/googledatalab/pydatalab/contributors","subscribers_url":"https://api.github.com/repos/googledatalab/pydatalab/subscribers","subscription_url":"https://api.github.com/repos/googledatalab/pydatalab/subscription","commits_url":"https://api.github.com/repos/googledatalab/pydatalab/commits{/sha}","git_commits_url":"https://api.github.com/repos/googledatalab/pydatalab/git/commits{/sha}","comments_url":"https://api.github.com/repos/googledatalab/pydatalab/comments{/number}","issue_comment_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/comments{/number}","contents_url":"https://api.github.com/repos/googledatalab/pydatalab/contents/{+path}","compare_url":"https://api.github.com/repos/googledatalab/pydatalab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/googledatalab/pydatalab/merges","archive_url":"https://api.github.com/repos/googledatalab/pydatalab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/googledatalab/pydatalab/downloads","issues_url":"https://api.github.com/repos/googledatalab/pydatalab/issues{/number}","pulls_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls{/number}","milestones_url":"https://api.github.com/repos/googledatalab/pydatalab/milestones{/number}","notifications_url":"https://api.github.com/repos/googledatalab/pydatalab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/googledatalab/pydatalab/labels{/name}","releases_url":"https://api.github.com/repos/googledatalab/pydatalab/releases{/id}","deployments_url":"https://api.github.com/repos/googledatalab/pydatalab/deployments","created_at":"2016-05-13T22:42:57Z","updated_at":"2017-02-02T01:37:58Z","pushed_at":"2017-02-09T17:58:54Z","git_url":"git://github.com/googledatalab/pydatalab.git","ssh_url":"git@github.com:googledatalab/pydatalab.git","clone_url":"https://github.com/googledatalab/pydatalab.git","svn_url":"https://github.com/googledatalab/pydatalab","homepage":null,"size":10029,"stargazers_count":16,"watchers_count":16,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":37,"forks":12,"open_issues":37,"watchers":16,"default_branch":"master"}},"base":{"label":"googledatalab:cloudml","ref":"cloudml","sha":"f3eb8388f34cd41d7ea16469ca378d0fb34f4873","user":{"login":"googledatalab","id":18582876,"avatar_url":"https://avatars.githubusercontent.com/u/18582876?v=3","gravatar_id":"","url":"https://api.github.com/users/googledatalab","html_url":"https://github.com/googledatalab","followers_url":"https://api.github.com/users/googledatalab/followers","following_url":"https://api.github.com/users/googledatalab/following{/other_user}","gists_url":"https://api.github.com/users/googledatalab/gists{/gist_id}","starred_url":"https://api.github.com/users/googledatalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googledatalab/subscriptions","organizations_url":"https://api.github.com/users/googledatalab/orgs","repos_url":"https://api.github.com/users/googledatalab/repos","events_url":"https://api.github.com/users/googledatalab/events{/privacy}","received_events_url":"https://api.github.com/users/googledatalab/received_events","type":"Organization","site_admin":false},"repo":{"id":58776721,"name":"pydatalab","full_name":"googledatalab/pydatalab","owner":{"login":"googledatalab","id":18582876,"avatar_url":"https://avatars.githubusercontent.com/u/18582876?v=3","gravatar_id":"","url":"https://api.github.com/users/googledatalab","html_url":"https://github.com/googledatalab","followers_url":"https://api.github.com/users/googledatalab/followers","following_url":"https://api.github.com/users/googledatalab/following{/other_user}","gists_url":"https://api.github.com/users/googledatalab/gists{/gist_id}","starred_url":"https://api.github.com/users/googledatalab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googledatalab/subscriptions","organizations_url":"https://api.github.com/users/googledatalab/orgs","repos_url":"https://api.github.com/users/googledatalab/repos","events_url":"https://api.github.com/users/googledatalab/events{/privacy}","received_events_url":"https://api.github.com/users/googledatalab/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/googledatalab/pydatalab","description":"Google Datalab Library","fork":false,"url":"https://api.github.com/repos/googledatalab/pydatalab","forks_url":"https://api.github.com/repos/googledatalab/pydatalab/forks","keys_url":"https://api.github.com/repos/googledatalab/pydatalab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/googledatalab/pydatalab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/googledatalab/pydatalab/teams","hooks_url":"https://api.github.com/repos/googledatalab/pydatalab/hooks","issue_events_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/events{/number}","events_url":"https://api.github.com/repos/googledatalab/pydatalab/events","assignees_url":"https://api.github.com/repos/googledatalab/pydatalab/assignees{/user}","branches_url":"https://api.github.com/repos/googledatalab/pydatalab/branches{/branch}","tags_url":"https://api.github.com/repos/googledatalab/pydatalab/tags","blobs_url":"https://api.github.com/repos/googledatalab/pydatalab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/googledatalab/pydatalab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/googledatalab/pydatalab/git/refs{/sha}","trees_url":"https://api.github.com/repos/googledatalab/pydatalab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/googledatalab/pydatalab/statuses/{sha}","languages_url":"https://api.github.com/repos/googledatalab/pydatalab/languages","stargazers_url":"https://api.github.com/repos/googledatalab/pydatalab/stargazers","contributors_url":"https://api.github.com/repos/googledatalab/pydatalab/contributors","subscribers_url":"https://api.github.com/repos/googledatalab/pydatalab/subscribers","subscription_url":"https://api.github.com/repos/googledatalab/pydatalab/subscription","commits_url":"https://api.github.com/repos/googledatalab/pydatalab/commits{/sha}","git_commits_url":"https://api.github.com/repos/googledatalab/pydatalab/git/commits{/sha}","comments_url":"https://api.github.com/repos/googledatalab/pydatalab/comments{/number}","issue_comment_url":"https://api.github.com/repos/googledatalab/pydatalab/issues/comments{/number}","contents_url":"https://api.github.com/repos/googledatalab/pydatalab/contents/{+path}","compare_url":"https://api.github.com/repos/googledatalab/pydatalab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/googledatalab/pydatalab/merges","archive_url":"https://api.github.com/repos/googledatalab/pydatalab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/googledatalab/pydatalab/downloads","issues_url":"https://api.github.com/repos/googledatalab/pydatalab/issues{/number}","pulls_url":"https://api.github.com/repos/googledatalab/pydatalab/pulls{/number}","milestones_url":"https://api.github.com/repos/googledatalab/pydatalab/milestones{/number}","notifications_url":"https://api.github.com/repos/googledatalab/pydatalab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/googledatalab/pydatalab/labels{/name}","releases_url":"https://api.github.com/repos/googledatalab/pydatalab/releases{/id}","deployments_url":"https://api.github.com/repos/googledatalab/pydatalab/deployments","created_at":"2016-05-13T22:42:57Z","updated_at":"2017-02-02T01:37:58Z","pushed_at":"2017-02-09T17:58:54Z","git_url":"git://github.com/googledatalab/pydatalab.git","ssh_url":"git@github.com:googledatalab/pydatalab.git","clone_url":"https://github.com/googledatalab/pydatalab.git","svn_url":"https://github.com/googledatalab/pydatalab","homepage":null,"size":10029,"stargazers_count":16,"watchers_count":16,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":37,"forks":12,"open_issues":37,"watchers":16,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177"},"html":{"href":"https://github.com/googledatalab/pydatalab/pull/177"},"issue":{"href":"https://api.github.com/repos/googledatalab/pydatalab/issues/177"},"comments":{"href":"https://api.github.com/repos/googledatalab/pydatalab/issues/177/comments"},"review_comments":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177/comments"},"review_comment":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/googledatalab/pydatalab/pulls/177/commits"},"statuses":{"href":"https://api.github.com/repos/googledatalab/pydatalab/statuses/206063ed43739704fa4a5551bae4c22f857f0874"}}}}
{"actor":{"display_login":"brandondutra"}}