{"action":"created","comment":{"url":"https://api.github.com/repos/seomoz/url-py/pulls/comments/73209796","id":73209796,"diff_hunk":"@@ -0,0 +1,265 @@\n+# cython: linetrace=True\n+# distutils: define_macros=CYTHON_TRACE=1\n+\n+from cython.operator import dereference\n+\n+from publicsuffix import PublicSuffixList\n+psl = PublicSuffixList()\n+\n+def ParseMethod(cls, s, encoding='utf-8'):\n+    if isinstance(s, bytes):\n+        if encoding == 'utf-8':\n+            return cls(s)\n+        else:\n+            return cls(s.decode(encoding).encode('utf-8'))\n+    else:\n+        return cls(s.encode('utf-8'))\n+\n+\n+cdef class URL:\n+    '''Wrapper around url-cpp.'''\n+\n+    cdef Url *ptr\n+\n+    parse = classmethod(ParseMethod)\n+\n+    def __cinit__(self, const string& s):\n+        self.ptr = new Url(s)\n+\n+    def __dealloc__(self):\n+        del self.ptr\n+\n+    property scheme:\n+        def __get__(self):\n+            return self.ptr.scheme()\n+        def __set__(self, s):\n+            self.ptr.setScheme(s)\n+\n+    property host:\n+        def __get__(self):\n+            return self.ptr.host()\n+        def __set__(self, s):\n+            self.ptr.setHost(s)\n+\n+    property port:\n+        def __get__(self):\n+            return self.ptr.port()\n+        def __set__(self, i):\n+            self.ptr.setPort(i)\n+\n+    property path:\n+        def __get__(self):\n+            return self.ptr.path()\n+        def __set__(self, s):\n+            self.ptr.setPath(s)\n+\n+    property params:\n+        def __get__(self):\n+            return self.ptr.params()\n+        def __set__(self, s):\n+            self.ptr.setParams(s)\n+\n+    property query:\n+        def __get__(self):\n+            return self.ptr.query()\n+        def __set__(self, s):\n+            self.ptr.setQuery(s)\n+\n+    property fragment:\n+        def __get__(self):\n+            return self.ptr.fragment()\n+        def __set__(self, s):\n+            self.ptr.setFragment(s)\n+\n+    property userinfo:\n+        def __get__(self):\n+            return self.ptr.userinfo()\n+        def __set__(self, s):\n+            self.ptr.setUserinfo(s)\n+\n+    def copy(self):\n+        '''Return a new instance of an identical URL.'''\n+        new = URL('')\n+        new.ptr.setScheme(self.ptr.scheme())\n+        new.ptr.setHost(self.ptr.host())\n+        new.ptr.setPort(self.ptr.port())\n+        new.ptr.setPath(self.ptr.path())\n+        new.ptr.setParams(self.ptr.params())\n+        new.ptr.setQuery(self.ptr.query())\n+        new.ptr.setFragment(self.ptr.fragment())\n+        new.ptr.setUserinfo(self.ptr.userinfo())\n+        return new\n+\n+    def equiv(self, other, encoding='utf-8'):\n+        '''Return true if this url is equivalent to another'''\n+        if isinstance(other, basestring):\n+            _other = self.parse(other, encoding)\n+        else:\n+            _other = self.parse(other.utf8)\n+\n+        _self = self.parse(self.utf8, 'utf-8')\n+        _self.canonical().defrag().abspath().escape().punycode().remove_default_port()\n+        _other.canonical().defrag().abspath().escape().punycode().remove_default_port()\n+\n+        return (\n+            _self.scheme    == _other.scheme    and\n+            _self.host      == _other.host      and\n+            _self.path      == _other.path      and\n+            _self.params    == _other.params    and\n+            _self.query     == _other.query     and\n+            _self.port      == _other.port\n+        )\n+\n+    def __richcmp__(self, other, op):\n+        '''Return true if this url is /exactly/ equal to another'''\n+        if op == 2:  # ==\n+            if isinstance(other, basestring):\n+                return self.__eq__(self.parse(other, 'utf-8'))\n+            return (\n+                self.scheme    == other.scheme    and\n+                self.host      == other.host      and\n+                self.path      == other.path      and\n+                self.port      == other.port      and\n+                self.params    == other.params    and\n+                self.query     == other.query     and\n+                self.fragment  == other.fragment  and\n+                self.userinfo  == other.userinfo)\n+        elif op == 3:  # !=\n+            return not (self == other)\n+        else:\n+            raise NotImplementedError(\n+                '%s does not support this operation.' % type(self).__name__)\n+\n+    def __str__(self):\n+        return self.utf8\n+\n+    def __repr__(self):\n+        return '<url.URL object \"%s\" >' % self.utf8\n+\n+    def canonical(self):\n+        self.ptr.sort_query()\n+        return self\n+\n+    def defrag(self):\n+        '''Remove the fragment from this url'''\n+        self.ptr.defrag()\n+        return self\n+\n+    def deparam(self, params):\n+        '''Strip any of the provided parameters out of the url'''\n+        lowered = unordered_set[string](p.lower() for p in params)","path":"url/url.pyx","position":121,"original_position":150,"commit_id":"54020a5d8fad9f2bbade2e80b41ec966d0c1b516","original_commit_id":"db4d81785c32f4f69bb8f44dd3e1f543b3c8a565","user":{"login":"b4hand","id":708767,"avatar_url":"https://avatars.githubusercontent.com/u/708767?v=3","gravatar_id":"","url":"https://api.github.com/users/b4hand","html_url":"https://github.com/b4hand","followers_url":"https://api.github.com/users/b4hand/followers","following_url":"https://api.github.com/users/b4hand/following{/other_user}","gists_url":"https://api.github.com/users/b4hand/gists{/gist_id}","starred_url":"https://api.github.com/users/b4hand/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/b4hand/subscriptions","organizations_url":"https://api.github.com/users/b4hand/orgs","repos_url":"https://api.github.com/users/b4hand/repos","events_url":"https://api.github.com/users/b4hand/events{/privacy}","received_events_url":"https://api.github.com/users/b4hand/received_events","type":"User","site_admin":false},"body":"I thought we discussed making `deparam` case sensitive?","created_at":"2016-08-02T18:25:02Z","updated_at":"2016-08-02T18:25:02Z","html_url":"https://github.com/seomoz/url-py/pull/41#discussion_r73209796","pull_request_url":"https://api.github.com/repos/seomoz/url-py/pulls/41","_links":{"self":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/comments/73209796"},"html":{"href":"https://github.com/seomoz/url-py/pull/41#discussion_r73209796"},"pull_request":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/41"}}},"pull_request":{"url":"https://api.github.com/repos/seomoz/url-py/pulls/41","id":79271212,"html_url":"https://github.com/seomoz/url-py/pull/41","diff_url":"https://github.com/seomoz/url-py/pull/41.diff","patch_url":"https://github.com/seomoz/url-py/pull/41.patch","issue_url":"https://api.github.com/repos/seomoz/url-py/issues/41","number":41,"state":"open","locked":false,"title":"Wrap `url-cpp`","user":{"login":"dlecocq","id":952157,"avatar_url":"https://avatars.githubusercontent.com/u/952157?v=3","gravatar_id":"","url":"https://api.github.com/users/dlecocq","html_url":"https://github.com/dlecocq","followers_url":"https://api.github.com/users/dlecocq/followers","following_url":"https://api.github.com/users/dlecocq/following{/other_user}","gists_url":"https://api.github.com/users/dlecocq/gists{/gist_id}","starred_url":"https://api.github.com/users/dlecocq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlecocq/subscriptions","organizations_url":"https://api.github.com/users/dlecocq/orgs","repos_url":"https://api.github.com/users/dlecocq/repos","events_url":"https://api.github.com/users/dlecocq/events{/privacy}","received_events_url":"https://api.github.com/users/dlecocq/received_events","type":"User","site_admin":false},"body":"Provides a Cython extension that wraps `url-cpp` for great performance improvement. The speedup of course depends on the task and the input, but in the range or 20-50x for parsing and normalization tasks.\r\n\r\nIn a test of 7.75M URL pairs taken from the wild (from 1M pages sampled from customer campaigns), `url-cpp` and the old `url-py` bindings disagreed on the interpretation of 122 URLs. The majority of these cases are described in new tests and are either 1) acceptably rare, 2) bugs in the old `url-py`, or 3) acceptable new rulings on edge cases previously not tested. They are described in additional tests as well:\r\n\r\n- The `port` must be an integer, and in the range [0-65535]. An exception is now thrown otherwise.\r\n- The old `url-py` was apparently interpreting `https://t…/` (with ellipsis as a single character) as `https://t.../` (three periods) instead of our new interpretation which considers that a single non-basic codepoint -- `https://xn--t-9hn/`\r\n- Relative URLs (without a host) now may have `punycode` and `unpunycode` called on them -- I'm not sure why we previously chose to throw an exception in that case\r\n- Always include a trailing slash for empty paths (`URL.parse('http://foo.com').utf8 == 'http://foo.com/'`)\r\n- Relative URL evaluation always 1) provides a trailing slash, 2) squashes excess `..`'s. This is in-line with the interpretation `abspath()` takes:\r\n\r\n```python\r\nURL.parse('http://base/').relative('../../../').utf8\r\n# Old:\r\n'http://base/../../../'\r\n# New:\r\n'http://base/'\r\n\r\nURL.parse('http://base/').relative('../../../').abspath().utf8\r\n# Old:\r\n'http://base'\r\n# New:\r\n'http://base/'\r\n```\r\n\r\n@neilmb @b4hand @lindseyreno @vadim-moz ","created_at":"2016-07-28T21:24:31Z","updated_at":"2016-08-02T18:25:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e90bf47ca1d83a4cd67cec23017846fe465aa00a","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/seomoz/url-py/pulls/41/commits","review_comments_url":"https://api.github.com/repos/seomoz/url-py/pulls/41/comments","review_comment_url":"https://api.github.com/repos/seomoz/url-py/pulls/comments{/number}","comments_url":"https://api.github.com/repos/seomoz/url-py/issues/41/comments","statuses_url":"https://api.github.com/repos/seomoz/url-py/statuses/54020a5d8fad9f2bbade2e80b41ec966d0c1b516","head":{"label":"seomoz:dan/url-cpp","ref":"dan/url-cpp","sha":"54020a5d8fad9f2bbade2e80b41ec966d0c1b516","user":{"login":"seomoz","id":104895,"avatar_url":"https://avatars.githubusercontent.com/u/104895?v=3","gravatar_id":"","url":"https://api.github.com/users/seomoz","html_url":"https://github.com/seomoz","followers_url":"https://api.github.com/users/seomoz/followers","following_url":"https://api.github.com/users/seomoz/following{/other_user}","gists_url":"https://api.github.com/users/seomoz/gists{/gist_id}","starred_url":"https://api.github.com/users/seomoz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seomoz/subscriptions","organizations_url":"https://api.github.com/users/seomoz/orgs","repos_url":"https://api.github.com/users/seomoz/repos","events_url":"https://api.github.com/users/seomoz/events{/privacy}","received_events_url":"https://api.github.com/users/seomoz/received_events","type":"Organization","site_admin":false},"repo":{"id":5943868,"name":"url-py","full_name":"seomoz/url-py","owner":{"login":"seomoz","id":104895,"avatar_url":"https://avatars.githubusercontent.com/u/104895?v=3","gravatar_id":"","url":"https://api.github.com/users/seomoz","html_url":"https://github.com/seomoz","followers_url":"https://api.github.com/users/seomoz/followers","following_url":"https://api.github.com/users/seomoz/following{/other_user}","gists_url":"https://api.github.com/users/seomoz/gists{/gist_id}","starred_url":"https://api.github.com/users/seomoz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seomoz/subscriptions","organizations_url":"https://api.github.com/users/seomoz/orgs","repos_url":"https://api.github.com/users/seomoz/repos","events_url":"https://api.github.com/users/seomoz/events{/privacy}","received_events_url":"https://api.github.com/users/seomoz/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/seomoz/url-py","description":"URL Transformation, Sanitization","fork":false,"url":"https://api.github.com/repos/seomoz/url-py","forks_url":"https://api.github.com/repos/seomoz/url-py/forks","keys_url":"https://api.github.com/repos/seomoz/url-py/keys{/key_id}","collaborators_url":"https://api.github.com/repos/seomoz/url-py/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/seomoz/url-py/teams","hooks_url":"https://api.github.com/repos/seomoz/url-py/hooks","issue_events_url":"https://api.github.com/repos/seomoz/url-py/issues/events{/number}","events_url":"https://api.github.com/repos/seomoz/url-py/events","assignees_url":"https://api.github.com/repos/seomoz/url-py/assignees{/user}","branches_url":"https://api.github.com/repos/seomoz/url-py/branches{/branch}","tags_url":"https://api.github.com/repos/seomoz/url-py/tags","blobs_url":"https://api.github.com/repos/seomoz/url-py/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/seomoz/url-py/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/seomoz/url-py/git/refs{/sha}","trees_url":"https://api.github.com/repos/seomoz/url-py/git/trees{/sha}","statuses_url":"https://api.github.com/repos/seomoz/url-py/statuses/{sha}","languages_url":"https://api.github.com/repos/seomoz/url-py/languages","stargazers_url":"https://api.github.com/repos/seomoz/url-py/stargazers","contributors_url":"https://api.github.com/repos/seomoz/url-py/contributors","subscribers_url":"https://api.github.com/repos/seomoz/url-py/subscribers","subscription_url":"https://api.github.com/repos/seomoz/url-py/subscription","commits_url":"https://api.github.com/repos/seomoz/url-py/commits{/sha}","git_commits_url":"https://api.github.com/repos/seomoz/url-py/git/commits{/sha}","comments_url":"https://api.github.com/repos/seomoz/url-py/comments{/number}","issue_comment_url":"https://api.github.com/repos/seomoz/url-py/issues/comments{/number}","contents_url":"https://api.github.com/repos/seomoz/url-py/contents/{+path}","compare_url":"https://api.github.com/repos/seomoz/url-py/compare/{base}...{head}","merges_url":"https://api.github.com/repos/seomoz/url-py/merges","archive_url":"https://api.github.com/repos/seomoz/url-py/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/seomoz/url-py/downloads","issues_url":"https://api.github.com/repos/seomoz/url-py/issues{/number}","pulls_url":"https://api.github.com/repos/seomoz/url-py/pulls{/number}","milestones_url":"https://api.github.com/repos/seomoz/url-py/milestones{/number}","notifications_url":"https://api.github.com/repos/seomoz/url-py/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/seomoz/url-py/labels{/name}","releases_url":"https://api.github.com/repos/seomoz/url-py/releases{/id}","deployments_url":"https://api.github.com/repos/seomoz/url-py/deployments","created_at":"2012-09-25T02:13:46Z","updated_at":"2016-08-01T09:45:16Z","pushed_at":"2016-08-01T16:10:07Z","git_url":"git://github.com/seomoz/url-py.git","ssh_url":"git@github.com:seomoz/url-py.git","clone_url":"https://github.com/seomoz/url-py.git","svn_url":"https://github.com/seomoz/url-py","homepage":null,"size":161,"stargazers_count":57,"watchers_count":57,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"open_issues_count":7,"forks":11,"open_issues":7,"watchers":57,"default_branch":"master"}},"base":{"label":"seomoz:master","ref":"master","sha":"ce1d3eb9d0d81142088f39a45a7ee9501b9e6580","user":{"login":"seomoz","id":104895,"avatar_url":"https://avatars.githubusercontent.com/u/104895?v=3","gravatar_id":"","url":"https://api.github.com/users/seomoz","html_url":"https://github.com/seomoz","followers_url":"https://api.github.com/users/seomoz/followers","following_url":"https://api.github.com/users/seomoz/following{/other_user}","gists_url":"https://api.github.com/users/seomoz/gists{/gist_id}","starred_url":"https://api.github.com/users/seomoz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seomoz/subscriptions","organizations_url":"https://api.github.com/users/seomoz/orgs","repos_url":"https://api.github.com/users/seomoz/repos","events_url":"https://api.github.com/users/seomoz/events{/privacy}","received_events_url":"https://api.github.com/users/seomoz/received_events","type":"Organization","site_admin":false},"repo":{"id":5943868,"name":"url-py","full_name":"seomoz/url-py","owner":{"login":"seomoz","id":104895,"avatar_url":"https://avatars.githubusercontent.com/u/104895?v=3","gravatar_id":"","url":"https://api.github.com/users/seomoz","html_url":"https://github.com/seomoz","followers_url":"https://api.github.com/users/seomoz/followers","following_url":"https://api.github.com/users/seomoz/following{/other_user}","gists_url":"https://api.github.com/users/seomoz/gists{/gist_id}","starred_url":"https://api.github.com/users/seomoz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seomoz/subscriptions","organizations_url":"https://api.github.com/users/seomoz/orgs","repos_url":"https://api.github.com/users/seomoz/repos","events_url":"https://api.github.com/users/seomoz/events{/privacy}","received_events_url":"https://api.github.com/users/seomoz/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/seomoz/url-py","description":"URL Transformation, Sanitization","fork":false,"url":"https://api.github.com/repos/seomoz/url-py","forks_url":"https://api.github.com/repos/seomoz/url-py/forks","keys_url":"https://api.github.com/repos/seomoz/url-py/keys{/key_id}","collaborators_url":"https://api.github.com/repos/seomoz/url-py/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/seomoz/url-py/teams","hooks_url":"https://api.github.com/repos/seomoz/url-py/hooks","issue_events_url":"https://api.github.com/repos/seomoz/url-py/issues/events{/number}","events_url":"https://api.github.com/repos/seomoz/url-py/events","assignees_url":"https://api.github.com/repos/seomoz/url-py/assignees{/user}","branches_url":"https://api.github.com/repos/seomoz/url-py/branches{/branch}","tags_url":"https://api.github.com/repos/seomoz/url-py/tags","blobs_url":"https://api.github.com/repos/seomoz/url-py/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/seomoz/url-py/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/seomoz/url-py/git/refs{/sha}","trees_url":"https://api.github.com/repos/seomoz/url-py/git/trees{/sha}","statuses_url":"https://api.github.com/repos/seomoz/url-py/statuses/{sha}","languages_url":"https://api.github.com/repos/seomoz/url-py/languages","stargazers_url":"https://api.github.com/repos/seomoz/url-py/stargazers","contributors_url":"https://api.github.com/repos/seomoz/url-py/contributors","subscribers_url":"https://api.github.com/repos/seomoz/url-py/subscribers","subscription_url":"https://api.github.com/repos/seomoz/url-py/subscription","commits_url":"https://api.github.com/repos/seomoz/url-py/commits{/sha}","git_commits_url":"https://api.github.com/repos/seomoz/url-py/git/commits{/sha}","comments_url":"https://api.github.com/repos/seomoz/url-py/comments{/number}","issue_comment_url":"https://api.github.com/repos/seomoz/url-py/issues/comments{/number}","contents_url":"https://api.github.com/repos/seomoz/url-py/contents/{+path}","compare_url":"https://api.github.com/repos/seomoz/url-py/compare/{base}...{head}","merges_url":"https://api.github.com/repos/seomoz/url-py/merges","archive_url":"https://api.github.com/repos/seomoz/url-py/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/seomoz/url-py/downloads","issues_url":"https://api.github.com/repos/seomoz/url-py/issues{/number}","pulls_url":"https://api.github.com/repos/seomoz/url-py/pulls{/number}","milestones_url":"https://api.github.com/repos/seomoz/url-py/milestones{/number}","notifications_url":"https://api.github.com/repos/seomoz/url-py/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/seomoz/url-py/labels{/name}","releases_url":"https://api.github.com/repos/seomoz/url-py/releases{/id}","deployments_url":"https://api.github.com/repos/seomoz/url-py/deployments","created_at":"2012-09-25T02:13:46Z","updated_at":"2016-08-01T09:45:16Z","pushed_at":"2016-08-01T16:10:07Z","git_url":"git://github.com/seomoz/url-py.git","ssh_url":"git@github.com:seomoz/url-py.git","clone_url":"https://github.com/seomoz/url-py.git","svn_url":"https://github.com/seomoz/url-py","homepage":null,"size":161,"stargazers_count":57,"watchers_count":57,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"open_issues_count":7,"forks":11,"open_issues":7,"watchers":57,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/41"},"html":{"href":"https://github.com/seomoz/url-py/pull/41"},"issue":{"href":"https://api.github.com/repos/seomoz/url-py/issues/41"},"comments":{"href":"https://api.github.com/repos/seomoz/url-py/issues/41/comments"},"review_comments":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/41/comments"},"review_comment":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/seomoz/url-py/pulls/41/commits"},"statuses":{"href":"https://api.github.com/repos/seomoz/url-py/statuses/54020a5d8fad9f2bbade2e80b41ec966d0c1b516"}}}}
{"actor":{"display_login":"b4hand"}}