GitHub PullRequestReviewCommentEvent at 2014-08-26 06:08:12
Repository: cliche
Actor: dahlia
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/clicheio/cliche/pulls/comments/16697208","id":16697208,"diff_hunk":"@@ -9,150 +9,246 @@\n from datetime import datetime, timedelta\n import urllib.parse\n \n+import requests\n+\n+from celery.signals import worker_process_init\n from celery.utils.log import get_task_logger\n-from lxml.html import parse\n+from lxml.html import parse, document_fromstring\n from sqlalchemy import create_engine\n from sqlalchemy.exc import IntegrityError\n+from sqlalchemy.orm.exc import FlushError, NoResultFound\n \n-from .entities import Entity, Relation\n+from .entities import Entity, Relation, Redirection\n from ...orm import Base, Session\n from ...worker import worker\n \n \n-INDEX_INDEX = 'http://tvtropes.org/pmwiki/index_report.php'\n-WIKI_PAGE = 'http://tvtropes.org/pmwiki/pmwiki.php/'\n+BASE_URL = 'http://tvtropes.org/pmwiki/'\n+INDEX_INDEX = urllib.parse.urljoin(BASE_URL, 'index_report.php')\n+WIKI_PAGE = urllib.parse.urljoin(BASE_URL, 'pmwiki.php/')\n+RELATED_SEARCH = urllib.parse.urljoin(BASE_URL, 'relatedsearch.php?term=')\n+\n CRAWL_INTERVAL = timedelta(days=7)\n \n \n+db_engine = None\n+\n+\n+@worker_process_init.connect\n+def establish_database_connection(*args, **kwargs):\n+    global db_engine\n+    db_engine = create_engine(worker.conf.DATABASE_URL)\n+\n+\n def determine_type(namespace):\n-    return 'Trope' if namespace == 'Main' else 'Work'\n+    if namespace == 'Main':\n+        return 'Trope'\n+    elif namespace == 'Administrivia':\n+        return 'Administrivia'\n+    else:\n+        return 'Work'\n \n \n def list_pages(namespace_url=None):\n     list_url = namespace_url or INDEX_INDEX\n-    print('Crawling {}'.format(list_url))\n     tree = parse(list_url)\n \n     for a in tree.xpath('//a[@class=\"twikilink\"]'):\n-        name = a.text.strip()\n         url = a.attrib['href']\n-        if namespace_url:\n-            yield (name,), url\n-        else:\n-            yield ('Main', name), url\n+        if WIKI_PAGE in url:\n+            yield url\n \n     if not namespace_url:\n         namespaces = tree.xpath(\n             '//a[starts-with(@href, \"index_report.php?groupname=\")]'\n         )\n+        print(' {} more.'.format(len(namespaces)), flush=True)\n \n+        count = 0\n         for a in namespaces:\n-            namespace = a.text.strip()\n+            count += 1\n+            if count % 10 == 0:\n+                print('/', end=\"\", flush=True)\n+            else:\n+                print('.', end=\"\", flush=True)\n+            if \"index_report.php?groupname=Administrivia\" in a.attrib['href']:\n+                continue\n             url = urllib.parse.urljoin(\n                 INDEX_INDEX, a.attrib['href']\n             )\n-            for key, value in list_pages(url):\n-                assert len(key) == 1\n-                yield (namespace,) + key, value\n+            for value in list_pages(url):\n+                yield value\n \n \n-@worker.task\n-def save_link(namepair, url):\n-    namespace, name = namepair\n-    engine = create_engine(worker.conf.DATABASE_URL)\n-    session = Session(bind=engine)\n+def new_or_update_entity(session, namespace, name, type, url):\n     try:\n         with session.begin():\n             new_entity = Entity(\n                 namespace=namespace,\n                 name=name,\n                 url=url,\n-                type=determine_type(namespace)\n+                type=type\n             )\n             session.add(new_entity)\n-    except IntegrityError:\n+    except (FlushError, IntegrityError):\n         with session.begin():\n             entity = session.query(Entity) \\\n                             .filter_by(namespace=namespace, name=name) \\\n                             .one()\n             entity.url = url\n-    get_task_logger(__name__ + '.save_link').info(\n-        'Total %d',\n-        session.query(Entity).count()\n-    )\n+\n+\n+def process_redirections(session, original_url, final_url, namespace, name):\n+    # note that indirection is not considered:\n+    # only the original and final path are saved.\n+    *_, original_path = urllib.parse.urlparse(original_url).path.split('/', 3)\n+    *_, final_path = urllib.parse.urlparse(final_url).path.split('/', 3)\n+    if original_path != final_path:\n+        rel = requests.get(RELATED_SEARCH + final_path)\n+        reltree = document_fromstring(rel.text)\n+        for link in reltree.xpath(\n+            \"//div[@id='wikimiddle']/div[re:test(text(),\"\n+            \"'This article is the target of [0-9]+ redirect\\(s\\)\\.'\"\n+            \")]/ul/li/a\",\n+            namespaces={'re': \"http://exslt.org/regular-expressions\"}\n+        ):\n+            alias_namespace = link.text[0:link.text.index('/')]\n+            alias_name = link.text[link.text.index('/') + 1:]\n+            try:\n+                with session.begin():\n+                    new_redirection = Redirection(\n+                        alias_namespace=alias_namespace,\n+                        alias_name=alias_name,\n+                        original_namespace=namespace,\n+                        original_name=name\n+                    )\n+                    session.add(new_redirection)\n+            except (FlushError, IntegrityError):\n+                with session.begin():\n+                    redirection = session.query(Redirection) \\\n+                        .filter_by(\n+                            alias_namespace=alias_namespace,\n+                            alias_name=alias_name\n+                        ) \\\n+                        .one()\n+                    redirection.original_namespace = namespace\n+                    redirection.original_name = name\n \n \n def fetch_link(url, session):\n-    tree = parse(url)\n+    r = requests.get(url)\n+    try:\n+        final_url = r.url[:r.url.index('?')]\n+    except ValueError:\n+        final_url = r.url\n+    if not is_wiki_page(final_url):\n+        return False, None, None, None, None, final_url\n+    tree = document_fromstring(r.text)\n     try:\n         namespace = tree.xpath('//div[@class=\"pagetitle\"]')[0] \\\n             .text.strip()[:-1]\n     except (AttributeError, AssertionError, IndexError):\n-        return None\n+        return False, tree, None, None, None, final_url\n     if namespace == '':\n         namespace = 'Main'\n     name = tree.xpath('//div[@class=\"pagetitle\"]/span')[0].text.strip()\n-    with session.begin():\n-        new_entity = Entity(\n-            namespace=namespace,\n-            name=name,\n-            url=url,\n-            type=determine_type(namespace)\n-        )\n-        session.add(new_entity)\n-    return tree, namespace, name\n+\n+    type = determine_type(namespace)\n+    if type == 'Administrivia':\n+        return False, tree, namespace, name, type, final_url\n+    new_or_update_entity(session, namespace, name, type, final_url)\n+    process_redirections(session, url, final_url, namespace, name)\n+    return True, tree, namespace, name, type, final_url\n+\n+\n+def recently_crawled(current_time, url, session):\n+    try:\n+        last_crawled = session.query(Entity).filter_by(url=url) \\\n+                              .one().last_crawled\n+        if last_crawled:\n+            if current_time - last_crawled < CRAWL_INTERVAL:\n+                return True\n+    except NoResultFound:\n+        pass\n+    return False\n+\n+\n+def is_wiki_page(url):\n+    return (BASE_URL not in url or WIKI_PAGE in url)\n \n \n @worker.task\n-def crawl_link(url, start_time,\n-               start_indexindex_count, start_relations_count,\n-               round_count):\n-    engine = create_engine(worker.conf.DATABASE_URL)\n-    session = Session(bind=engine)\n+def crawl_link(url):\n+    global db_engine\n+    session = Session(bind=db_engine)\n     logger = get_task_logger(__name__ + '.crawl_link')\n     current_time = datetime.now()\n-\n-    last_crawled = session.query(Entity).filter_by(url=url) \\\n-                          .first().last_crawled\n-    if last_crawled:\n-        if current_time - last_crawled < CRAWL_INTERVAL:\n-            logger.info('Skipping: {} due to '\n-                        'recent crawl in {} days'\n-                        .format(url, CRAWL_INTERVAL))\n-            return\n-\n+    if recently_crawled(current_time, url, session):\n+        logger.info('Skipping: {} due to '\n+                    'recent crawl in {} days'\n+                    .format(url, CRAWL_INTERVAL))\n+        return\n+    if not is_wiki_page(url):\n+        return\n     fetch_result = fetch_link(url, session)\n-    if fetch_result is None:\n-        logger.warning('Warning: There is no pagetitle on this page. '\n-                       'Ignoring.')\n+    result, tree, namespace, name, type, url = fetch_result\n+    if not is_wiki_page(url):\n+        return\n+    if name is None:\n+        logger.warning('Warning on url {}:'.format(url))\n+        logger.warning('There is no pagetitle on this page. Ignoring.')\n+        return\n+    elif not result:\n+        if not type == 'Administrivia':\n+            logger.warning('Warning on url {}: This page is not able to be'\n+                           ' crawled. Ignoring.'.format(url))\n+        return\n+    # make sure that if redirected, final url is not also recently crawled.\n+    if recently_crawled(current_time, url, session):\n+        logger.info('Skipping: {} due to '\n+                    'recent crawl in {} days'\n+                    .format(url, CRAWL_INTERVAL))","path":"cliche/services/tvtropes/crawler.py","position":257,"original_position":257,"commit_id":"747a7a6977e2c782d7ced71fc96f0d7ea4d016f4","original_commit_id":"747a7a6977e2c782d7ced71fc96f0d7ea4d016f4","user":{"login":"dahlia","id":12431,"avatar_url":"https://avatars.githubusercontent.com/u/12431?v=2","gravatar_id":"f0d32b762b34cfc337c9fba03bc3ced4","url":"https://api.github.com/users/dahlia","html_url":"https://github.com/dahlia","followers_url":"https://api.github.com/users/dahlia/followers","following_url":"https://api.github.com/users/dahlia/following{/other_user}","gists_url":"https://api.github.com/users/dahlia/gists{/gist_id}","starred_url":"https://api.github.com/users/dahlia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dahlia/subscriptions","organizations_url":"https://api.github.com/users/dahlia/orgs","repos_url":"https://api.github.com/users/dahlia/repos","events_url":"https://api.github.com/users/dahlia/events{/privacy}","received_events_url":"https://api.github.com/users/dahlia/received_events","type":"User","site_admin":false},"body":"[Same here.](#r16697186)","created_at":"2014-08-26T06:08:12Z","updated_at":"2014-08-26T06:08:12Z","html_url":"https://github.com/clicheio/cliche/pull/7#discussion_r16697208","pull_request_url":"https://api.github.com/repos/clicheio/cliche/pulls/7","_links":{"self":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/comments/16697208"},"html":{"href":"https://github.com/clicheio/cliche/pull/7#discussion_r16697208"},"pull_request":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/7"}}},"pull_request":{"url":"https://api.github.com/repos/clicheio/cliche/pulls/7","id":19762077,"html_url":"https://github.com/clicheio/cliche/pull/7","diff_url":"https://github.com/clicheio/cliche/pull/7.diff","patch_url":"https://github.com/clicheio/cliche/pull/7.patch","issue_url":"https://api.github.com/repos/clicheio/cliche/issues/7","number":7,"state":"open","locked":false,"title":"Bug fixes in tvtropes crawler","user":{"login":"tkiapril","id":7963440,"avatar_url":"https://avatars.githubusercontent.com/u/7963440?v=2","gravatar_id":"76f55877ab6ffab0ab683dcf5cf09bc5","url":"https://api.github.com/users/tkiapril","html_url":"https://github.com/tkiapril","followers_url":"https://api.github.com/users/tkiapril/followers","following_url":"https://api.github.com/users/tkiapril/following{/other_user}","gists_url":"https://api.github.com/users/tkiapril/gists{/gist_id}","starred_url":"https://api.github.com/users/tkiapril/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tkiapril/subscriptions","organizations_url":"https://api.github.com/users/tkiapril/orgs","repos_url":"https://api.github.com/users/tkiapril/repos","events_url":"https://api.github.com/users/tkiapril/events{/privacy}","received_events_url":"https://api.github.com/users/tkiapril/received_events","type":"User","site_admin":false},"body":"Too many connection errors and some exceptions such as IntegrityError, FlushError and MultipleResultsFound are handled.\r\nThese commits are currently functional. \r\n","created_at":"2014-08-14T00:22:14Z","updated_at":"2014-08-26T06:08:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d193a58717eb3b3c306992014bbeadab2150aaa9","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/clicheio/cliche/pulls/7/commits","review_comments_url":"https://api.github.com/repos/clicheio/cliche/pulls/7/comments","review_comment_url":"https://api.github.com/repos/clicheio/cliche/pulls/comments/{number}","comments_url":"https://api.github.com/repos/clicheio/cliche/issues/7/comments","statuses_url":"https://api.github.com/repos/clicheio/cliche/statuses/747a7a6977e2c782d7ced71fc96f0d7ea4d016f4","head":{"label":"tkiapril:sqlalchemy","ref":"sqlalchemy","sha":"747a7a6977e2c782d7ced71fc96f0d7ea4d016f4","user":{"login":"tkiapril","id":7963440,"avatar_url":"https://avatars.githubusercontent.com/u/7963440?v=2","gravatar_id":"76f55877ab6ffab0ab683dcf5cf09bc5","url":"https://api.github.com/users/tkiapril","html_url":"https://github.com/tkiapril","followers_url":"https://api.github.com/users/tkiapril/followers","following_url":"https://api.github.com/users/tkiapril/following{/other_user}","gists_url":"https://api.github.com/users/tkiapril/gists{/gist_id}","starred_url":"https://api.github.com/users/tkiapril/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tkiapril/subscriptions","organizations_url":"https://api.github.com/users/tkiapril/orgs","repos_url":"https://api.github.com/users/tkiapril/repos","events_url":"https://api.github.com/users/tkiapril/events{/privacy}","received_events_url":"https://api.github.com/users/tkiapril/received_events","type":"User","site_admin":false},"repo":{"id":22629592,"name":"cliche","full_name":"tkiapril/cliche","owner":{"login":"tkiapril","id":7963440,"avatar_url":"https://avatars.githubusercontent.com/u/7963440?v=2","gravatar_id":"76f55877ab6ffab0ab683dcf5cf09bc5","url":"https://api.github.com/users/tkiapril","html_url":"https://github.com/tkiapril","followers_url":"https://api.github.com/users/tkiapril/followers","following_url":"https://api.github.com/users/tkiapril/following{/other_user}","gists_url":"https://api.github.com/users/tkiapril/gists{/gist_id}","starred_url":"https://api.github.com/users/tkiapril/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tkiapril/subscriptions","organizations_url":"https://api.github.com/users/tkiapril/orgs","repos_url":"https://api.github.com/users/tkiapril/repos","events_url":"https://api.github.com/users/tkiapril/events{/privacy}","received_events_url":"https://api.github.com/users/tkiapril/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tkiapril/cliche","description":"Cliche is an ontology of fictional works.","fork":true,"url":"https://api.github.com/repos/tkiapril/cliche","forks_url":"https://api.github.com/repos/tkiapril/cliche/forks","keys_url":"https://api.github.com/repos/tkiapril/cliche/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tkiapril/cliche/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tkiapril/cliche/teams","hooks_url":"https://api.github.com/repos/tkiapril/cliche/hooks","issue_events_url":"https://api.github.com/repos/tkiapril/cliche/issues/events{/number}","events_url":"https://api.github.com/repos/tkiapril/cliche/events","assignees_url":"https://api.github.com/repos/tkiapril/cliche/assignees{/user}","branches_url":"https://api.github.com/repos/tkiapril/cliche/branches{/branch}","tags_url":"https://api.github.com/repos/tkiapril/cliche/tags","blobs_url":"https://api.github.com/repos/tkiapril/cliche/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tkiapril/cliche/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tkiapril/cliche/git/refs{/sha}","trees_url":"https://api.github.com/repos/tkiapril/cliche/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tkiapril/cliche/statuses/{sha}","languages_url":"https://api.github.com/repos/tkiapril/cliche/languages","stargazers_url":"https://api.github.com/repos/tkiapril/cliche/stargazers","contributors_url":"https://api.github.com/repos/tkiapril/cliche/contributors","subscribers_url":"https://api.github.com/repos/tkiapril/cliche/subscribers","subscription_url":"https://api.github.com/repos/tkiapril/cliche/subscription","commits_url":"https://api.github.com/repos/tkiapril/cliche/commits{/sha}","git_commits_url":"https://api.github.com/repos/tkiapril/cliche/git/commits{/sha}","comments_url":"https://api.github.com/repos/tkiapril/cliche/comments{/number}","issue_comment_url":"https://api.github.com/repos/tkiapril/cliche/issues/comments/{number}","contents_url":"https://api.github.com/repos/tkiapril/cliche/contents/{+path}","compare_url":"https://api.github.com/repos/tkiapril/cliche/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tkiapril/cliche/merges","archive_url":"https://api.github.com/repos/tkiapril/cliche/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tkiapril/cliche/downloads","issues_url":"https://api.github.com/repos/tkiapril/cliche/issues{/number}","pulls_url":"https://api.github.com/repos/tkiapril/cliche/pulls{/number}","milestones_url":"https://api.github.com/repos/tkiapril/cliche/milestones{/number}","notifications_url":"https://api.github.com/repos/tkiapril/cliche/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tkiapril/cliche/labels{/name}","releases_url":"https://api.github.com/repos/tkiapril/cliche/releases{/id}","created_at":"2014-08-05T03:55:46Z","updated_at":"2014-08-13T23:10:39Z","pushed_at":"2014-08-26T03:51:36Z","git_url":"git://github.com/tkiapril/cliche.git","ssh_url":"git@github.com:tkiapril/cliche.git","clone_url":"https://github.com/tkiapril/cliche.git","svn_url":"https://github.com/tkiapril/cliche","homepage":null,"size":511,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"clicheio:master","ref":"master","sha":"e96fe5590ab2823965984d20290c9bf4b910466b","user":{"login":"clicheio","id":8352375,"avatar_url":"https://avatars.githubusercontent.com/u/8352375?v=2","gravatar_id":null,"url":"https://api.github.com/users/clicheio","html_url":"https://github.com/clicheio","followers_url":"https://api.github.com/users/clicheio/followers","following_url":"https://api.github.com/users/clicheio/following{/other_user}","gists_url":"https://api.github.com/users/clicheio/gists{/gist_id}","starred_url":"https://api.github.com/users/clicheio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clicheio/subscriptions","organizations_url":"https://api.github.com/users/clicheio/orgs","repos_url":"https://api.github.com/users/clicheio/repos","events_url":"https://api.github.com/users/clicheio/events{/privacy}","received_events_url":"https://api.github.com/users/clicheio/received_events","type":"Organization","site_admin":false},"repo":{"id":22606614,"name":"cliche","full_name":"clicheio/cliche","owner":{"login":"clicheio","id":8352375,"avatar_url":"https://avatars.githubusercontent.com/u/8352375?v=2","gravatar_id":null,"url":"https://api.github.com/users/clicheio","html_url":"https://github.com/clicheio","followers_url":"https://api.github.com/users/clicheio/followers","following_url":"https://api.github.com/users/clicheio/following{/other_user}","gists_url":"https://api.github.com/users/clicheio/gists{/gist_id}","starred_url":"https://api.github.com/users/clicheio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clicheio/subscriptions","organizations_url":"https://api.github.com/users/clicheio/orgs","repos_url":"https://api.github.com/users/clicheio/repos","events_url":"https://api.github.com/users/clicheio/events{/privacy}","received_events_url":"https://api.github.com/users/clicheio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/clicheio/cliche","description":"Cliche is an ontology of fictional works.","fork":false,"url":"https://api.github.com/repos/clicheio/cliche","forks_url":"https://api.github.com/repos/clicheio/cliche/forks","keys_url":"https://api.github.com/repos/clicheio/cliche/keys{/key_id}","collaborators_url":"https://api.github.com/repos/clicheio/cliche/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/clicheio/cliche/teams","hooks_url":"https://api.github.com/repos/clicheio/cliche/hooks","issue_events_url":"https://api.github.com/repos/clicheio/cliche/issues/events{/number}","events_url":"https://api.github.com/repos/clicheio/cliche/events","assignees_url":"https://api.github.com/repos/clicheio/cliche/assignees{/user}","branches_url":"https://api.github.com/repos/clicheio/cliche/branches{/branch}","tags_url":"https://api.github.com/repos/clicheio/cliche/tags","blobs_url":"https://api.github.com/repos/clicheio/cliche/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/clicheio/cliche/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/clicheio/cliche/git/refs{/sha}","trees_url":"https://api.github.com/repos/clicheio/cliche/git/trees{/sha}","statuses_url":"https://api.github.com/repos/clicheio/cliche/statuses/{sha}","languages_url":"https://api.github.com/repos/clicheio/cliche/languages","stargazers_url":"https://api.github.com/repos/clicheio/cliche/stargazers","contributors_url":"https://api.github.com/repos/clicheio/cliche/contributors","subscribers_url":"https://api.github.com/repos/clicheio/cliche/subscribers","subscription_url":"https://api.github.com/repos/clicheio/cliche/subscription","commits_url":"https://api.github.com/repos/clicheio/cliche/commits{/sha}","git_commits_url":"https://api.github.com/repos/clicheio/cliche/git/commits{/sha}","comments_url":"https://api.github.com/repos/clicheio/cliche/comments{/number}","issue_comment_url":"https://api.github.com/repos/clicheio/cliche/issues/comments/{number}","contents_url":"https://api.github.com/repos/clicheio/cliche/contents/{+path}","compare_url":"https://api.github.com/repos/clicheio/cliche/compare/{base}...{head}","merges_url":"https://api.github.com/repos/clicheio/cliche/merges","archive_url":"https://api.github.com/repos/clicheio/cliche/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/clicheio/cliche/downloads","issues_url":"https://api.github.com/repos/clicheio/cliche/issues{/number}","pulls_url":"https://api.github.com/repos/clicheio/cliche/pulls{/number}","milestones_url":"https://api.github.com/repos/clicheio/cliche/milestones{/number}","notifications_url":"https://api.github.com/repos/clicheio/cliche/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/clicheio/cliche/labels{/name}","releases_url":"https://api.github.com/repos/clicheio/cliche/releases{/id}","created_at":"2014-08-04T14:12:58Z","updated_at":"2014-08-18T09:01:37Z","pushed_at":"2014-08-21T17:00:15Z","git_url":"git://github.com/clicheio/cliche.git","ssh_url":"git@github.com:clicheio/cliche.git","clone_url":"https://github.com/clicheio/cliche.git","svn_url":"https://github.com/clicheio/cliche","homepage":"http://cliche.io/","size":835,"stargazers_count":3,"watchers_count":3,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"forks_count":5,"mirror_url":null,"open_issues_count":3,"forks":5,"open_issues":3,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/7"},"html":{"href":"https://github.com/clicheio/cliche/pull/7"},"issue":{"href":"https://api.github.com/repos/clicheio/cliche/issues/7"},"comments":{"href":"https://api.github.com/repos/clicheio/cliche/issues/7/comments"},"review_comments":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/7/comments"},"review_comment":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/clicheio/cliche/pulls/7/commits"},"statuses":{"href":"https://api.github.com/repos/clicheio/cliche/statuses/747a7a6977e2c782d7ced71fc96f0d7ea4d016f4"}}}}