{"action":"created","comment":{"url":"https://api.github.com/repos/python/mypy/pulls/comments/71047459","id":71047459,"diff_hunk":"@@ -0,0 +1,280 @@\n+#!/usr/bin/env python3\n+\"\"\"\n+This file compares the output and runtime of running normal vs incremental mode\n+on the history of any arbitrary git repo as a way of performing a sanity check\n+to make sure incremental mode is working correctly and efficiently.\n+\n+This script will, by default, run all tests against mypy's repo.\n+\n+Examples:\n+\n+To run this script against the last 30 commits, do:\n+\n+    python3 misc/incremental_checker.py -30\n+\n+To run this script starting from the commit id 2a432b, do:\n+\n+    python3 misc/incremental_checker.py 2a432b\n+\"\"\"\n+\n+from typing import Any, Dict, List, Tuple\n+\n+from argparse import ArgumentParser, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter\n+import json\n+import os\n+import shutil\n+import subprocess\n+import sys\n+import textwrap\n+import time\n+\n+\n+DEFAULT_START = \"-30\"\n+CACHE_PATH = \".incremental_checker_cache.json\"\n+MYPY_REPO_URL = \"https://github.com/python/mypy.git\"\n+MYPY_MODULE_NAME = \"mypy\"\n+\n+JsonDict = Dict[str, Any]\n+\n+\n+def print_offset(text: str, indent_length: int = 4) -> None:\n+    print()\n+    print(textwrap.indent(text, ' ' * indent_length))\n+    print()\n+\n+\n+def delete_folder(folder_path: str) -> None:\n+    if os.path.exists(folder_path):\n+        shutil.rmtree(folder_path)\n+\n+\n+def absolute_path(*paths: str) -> str:\n+    return os.path.abspath(os.path.join(*paths))\n+\n+\n+def execute(command: List[str], fail_on_error: bool = True) -> Tuple[str, str, int]:\n+    proc = subprocess.Popen(\n+        ' '.join(command),\n+        stderr=subprocess.PIPE,\n+        stdout=subprocess.PIPE,\n+        shell=True)\n+    stdout_bytes, stderr_bytes = proc.communicate()  # type: Tuple[bytes, bytes]\n+    stdout, stderr = stdout_bytes.decode('utf-8'), stderr_bytes.decode('utf-8')\n+    if fail_on_error and proc.returncode != 0:\n+        print('EXECUTED COMMAND:', ' '.join(command))\n+        print('RETURN CODE:', proc.returncode)\n+        print()\n+        print('STDOUT:')\n+        print_offset(stdout)\n+        print('STDERR:')\n+        print_offset(stderr)\n+        raise RuntimeError('Unexpected error from external tool.')\n+    return stdout, stderr, proc.returncode\n+\n+\n+def ensure_environment_is_ready(mypy_path: str, temp_repo_path: str, mypy_cache_path: str) -> None:\n+    os.chdir(mypy_path)\n+    delete_folder(temp_repo_path)\n+    delete_folder(\"misc/.mypy_cache\")\n+\n+\n+def initialize_repo(repo_url: str, temp_repo_path: str, branch: str) -> None:\n+    print(\"Cloning repo {0} to {1}\".format(repo_url, temp_repo_path))\n+    execute([\"git\", \"clone\", repo_url, temp_repo_path])\n+    if branch is not None:\n+        print(\"Checking out branch {}\".format(branch))\n+        execute([\"git\", \"-C\", temp_repo_path, \"checkout\", branch])\n+\n+\n+def get_commits(repo_folder_path: str, commit_range: str) -> List[Tuple[str, str]]:\n+    raw_data, _stderr, _errcode = execute([\n+        \"git\", \"-C\", repo_folder_path, \"log\", \"--reverse\", \"--oneline\", commit_range])\n+    output = []\n+    for line in raw_data.strip().split('\\n'):\n+        commit_id, _, message = line.partition(' ')\n+        output.append((commit_id, message))\n+    return output\n+\n+\n+def get_commits_starting_at(repo_folder_path: str, start_commit: str) -> List[Tuple[str, str]]:\n+    print(\"Fetching commits starting at {0}\".format(start_commit))\n+    return get_commits(repo_folder_path, '{0}^..HEAD'.format(start_commit))\n+\n+\n+def get_nth_commit(repo_folder_path, n: int) -> Tuple[str, str]:\n+    print(\"Fetching last {} commits (or earliest)\".format(n))\n+    return get_commits(repo_folder_path, '-{}'.format(n))[0]\n+\n+\n+def run_mypy(target: str,\n+             mypy_cache_location: str,\n+             incremental: bool = True,\n+             verbose: bool = False) -> Tuple[float, str]:\n+    command = [\"python3\", \"-m\", \"mypy\", \"--cache-dir\", mypy_cache_location]\n+    if incremental:\n+        command.append(\"--incremental\")\n+    if verbose:\n+        command.extend([\"-v\", \"-v\"])\n+    command.append(target)\n+    start = time.time()\n+    output, stderr, _ = execute(command, False)\n+    if stderr != \"\":\n+        output = stderr\n+    runtime = time.time() - start\n+    return runtime, output\n+\n+\n+def load_cache(incremental_cache_path: str = CACHE_PATH) -> JsonDict:\n+    if os.path.exists(incremental_cache_path):\n+        with open(incremental_cache_path, 'r') as stream:\n+            return json.load(stream)\n+    else:\n+        return {}\n+\n+\n+def save_cache(cache: JsonDict, incremental_cache_path: str = CACHE_PATH) -> None:\n+    with open(incremental_cache_path, 'w') as stream:\n+        json.dump(cache, stream, indent=2)\n+\n+\n+def set_expected(commits: List[Tuple[str, str]],\n+                 cache: JsonDict,\n+                 temp_repo_path: str,\n+                 module_path: str,\n+                 mypy_cache_path: str) -> None:\n+    for commit_id, message in commits:\n+        if commit_id in cache:\n+            print('Skipping commit (already cached): {0}: \"{1}\"'.format(commit_id, message))\n+        else:\n+            print('Caching expected output for commit {0}: \"{1}\"'.format(commit_id, message))\n+            execute([\"git\", \"-C\", temp_repo_path, \"checkout\", commit_id])\n+            runtime, output = run_mypy(module_path, mypy_cache_path, incremental=False)\n+            cache[commit_id] = {'runtime': runtime, 'output': output}\n+            if output == \"\":\n+                print(\"    Clean output ({:.3f} sec)\".format(runtime))\n+            else:\n+                print(\"    Output ({:.3f} sec)\".format(runtime))\n+                print_offset(output, 8)\n+    print()\n+\n+\n+def test_incremental(commits: List[Tuple[str, str]],\n+                     cache: JsonDict,\n+                     temp_repo_path: str,\n+                     module_path: str,\n+                     mypy_cache_path: str) -> None:\n+    print(\"Note: first commit is evaluated twice to warm up cache\")\n+    commits = [commits[0]] + commits\n+    for commit_id, message in commits:\n+        print('Now testing commit {0}: \"{1}\"'.format(commit_id, message))\n+        execute([\"git\", \"-C\", temp_repo_path, \"checkout\", commit_id])\n+        runtime, output = run_mypy(module_path, mypy_cache_path, incremental=True)\n+        expected_runtime = cache[commit_id]['runtime']  # type: float\n+        expected_output = cache[commit_id]['output']  # type: str\n+        if output != expected_output:\n+            print(\"    Output does not match expected result!\")\n+            print(\"    Expected output ({:.3f} sec):\".format(expected_runtime))\n+            print_offset(expected_output, 8)\n+            print(\"    Actual output: ({:.3f} sec):\".format(runtime))\n+            print_offset(output, 8)\n+        else:\n+            print(\"    Output matches expected result!\")\n+            print(\"    Incremental: {:.3f} sec\".format(runtime))\n+            print(\"    Original:    {:.3f} sec\".format(expected_runtime))\n+\n+\n+def cleanup(temp_repo_path: str, mypy_cache_path: str) -> None:\n+    delete_folder(temp_repo_path)\n+    delete_folder(\"misc/.mypy_cache\")\n+\n+\n+def test_repo(target_repo_url: str, temp_repo_path: str, module_path: str,\n+              mypy_path: str, incremental_cache_path: str, mypy_cache_path: str,\n+              range_start: str, branch: str) -> None:\n+    \"\"\"Tests incremental mode against the repo specified in `target_repo_url`.\n+\n+    This algorithm runs in four main stages:\n+\n+    1.  Clones `target_repo_url` into the `temp_repo_path` folder locally,\n+        checking out the specified `branch` if applicable.\n+    2.  Examines the repo's history to get the list of all commits to\n+        to test incremental mode on.\n+    3.  Runs mypy WITHOUT incremental mode against the `module_path` (which is\n+        assumed to be located inside the `temp_repo_path`), testing each commit\n+        discovered in stage two.\n+        -   If the results of running mypy WITHOUT incremental mode on a\n+            particular commit are already cached inside the `incremental_cache_path`,\n+            skip that commit to save time.\n+        -   Cache the results after finishing.\n+    4.  Rewind back to the first commit, and run mypy WITH incremental mode\n+        against the `module_path` commit-by-commit, and compare to the expected\n+        results found in stage 3.\n+    \"\"\"\n+    # Stage 1: Clone repo and get ready to being testing\n+    ensure_environment_is_ready(mypy_path, temp_repo_path, mypy_cache_path)\n+    initialize_repo(target_repo_url, temp_repo_path, branch)\n+\n+    # Stage 2: Get all commits we want to test\n+    if range_start.startswith(\"-\"):\n+        start_commit = get_nth_commit(temp_repo_path, int(range_start[1:]))[0]\n+    else:\n+        start_commit = range_start\n+    commits = get_commits_starting_at(temp_repo_path, start_commit)\n+\n+    # Stage 3: Find and cache expected results for each commit (without incremental mode)\n+    cache = load_cache(incremental_cache_path)\n+    set_expected(commits, cache, temp_repo_path, module_path, mypy_cache_path)\n+    save_cache(cache, incremental_cache_path)\n+\n+    # Stage 4: Rewind and re-run mypy (with incremental mode enabled)\n+    test_incremental(commits, cache, temp_repo_path, module_path, mypy_cache_path)\n+    cleanup(temp_repo_path, mypy_cache_path)","path":"misc/incremental_checker.py","position":231,"original_position":231,"commit_id":"d95b4476a6eca076da5830cb7b394ae6b176752f","original_commit_id":"d95b4476a6eca076da5830cb7b394ae6b176752f","user":{"login":"gvanrossum","id":2894642,"avatar_url":"https://avatars.githubusercontent.com/u/2894642?v=3","gravatar_id":"","url":"https://api.github.com/users/gvanrossum","html_url":"https://github.com/gvanrossum","followers_url":"https://api.github.com/users/gvanrossum/followers","following_url":"https://api.github.com/users/gvanrossum/following{/other_user}","gists_url":"https://api.github.com/users/gvanrossum/gists{/gist_id}","starred_url":"https://api.github.com/users/gvanrossum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gvanrossum/subscriptions","organizations_url":"https://api.github.com/users/gvanrossum/orgs","repos_url":"https://api.github.com/users/gvanrossum/repos","events_url":"https://api.github.com/users/gvanrossum/events{/privacy}","received_events_url":"https://api.github.com/users/gvanrossum/received_events","type":"User","site_admin":false},"body":"This is not really part of stage 4, is it?","created_at":"2016-07-15T22:06:41Z","updated_at":"2016-07-15T22:06:41Z","html_url":"https://github.com/python/mypy/pull/1870#discussion_r71047459","pull_request_url":"https://api.github.com/repos/python/mypy/pulls/1870","_links":{"self":{"href":"https://api.github.com/repos/python/mypy/pulls/comments/71047459"},"html":{"href":"https://github.com/python/mypy/pull/1870#discussion_r71047459"},"pull_request":{"href":"https://api.github.com/repos/python/mypy/pulls/1870"}}},"pull_request":{"url":"https://api.github.com/repos/python/mypy/pulls/1870","id":77559762,"html_url":"https://github.com/python/mypy/pull/1870","diff_url":"https://github.com/python/mypy/pull/1870.diff","patch_url":"https://github.com/python/mypy/pull/1870.patch","issue_url":"https://api.github.com/repos/python/mypy/issues/1870","number":1870,"state":"open","locked":false,"title":"Add script to stress-test incremental mode","user":{"login":"Michael0x2a","id":1424628,"avatar_url":"https://avatars.githubusercontent.com/u/1424628?v=3","gravatar_id":"","url":"https://api.github.com/users/Michael0x2a","html_url":"https://github.com/Michael0x2a","followers_url":"https://api.github.com/users/Michael0x2a/followers","following_url":"https://api.github.com/users/Michael0x2a/following{/other_user}","gists_url":"https://api.github.com/users/Michael0x2a/gists{/gist_id}","starred_url":"https://api.github.com/users/Michael0x2a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Michael0x2a/subscriptions","organizations_url":"https://api.github.com/users/Michael0x2a/orgs","repos_url":"https://api.github.com/users/Michael0x2a/repos","events_url":"https://api.github.com/users/Michael0x2a/events{/privacy}","received_events_url":"https://api.github.com/users/Michael0x2a/received_events","type":"User","site_admin":false},"body":"The `incremental_checker.py` script, when run within the `misc` folder, will run incremental mode on a portion of mypy's commit history using the version of mypy in the parent folder.\r\n\r\nThe checker script will check out an independent copy of the mypy repo to test to avoid interfering with the current repo.","created_at":"2016-07-15T03:47:12Z","updated_at":"2016-07-15T22:06:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6f1903db1d6afe35419de933880bfd76ae324b17","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/python/mypy/pulls/1870/commits","review_comments_url":"https://api.github.com/repos/python/mypy/pulls/1870/comments","review_comment_url":"https://api.github.com/repos/python/mypy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/mypy/issues/1870/comments","statuses_url":"https://api.github.com/repos/python/mypy/statuses/d95b4476a6eca076da5830cb7b394ae6b176752f","head":{"label":"Michael0x2a:incremental-mode-stress-test","ref":"incremental-mode-stress-test","sha":"d95b4476a6eca076da5830cb7b394ae6b176752f","user":{"login":"Michael0x2a","id":1424628,"avatar_url":"https://avatars.githubusercontent.com/u/1424628?v=3","gravatar_id":"","url":"https://api.github.com/users/Michael0x2a","html_url":"https://github.com/Michael0x2a","followers_url":"https://api.github.com/users/Michael0x2a/followers","following_url":"https://api.github.com/users/Michael0x2a/following{/other_user}","gists_url":"https://api.github.com/users/Michael0x2a/gists{/gist_id}","starred_url":"https://api.github.com/users/Michael0x2a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Michael0x2a/subscriptions","organizations_url":"https://api.github.com/users/Michael0x2a/orgs","repos_url":"https://api.github.com/users/Michael0x2a/repos","events_url":"https://api.github.com/users/Michael0x2a/events{/privacy}","received_events_url":"https://api.github.com/users/Michael0x2a/received_events","type":"User","site_admin":false},"repo":{"id":62247533,"name":"mypy","full_name":"Michael0x2a/mypy","owner":{"login":"Michael0x2a","id":1424628,"avatar_url":"https://avatars.githubusercontent.com/u/1424628?v=3","gravatar_id":"","url":"https://api.github.com/users/Michael0x2a","html_url":"https://github.com/Michael0x2a","followers_url":"https://api.github.com/users/Michael0x2a/followers","following_url":"https://api.github.com/users/Michael0x2a/following{/other_user}","gists_url":"https://api.github.com/users/Michael0x2a/gists{/gist_id}","starred_url":"https://api.github.com/users/Michael0x2a/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Michael0x2a/subscriptions","organizations_url":"https://api.github.com/users/Michael0x2a/orgs","repos_url":"https://api.github.com/users/Michael0x2a/repos","events_url":"https://api.github.com/users/Michael0x2a/events{/privacy}","received_events_url":"https://api.github.com/users/Michael0x2a/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Michael0x2a/mypy","description":"Optional static typing for Python 2 and 3 (PEP484)","fork":true,"url":"https://api.github.com/repos/Michael0x2a/mypy","forks_url":"https://api.github.com/repos/Michael0x2a/mypy/forks","keys_url":"https://api.github.com/repos/Michael0x2a/mypy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Michael0x2a/mypy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Michael0x2a/mypy/teams","hooks_url":"https://api.github.com/repos/Michael0x2a/mypy/hooks","issue_events_url":"https://api.github.com/repos/Michael0x2a/mypy/issues/events{/number}","events_url":"https://api.github.com/repos/Michael0x2a/mypy/events","assignees_url":"https://api.github.com/repos/Michael0x2a/mypy/assignees{/user}","branches_url":"https://api.github.com/repos/Michael0x2a/mypy/branches{/branch}","tags_url":"https://api.github.com/repos/Michael0x2a/mypy/tags","blobs_url":"https://api.github.com/repos/Michael0x2a/mypy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Michael0x2a/mypy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Michael0x2a/mypy/git/refs{/sha}","trees_url":"https://api.github.com/repos/Michael0x2a/mypy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Michael0x2a/mypy/statuses/{sha}","languages_url":"https://api.github.com/repos/Michael0x2a/mypy/languages","stargazers_url":"https://api.github.com/repos/Michael0x2a/mypy/stargazers","contributors_url":"https://api.github.com/repos/Michael0x2a/mypy/contributors","subscribers_url":"https://api.github.com/repos/Michael0x2a/mypy/subscribers","subscription_url":"https://api.github.com/repos/Michael0x2a/mypy/subscription","commits_url":"https://api.github.com/repos/Michael0x2a/mypy/commits{/sha}","git_commits_url":"https://api.github.com/repos/Michael0x2a/mypy/git/commits{/sha}","comments_url":"https://api.github.com/repos/Michael0x2a/mypy/comments{/number}","issue_comment_url":"https://api.github.com/repos/Michael0x2a/mypy/issues/comments{/number}","contents_url":"https://api.github.com/repos/Michael0x2a/mypy/contents/{+path}","compare_url":"https://api.github.com/repos/Michael0x2a/mypy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Michael0x2a/mypy/merges","archive_url":"https://api.github.com/repos/Michael0x2a/mypy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Michael0x2a/mypy/downloads","issues_url":"https://api.github.com/repos/Michael0x2a/mypy/issues{/number}","pulls_url":"https://api.github.com/repos/Michael0x2a/mypy/pulls{/number}","milestones_url":"https://api.github.com/repos/Michael0x2a/mypy/milestones{/number}","notifications_url":"https://api.github.com/repos/Michael0x2a/mypy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Michael0x2a/mypy/labels{/name}","releases_url":"https://api.github.com/repos/Michael0x2a/mypy/releases{/id}","deployments_url":"https://api.github.com/repos/Michael0x2a/mypy/deployments","created_at":"2016-06-29T18:12:20Z","updated_at":"2016-06-29T18:12:22Z","pushed_at":"2016-07-15T20:27:24Z","git_url":"git://github.com/Michael0x2a/mypy.git","ssh_url":"git@github.com:Michael0x2a/mypy.git","clone_url":"https://github.com/Michael0x2a/mypy.git","svn_url":"https://github.com/Michael0x2a/mypy","homepage":"http://www.mypy-lang.org/","size":14189,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"python:master","ref":"master","sha":"64c0acd4e10e7ba443b1fffba6e945e6943cd6ea","user":{"login":"python","id":1525981,"avatar_url":"https://avatars.githubusercontent.com/u/1525981?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"Organization","site_admin":false},"repo":{"id":7053637,"name":"mypy","full_name":"python/mypy","owner":{"login":"python","id":1525981,"avatar_url":"https://avatars.githubusercontent.com/u/1525981?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/python/mypy","description":"Optional static typing for Python 2 and 3 (PEP484)","fork":false,"url":"https://api.github.com/repos/python/mypy","forks_url":"https://api.github.com/repos/python/mypy/forks","keys_url":"https://api.github.com/repos/python/mypy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/mypy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/mypy/teams","hooks_url":"https://api.github.com/repos/python/mypy/hooks","issue_events_url":"https://api.github.com/repos/python/mypy/issues/events{/number}","events_url":"https://api.github.com/repos/python/mypy/events","assignees_url":"https://api.github.com/repos/python/mypy/assignees{/user}","branches_url":"https://api.github.com/repos/python/mypy/branches{/branch}","tags_url":"https://api.github.com/repos/python/mypy/tags","blobs_url":"https://api.github.com/repos/python/mypy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/mypy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/mypy/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/mypy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/mypy/statuses/{sha}","languages_url":"https://api.github.com/repos/python/mypy/languages","stargazers_url":"https://api.github.com/repos/python/mypy/stargazers","contributors_url":"https://api.github.com/repos/python/mypy/contributors","subscribers_url":"https://api.github.com/repos/python/mypy/subscribers","subscription_url":"https://api.github.com/repos/python/mypy/subscription","commits_url":"https://api.github.com/repos/python/mypy/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/mypy/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/mypy/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/mypy/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/mypy/contents/{+path}","compare_url":"https://api.github.com/repos/python/mypy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/mypy/merges","archive_url":"https://api.github.com/repos/python/mypy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/mypy/downloads","issues_url":"https://api.github.com/repos/python/mypy/issues{/number}","pulls_url":"https://api.github.com/repos/python/mypy/pulls{/number}","milestones_url":"https://api.github.com/repos/python/mypy/milestones{/number}","notifications_url":"https://api.github.com/repos/python/mypy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/mypy/labels{/name}","releases_url":"https://api.github.com/repos/python/mypy/releases{/id}","deployments_url":"https://api.github.com/repos/python/mypy/deployments","created_at":"2012-12-07T13:30:23Z","updated_at":"2016-07-15T20:10:32Z","pushed_at":"2016-07-15T20:27:24Z","git_url":"git://github.com/python/mypy.git","ssh_url":"git@github.com:python/mypy.git","clone_url":"https://github.com/python/mypy.git","svn_url":"https://github.com/python/mypy","homepage":"http://www.mypy-lang.org/","size":16298,"stargazers_count":1583,"watchers_count":1583,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":190,"mirror_url":null,"open_issues_count":446,"forks":190,"open_issues":446,"watchers":1583,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/mypy/pulls/1870"},"html":{"href":"https://github.com/python/mypy/pull/1870"},"issue":{"href":"https://api.github.com/repos/python/mypy/issues/1870"},"comments":{"href":"https://api.github.com/repos/python/mypy/issues/1870/comments"},"review_comments":{"href":"https://api.github.com/repos/python/mypy/pulls/1870/comments"},"review_comment":{"href":"https://api.github.com/repos/python/mypy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/mypy/pulls/1870/commits"},"statuses":{"href":"https://api.github.com/repos/python/mypy/statuses/d95b4476a6eca076da5830cb7b394ae6b176752f"}}}}
{"actor":{"display_login":"gvanrossum"}}