{"action":"created","comment":{"url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/comments/135928257","pull_request_review_id":59397457,"id":135928257,"diff_hunk":"@@ -0,0 +1,171 @@\n+# This Source Code Form is subject to the terms of the Mozilla Public\n+# License, v. 2.0. If a copy of the MPL was not distributed with this\n+# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n+#\n+# Copyright (c) 2017 Tyler Goodlet <tgoodlet@gmail.com>\n+\"\"\"\n+Asyncio based reactor core\n+\"\"\"\n+import asyncio\n+from functools import partial\n+from collections import deque\n+import time\n+from threading import Thread, current_thread, get_ident\n+from .async import EventLoop\n+from . import utils\n+\n+\n+def new_event_loop():\n+    \"\"\"Get the fastest loop available.\n+    \"\"\"\n+    try:\n+        import uvloop\n+        return uvloop.new_event_loop()\n+    except ImportError:\n+        return asyncio.new_event_loop()\n+\n+\n+class AsyncIOEventLoop(EventLoop):\n+    \"\"\"Re-implementation of the event loop with ``asyncio`` and coroutines\n+    support.\n+    \"\"\"\n+    def _run_loop(self):\n+        self.loop = loop = new_event_loop()\n+        loop._tid = get_ident()\n+        loop.set_debug(True)\n+        asyncio.set_event_loop(loop)\n+        self.loop.run_forever()\n+\n+    def _launch_bg_loop(self):\n+        if self._thread is None or not self._thread.is_alive():\n+            self.log.debug(\"starting event loop thread...\")\n+            self._thread = Thread(\n+                target=self._run_loop, args=(),\n+                name='switchy_event_loop[{}]'.format(self.host),\n+            )\n+            self._thread.daemon = True  # die with parent\n+            self._thread.start()\n+\n+    def connect(self, loop=None):\n+        '''Initialize underlying receive connection.\n+        '''\n+        # TODO: once we remove SWIG/py27 support this check can be removed\n+        if self.connected() and self.is_alive():\n+            raise utils.ConfigurationError(\n+                \"event loop is already active, call 'disconnect()' first\")\n+        elif self.connected():\n+                self.log.info(\"event loop is already connected\")\n+                return\n+\n+        if not self.is_alive():\n+            self._launch_bg_loop()\n+            while not self.loop:\n+                time.sleep(0.1)\n+\n+        future = asyncio.run_coroutine_threadsafe(\n+            self._rx_con.connect(block=False, loop=self.loop), self.loop)\n+        future.result(1)\n+\n+        # subscribe for events\n+        self._rx_con.subscribe(\n+            (ev for ev in self._handlers if ev not in self._unsub))\n+        self.log.info(\"Connected event loop '{}' to '{}'\".format(self._id,\n+                      self.host))\n+\n+    def start(self):\n+        '''Start this loop's listen coroutine and start processing\n+        all received events.\n+        '''\n+        if not self._rx_con.connected():\n+            raise utils.ConfigurationError(\"you must call 'connect' first\")\n+\n+        self.listen = asyncio.run_coroutine_threadsafe(\n+            self._listen_forever(), loop=self.loop)\n+\n+    async def _listen_forever(self):\n+        '''Process events until stopped\n+        '''\n+        self.log.debug(\"starting listen loop\")\n+        self._running = True\n+        while self._rx_con.connected():\n+            # block waiting for next event\n+            e = await self._rx_con.recv_event()\n+            # self.log.warning(get_event_time(e) - self._fs_time)\n+            if e is None:\n+                self.log.debug(\"Exiting listen loop\")\n+                break\n+            elif not e:\n+                self.log.error(\"Received empty event!?\")\n+            else:\n+                evname = e.get('Event-Name')\n+                if evname:\n+                    consumed = self._process_event(e, evname)\n+                else:\n+                    self.log.warn(\"received unamed event '{}'?\".format(e))\n+                # append events which are not consumed\n+                if not consumed:\n+                    # store up to the last 1k of each event type\n+                    self.events.setdefault(\n+                        evname, deque(maxlen=1000)).append((e, time.time()))\n+\n+        self.log.debug(\"Exiting listen loop\")\n+        self._running = False\n+\n+    def _stop(self):\n+        '''Stop bg thread and event loop.\n+        '''\n+        if current_thread() is self._thread:\n+            self.log.warn(\"Stop called from event loop thread?\")\n+\n+        future = asyncio.run_coroutine_threadsafe(\n+            self._rx_con.adisconnect(), self.loop\n+        )\n+        # wait on disconnect success\n+        future.result(1)\n+        while self._rx_con.connected():\n+            time.sleep(0.1)\n+\n+        def trigger_exit():\n+            # manually signal listen-loop exit (usually stuck in polling\n+            # the queue for some weird reason?)\n+            self.loop.call_soon_threadsafe(\n+                self._rx_con.protocol.event_queue.put_nowait, None)\n+\n+        # trigger event consumer to terminate\n+        trigger_exit()\n+        pending = asyncio.Task.all_tasks(self.loop)\n+        self.loop.call_soon_threadsafe(\n+            partial(asyncio.gather, *pending, loop=self.loop))\n+\n+        # tear down the event loop\n+        self.loop.stop()\n+        for _ in range(10):\n+            if self.loop.is_running():\n+                trigger_exit()\n+                time.sleep(0.1)\n+            else:\n+                break\n+        else:\n+            if self.loop.is_running():\n+                raise TimeoutError(\"Failed to stop event loop {}\"\n+                                   .format(self.loop))\n+\n+    def add_handler(self, evname, handler):\n+        \"\"\"Register an event handler for events of type ``evname``.\n+        If a handler for ``evname`` already exists or if ``evname`` is in the\n+        unsubscribe list an error will be raised.\n+        \"\"\"\n+        if evname in self._unsub:\n+            raise utils.ConfigurationError(\n+                \"'{}' events have been unsubscribed for this event loop\"\n+                .format(evname))\n+        # TODO: add a force option which allows overwrite?\n+        if evname in self._handlers:","path":"switchy/reactor.py","position":163,"original_position":163,"commit_id":"2cb28af1ec031c8694389dd16975f97681146162","original_commit_id":"2cb28af1ec031c8694389dd16975f97681146162","user":{"login":"moises-silva","id":680665,"avatar_url":"https://avatars3.githubusercontent.com/u/680665?v=4","gravatar_id":"","url":"https://api.github.com/users/moises-silva","html_url":"https://github.com/moises-silva","followers_url":"https://api.github.com/users/moises-silva/followers","following_url":"https://api.github.com/users/moises-silva/following{/other_user}","gists_url":"https://api.github.com/users/moises-silva/gists{/gist_id}","starred_url":"https://api.github.com/users/moises-silva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/moises-silva/subscriptions","organizations_url":"https://api.github.com/users/moises-silva/orgs","repos_url":"https://api.github.com/users/moises-silva/repos","events_url":"https://api.github.com/users/moises-silva/events{/privacy}","received_events_url":"https://api.github.com/users/moises-silva/received_events","type":"User","site_admin":false},"body":"Personally I'd just override. Kinda like the sigaction() unix handler works. If a handler is registered already for a signal it just overwrites it. I understand the desire to prevent users from shooting themselves in the foot, but I'd say for an event-based API feels more normal to me that it'd simply override the existing handler if any. The API name may be more ergonomic if called set_handler() if override is possible.","created_at":"2017-08-29T22:26:37Z","updated_at":"2017-08-29T22:28:13Z","html_url":"https://github.com/friends-of-freeswitch/switchy/pull/4#discussion_r135928257","pull_request_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/comments/135928257"},"html":{"href":"https://github.com/friends-of-freeswitch/switchy/pull/4#discussion_r135928257"},"pull_request":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4"}}},"pull_request":{"url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4","id":136845492,"html_url":"https://github.com/friends-of-freeswitch/switchy/pull/4","diff_url":"https://github.com/friends-of-freeswitch/switchy/pull/4.diff","patch_url":"https://github.com/friends-of-freeswitch/switchy/pull/4.patch","issue_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/4","number":4,"state":"open","locked":false,"title":"Core asyncio support!","user":{"login":"tgoodlet","id":291685,"avatar_url":"https://avatars0.githubusercontent.com/u/291685?v=4","gravatar_id":"","url":"https://api.github.com/users/tgoodlet","html_url":"https://github.com/tgoodlet","followers_url":"https://api.github.com/users/tgoodlet/followers","following_url":"https://api.github.com/users/tgoodlet/following{/other_user}","gists_url":"https://api.github.com/users/tgoodlet/gists{/gist_id}","starred_url":"https://api.github.com/users/tgoodlet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgoodlet/subscriptions","organizations_url":"https://api.github.com/users/tgoodlet/orgs","repos_url":"https://api.github.com/users/tgoodlet/repos","events_url":"https://api.github.com/users/tgoodlet/events{/privacy}","received_events_url":"https://api.github.com/users/tgoodlet/received_events","type":"User","site_admin":false},"body":"Adds `asyncio` support to the core by providing an alternative protocol, connection, and event loop.\r\nOnce this gets in I'll submit a new PR which adds coroutine based \"app\" support (yey).\r\n\r\nBecause of the use of an `asyncio.Protocol` (suggested by @vodik) during high load ESL \"packets\" can be segmented across reads. This can result in mangled packets due to my refusal to make the parser any more complicated to handle such cases. @vodik mentioned he would take a look at maybe reimplementing that part to make it more reliable.\r\n\r\n@vodik @moises-silva I'm looking for some serious feedback here.\r\nAlso don't expect the tests to be all that reliable yet - there still seems to be some races here and there with the \"event loop in a separate thread\" stuff.","created_at":"2017-08-21T21:18:42Z","updated_at":"2017-08-29T22:28:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6545a432ba867affb89115bf9c6cd00f42e83811","assignee":{"login":"tgoodlet","id":291685,"avatar_url":"https://avatars0.githubusercontent.com/u/291685?v=4","gravatar_id":"","url":"https://api.github.com/users/tgoodlet","html_url":"https://github.com/tgoodlet","followers_url":"https://api.github.com/users/tgoodlet/followers","following_url":"https://api.github.com/users/tgoodlet/following{/other_user}","gists_url":"https://api.github.com/users/tgoodlet/gists{/gist_id}","starred_url":"https://api.github.com/users/tgoodlet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgoodlet/subscriptions","organizations_url":"https://api.github.com/users/tgoodlet/orgs","repos_url":"https://api.github.com/users/tgoodlet/repos","events_url":"https://api.github.com/users/tgoodlet/events{/privacy}","received_events_url":"https://api.github.com/users/tgoodlet/received_events","type":"User","site_admin":false},"assignees":[{"login":"tgoodlet","id":291685,"avatar_url":"https://avatars0.githubusercontent.com/u/291685?v=4","gravatar_id":"","url":"https://api.github.com/users/tgoodlet","html_url":"https://github.com/tgoodlet","followers_url":"https://api.github.com/users/tgoodlet/followers","following_url":"https://api.github.com/users/tgoodlet/following{/other_user}","gists_url":"https://api.github.com/users/tgoodlet/gists{/gist_id}","starred_url":"https://api.github.com/users/tgoodlet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgoodlet/subscriptions","organizations_url":"https://api.github.com/users/tgoodlet/orgs","repos_url":"https://api.github.com/users/tgoodlet/repos","events_url":"https://api.github.com/users/tgoodlet/events{/privacy}","received_events_url":"https://api.github.com/users/tgoodlet/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4/commits","review_comments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4/comments","review_comment_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/4/comments","statuses_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/statuses/2cb28af1ec031c8694389dd16975f97681146162","head":{"label":"friends-of-freeswitch:asyncio","ref":"asyncio","sha":"2cb28af1ec031c8694389dd16975f97681146162","user":{"login":"friends-of-freeswitch","id":28784258,"avatar_url":"https://avatars2.githubusercontent.com/u/28784258?v=4","gravatar_id":"","url":"https://api.github.com/users/friends-of-freeswitch","html_url":"https://github.com/friends-of-freeswitch","followers_url":"https://api.github.com/users/friends-of-freeswitch/followers","following_url":"https://api.github.com/users/friends-of-freeswitch/following{/other_user}","gists_url":"https://api.github.com/users/friends-of-freeswitch/gists{/gist_id}","starred_url":"https://api.github.com/users/friends-of-freeswitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friends-of-freeswitch/subscriptions","organizations_url":"https://api.github.com/users/friends-of-freeswitch/orgs","repos_url":"https://api.github.com/users/friends-of-freeswitch/repos","events_url":"https://api.github.com/users/friends-of-freeswitch/events{/privacy}","received_events_url":"https://api.github.com/users/friends-of-freeswitch/received_events","type":"Organization","site_admin":false},"repo":{"id":56267801,"name":"switchy","full_name":"friends-of-freeswitch/switchy","owner":{"login":"friends-of-freeswitch","id":28784258,"avatar_url":"https://avatars2.githubusercontent.com/u/28784258?v=4","gravatar_id":"","url":"https://api.github.com/users/friends-of-freeswitch","html_url":"https://github.com/friends-of-freeswitch","followers_url":"https://api.github.com/users/friends-of-freeswitch/followers","following_url":"https://api.github.com/users/friends-of-freeswitch/following{/other_user}","gists_url":"https://api.github.com/users/friends-of-freeswitch/gists{/gist_id}","starred_url":"https://api.github.com/users/friends-of-freeswitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friends-of-freeswitch/subscriptions","organizations_url":"https://api.github.com/users/friends-of-freeswitch/orgs","repos_url":"https://api.github.com/users/friends-of-freeswitch/repos","events_url":"https://api.github.com/users/friends-of-freeswitch/events{/privacy}","received_events_url":"https://api.github.com/users/friends-of-freeswitch/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/friends-of-freeswitch/switchy","description":"asyncio powered FreeSWITCH cluster control","fork":true,"url":"https://api.github.com/repos/friends-of-freeswitch/switchy","forks_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/forks","keys_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/teams","hooks_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/hooks","issue_events_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/events{/number}","events_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/events","assignees_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/assignees{/user}","branches_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/branches{/branch}","tags_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/tags","blobs_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/refs{/sha}","trees_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/statuses/{sha}","languages_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/languages","stargazers_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/stargazers","contributors_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/contributors","subscribers_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/subscribers","subscription_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/subscription","commits_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/commits{/sha}","git_commits_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/commits{/sha}","comments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/comments{/number}","issue_comment_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/comments{/number}","contents_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/contents/{+path}","compare_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/merges","archive_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/downloads","issues_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues{/number}","pulls_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls{/number}","milestones_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/milestones{/number}","notifications_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/labels{/name}","releases_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/releases{/id}","deployments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/deployments","created_at":"2016-04-14T20:25:07Z","updated_at":"2017-07-24T22:35:16Z","pushed_at":"2017-08-21T21:18:42Z","git_url":"git://github.com/friends-of-freeswitch/switchy.git","ssh_url":"git@github.com:friends-of-freeswitch/switchy.git","clone_url":"https://github.com/friends-of-freeswitch/switchy.git","svn_url":"https://github.com/friends-of-freeswitch/switchy","homepage":"https://switchy.readthedocs.org","size":3925,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"base":{"label":"friends-of-freeswitch:master","ref":"master","sha":"34628bb18eff3c50953d1a8796866c13d25903d1","user":{"login":"friends-of-freeswitch","id":28784258,"avatar_url":"https://avatars2.githubusercontent.com/u/28784258?v=4","gravatar_id":"","url":"https://api.github.com/users/friends-of-freeswitch","html_url":"https://github.com/friends-of-freeswitch","followers_url":"https://api.github.com/users/friends-of-freeswitch/followers","following_url":"https://api.github.com/users/friends-of-freeswitch/following{/other_user}","gists_url":"https://api.github.com/users/friends-of-freeswitch/gists{/gist_id}","starred_url":"https://api.github.com/users/friends-of-freeswitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friends-of-freeswitch/subscriptions","organizations_url":"https://api.github.com/users/friends-of-freeswitch/orgs","repos_url":"https://api.github.com/users/friends-of-freeswitch/repos","events_url":"https://api.github.com/users/friends-of-freeswitch/events{/privacy}","received_events_url":"https://api.github.com/users/friends-of-freeswitch/received_events","type":"Organization","site_admin":false},"repo":{"id":56267801,"name":"switchy","full_name":"friends-of-freeswitch/switchy","owner":{"login":"friends-of-freeswitch","id":28784258,"avatar_url":"https://avatars2.githubusercontent.com/u/28784258?v=4","gravatar_id":"","url":"https://api.github.com/users/friends-of-freeswitch","html_url":"https://github.com/friends-of-freeswitch","followers_url":"https://api.github.com/users/friends-of-freeswitch/followers","following_url":"https://api.github.com/users/friends-of-freeswitch/following{/other_user}","gists_url":"https://api.github.com/users/friends-of-freeswitch/gists{/gist_id}","starred_url":"https://api.github.com/users/friends-of-freeswitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friends-of-freeswitch/subscriptions","organizations_url":"https://api.github.com/users/friends-of-freeswitch/orgs","repos_url":"https://api.github.com/users/friends-of-freeswitch/repos","events_url":"https://api.github.com/users/friends-of-freeswitch/events{/privacy}","received_events_url":"https://api.github.com/users/friends-of-freeswitch/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/friends-of-freeswitch/switchy","description":"asyncio powered FreeSWITCH cluster control","fork":true,"url":"https://api.github.com/repos/friends-of-freeswitch/switchy","forks_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/forks","keys_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/teams","hooks_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/hooks","issue_events_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/events{/number}","events_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/events","assignees_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/assignees{/user}","branches_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/branches{/branch}","tags_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/tags","blobs_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/refs{/sha}","trees_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/statuses/{sha}","languages_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/languages","stargazers_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/stargazers","contributors_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/contributors","subscribers_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/subscribers","subscription_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/subscription","commits_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/commits{/sha}","git_commits_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/git/commits{/sha}","comments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/comments{/number}","issue_comment_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/comments{/number}","contents_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/contents/{+path}","compare_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/merges","archive_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/downloads","issues_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues{/number}","pulls_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls{/number}","milestones_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/milestones{/number}","notifications_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/labels{/name}","releases_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/releases{/id}","deployments_url":"https://api.github.com/repos/friends-of-freeswitch/switchy/deployments","created_at":"2016-04-14T20:25:07Z","updated_at":"2017-07-24T22:35:16Z","pushed_at":"2017-08-21T21:18:42Z","git_url":"git://github.com/friends-of-freeswitch/switchy.git","ssh_url":"git@github.com:friends-of-freeswitch/switchy.git","clone_url":"https://github.com/friends-of-freeswitch/switchy.git","svn_url":"https://github.com/friends-of-freeswitch/switchy","homepage":"https://switchy.readthedocs.org","size":3925,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4"},"html":{"href":"https://github.com/friends-of-freeswitch/switchy/pull/4"},"issue":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/4"},"comments":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/issues/4/comments"},"review_comments":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4/comments"},"review_comment":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/pulls/4/commits"},"statuses":{"href":"https://api.github.com/repos/friends-of-freeswitch/switchy/statuses/2cb28af1ec031c8694389dd16975f97681146162"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"moises-silva"}}