{"action":"created","comment":{"url":"https://api.github.com/repos/zopefoundation/persistent/pulls/comments/30593926","id":30593926,"diff_hunk":"@@ -0,0 +1,228 @@\n+# -*- coding: utf-8 -*-\n+##############################################################################\n+#\n+# Copyright (c) 2015 Zope Foundation and Contributors.\n+# All Rights Reserved.\n+#\n+# This software is subject to the provisions of the Zope Public License,\n+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.\n+# THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL EXPRESS OR IMPLIED\n+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS\n+# FOR A PARTICULAR PURPOSE.\n+#\n+##############################################################################\n+\n+#pylint: disable=W0212,E0211,W0622,E0213,W0221,E0239\n+\n+from zope.interface import Interface\n+from zope.interface import implementer\n+\n+class IRing(Interface):\n+    \"\"\"\n+    Conceptually, a doubly-linked list for efficiently keeping track of least-\n+    and most-recently used :class:`persistent.interfaces.IPersistent` objects.\n+\n+    This is meant to be used by the :class:`persistent.picklecache.PickleCache`\n+    and should not be considered a public API. This interface documentation exists\n+    to assist development of the picklecache and alternate implementations by\n+    explaining assumptions and performance requirements.\n+    \"\"\"\n+\n+    def __len__():\n+        \"\"\"\n+        Return the number of persistent objects stored in the ring. Should\n+        be constant time.\n+        \"\"\"\n+\n+    def __contains__(object):\n+        \"\"\"\n+        Answer whether the given persistent object is found in the ring.\n+        Must not rely on object equality or object hashing, but only\n+        identity or the `_p_oid`. Should be constant time.\n+        \"\"\"\n+\n+    def add(object):\n+        \"\"\"\n+        Add the persistent object to the ring as most-recently used. When\n+        an object is in the ring, the ring holds a strong reference to\n+        it so it can be deactivated later by the pickle cache. Should\n+        be constant time.\n+\n+        The object should not already be in the ring, but this is not necessarily\n+        enforced.\n+        \"\"\"\n+\n+    def delete(object):\n+        \"\"\"\n+        Remove the object from the ring if it is present. Returns a true\n+        value if it was present and a false value otherwise. An ideal\n+        implementation should be constant time, but linear time is\n+        allowed.\n+        \"\"\"\n+\n+    def move_to_head(object):\n+        \"\"\"\n+        Place the object as the most recently used object in the ring. The\n+        object should already be in the ring, but this is not\n+        necessarily enforced, and attempting to move an object that is\n+        not in the ring has undefined consequences. An ideal\n+        implementation should be constant time, but linear time is\n+        allowed.\n+        \"\"\"\n+\n+    def delete_all(indexes_and_values):\n+        \"\"\"\n+        Given a sequence of pairs (index, object), remove all of them from\n+        the ring. This should be equivalent to calling :meth:`delete` for each\n+        value, but allows for a more efficient bulk deletion process.\n+\n+        If the index and object pairs do not match with the actual state of the\n+        ring, this operation is undefined.\n+\n+        Should be at least linear time (not quadratic).\n+        \"\"\"\n+\n+    def __iter__():\n+        \"\"\"\n+        Iterate over each persistent object in the ring, in the order of least\n+        recently used to most recently used. Mutating the ring while an iteration\n+        is in progress has undefined consequences.\n+        \"\"\"\n+\n+from collections import deque\n+\n+@implementer(IRing)\n+class _DequeRing(object):\n+    \"\"\"\n+    A ring backed by the :class:`collections.deque` class. Operations\n+    are a mix of constant and linear time.\n+\n+    It is available on all platforms.\n+    \"\"\"\n+\n+    __slots__ = ('ring', 'ring_oids')\n+\n+    def __init__(self):\n+\n+        self.ring = deque()\n+        self.ring_oids = set()\n+\n+    def __len__(self):\n+        return len(self.ring)\n+\n+    def __contains__(self, pobj):\n+        return pobj._p_oid in self.ring_oids\n+\n+    def add(self, pobj):\n+        self.ring.append(pobj)\n+        self.ring_oids.add(pobj._p_oid)\n+\n+    def delete(self, pobj):\n+        # Note that we do not use self.ring.remove() because that\n+        # uses equality semantics and we don't want to call the persistent\n+        # object's __eq__ method (which might wake it up just after we\n+        # tried to ghost it)\n+        i = 0 # Using a manual numeric counter instead of enumerate() is much faster on PyPy\n+        for o in self.ring:\n+            if o is pobj:\n+                del self.ring[i]\n+                self.ring_oids.discard(pobj._p_oid)\n+                return 1\n+            i += 1\n+\n+    def move_to_head(self, pobj):\n+        self.delete(pobj)\n+        self.add(pobj)\n+\n+    def delete_all(self, indexes_and_values):\n+        for ix, value in reversed(indexes_and_values):\n+            del self.ring[ix]\n+            self.ring_oids.discard(value._p_oid)\n+\n+    def __iter__(self):\n+        return iter(self.ring)\n+\n+\n+try:\n+    from cffi import FFI\n+except ImportError: # pragma: no cover\n+    _CFFIRing = None\n+else:\n+\n+    import os\n+    this_dir = os.path.dirname(os.path.abspath(__file__))\n+\n+    ffi = FFI()\n+    with open(os.path.join(this_dir, 'ring.h')) as f:\n+        ffi.cdef(f.read())\n+\n+    ring = ffi.verify(\"\"\"\n+    #include \"ring.c\"\n+    \"\"\", include_dirs=[this_dir])\n+\n+    _OGA = object.__getattribute__\n+    _OSA = object.__setattr__\n+\n+    #pylint: disable=E1101\n+    @implementer(IRing)\n+    class _CFFIRing(object):\n+        \"\"\"\n+        A ring backed by a C implementation. All operations are constant time.\n+\n+        It is only available on platforms with ``cffi`` installed.\n+        \"\"\"\n+\n+        __slots__ = ('ring_home', 'ring_to_obj')\n+\n+        def __init__(self):\n+            node = self.ring_home = ffi.new(\"CPersistentRing*\")\n+            node.r_next = node\n+            node.r_prev = node\n+\n+            # In order for the CFFI objects to stay alive, we must keep\n+            # a strong reference to them, otherwise they get freed. We must\n+            # also keep strong references to the objects so they can be deactivated\n+            self.ring_to_obj = dict()\n+\n+        def __len__(self):\n+            return len(self.ring_to_obj)\n+\n+        def __contains__(self, pobj):\n+            return getattr(pobj, '_Persistent__ring', self) in self.ring_to_obj\n+\n+        def add(self, pobj):\n+            node = ffi.new(\"CPersistentRing*\")\n+            ring.ring_add(self.ring_home, node)","path":"persistent/ring.py","position":196,"original_position":196,"commit_id":"d4d19019af1da75a276afe632decbaae5eacc8e5","original_commit_id":"d4d19019af1da75a276afe632decbaae5eacc8e5","user":{"login":"tseaver","id":242750,"avatar_url":"https://avatars.githubusercontent.com/u/242750?v=3","gravatar_id":"","url":"https://api.github.com/users/tseaver","html_url":"https://github.com/tseaver","followers_url":"https://api.github.com/users/tseaver/followers","following_url":"https://api.github.com/users/tseaver/following{/other_user}","gists_url":"https://api.github.com/users/tseaver/gists{/gist_id}","starred_url":"https://api.github.com/users/tseaver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tseaver/subscriptions","organizations_url":"https://api.github.com/users/tseaver/orgs","repos_url":"https://api.github.com/users/tseaver/repos","events_url":"https://api.github.com/users/tseaver/events{/privacy}","received_events_url":"https://api.github.com/users/tseaver/received_events","type":"User","site_admin":false},"body":"Where is the `ring` name coming from?","created_at":"2015-05-19T12:34:41Z","updated_at":"2015-05-19T12:34:41Z","html_url":"https://github.com/zopefoundation/persistent/pull/20#discussion_r30593926","pull_request_url":"https://api.github.com/repos/zopefoundation/persistent/pulls/20","_links":{"self":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/comments/30593926"},"html":{"href":"https://github.com/zopefoundation/persistent/pull/20#discussion_r30593926"},"pull_request":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/20"}}},"pull_request":{"url":"https://api.github.com/repos/zopefoundation/persistent/pulls/20","id":32916799,"html_url":"https://github.com/zopefoundation/persistent/pull/20","diff_url":"https://github.com/zopefoundation/persistent/pull/20.diff","patch_url":"https://github.com/zopefoundation/persistent/pull/20.patch","issue_url":"https://api.github.com/repos/zopefoundation/persistent/issues/20","number":20,"state":"open","locked":false,"title":"Support for ZODB on PyPy","user":{"login":"jamadden","id":1256082,"avatar_url":"https://avatars.githubusercontent.com/u/1256082?v=3","gravatar_id":"","url":"https://api.github.com/users/jamadden","html_url":"https://github.com/jamadden","followers_url":"https://api.github.com/users/jamadden/followers","following_url":"https://api.github.com/users/jamadden/following{/other_user}","gists_url":"https://api.github.com/users/jamadden/gists{/gist_id}","starred_url":"https://api.github.com/users/jamadden/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jamadden/subscriptions","organizations_url":"https://api.github.com/users/jamadden/orgs","repos_url":"https://api.github.com/users/jamadden/repos","events_url":"https://api.github.com/users/jamadden/events{/privacy}","received_events_url":"https://api.github.com/users/jamadden/received_events","type":"User","site_admin":false},"body":"This PR brings the necessary changes to make the complete set of ZODB unit and functional tests pass (1410 in total). (See zopefoundation/ZODB#33)\r\n\r\nMostly this consisted of making the Python implementation more closely mirror the C implementation. A few notable things:\r\n\r\n- The PickleCache now supports the byte-size limit and the `total_estimated_size`.\r\n- The PickleCache now ghostifies objects during a GC, and handles objects that either deliberately or accidentally override `_p_invalidate` and `_p_activate` without calling super (ZODB's Blob is an example of a deliberate use of both).\r\n- The Persistent class now calls the same methods in the same order as the C class; code in the wild could easily depend on this accidentally. It's also safe to use many of the `_p` attributes in more circumstances than before (ZODB's Broken class relies on this).\r\n- A few restrictions had to be relaxed to match the C implementation (e.g., allowing arbitrary values for `_p_oid`, something that could easily crop up in test code in the wild like it did in ZODB) ; a few restrictions had to be added to match the C implementation (e.g., requiring objects in the PickleCache to have a jar).\r\n\r\nAll of the existing test cases should pass with very minor changes. I've already added a few test cases for the easier things and will see about adding a few more if I can extract them from ZODB in a reasonable form to try to get the coverage a bit higher (although it's not too bad as it is). Some of the scenarios are fairly complex though so it may be difficult to separate them. (Although, especially in the case of the C implementations it seems like many of the test cases still effectively live in ZODB.)\r\n\r\nI'm opening this PR a bit early because I know it's large and involves some changes in the way the cache and Persistent objects have to interact, so I'm open to any feedback and suggestions. Plus I probably can't open a PR for the ZODB changes until this gets merged and released :)\r\n\r\n```\r\nName                     Stmts   Miss Branch BrMiss  Cover   Missing\r\n--------------------------------------------------------------------\r\npersistent                  21     11      0      0    48%   24-46\r\npersistent._compat           1      0      0      0   100%\r\npersistent.dict              1      0      0      0   100%\r\npersistent.interfaces        9      3      0      0    67%   23-25\r\npersistent.list             60      0      0      0   100%\r\npersistent.mapping          44      0      4      0   100%\r\npersistent.persistence     297      0    150      0   100%\r\npersistent.picklecache     223     15    106     12    92%   65-67, 100, 118, 124, 165, 295-305, 399-401\r\npersistent.timestamp       125      0     14      0   100%\r\npersistent.timestamp       125      0     14      0   100%\r\npersistent.wref             68      0     18      0   100%\r\n--------------------------------------------------------------------\r\nTOTAL                      972     31    308     14    96%\r\n```","created_at":"2015-04-09T00:58:26Z","updated_at":"2015-05-19T12:34:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3df6b8344b4a218baadfa58fdde268e9937cfc2a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/zopefoundation/persistent/pulls/20/commits","review_comments_url":"https://api.github.com/repos/zopefoundation/persistent/pulls/20/comments","review_comment_url":"https://api.github.com/repos/zopefoundation/persistent/pulls/comments{/number}","comments_url":"https://api.github.com/repos/zopefoundation/persistent/issues/20/comments","statuses_url":"https://api.github.com/repos/zopefoundation/persistent/statuses/d4d19019af1da75a276afe632decbaae5eacc8e5","head":{"label":"NextThought:zodb-on-pypy-support","ref":"zodb-on-pypy-support","sha":"d4d19019af1da75a276afe632decbaae5eacc8e5","user":{"login":"NextThought","id":2072248,"avatar_url":"https://avatars.githubusercontent.com/u/2072248?v=3","gravatar_id":"","url":"https://api.github.com/users/NextThought","html_url":"https://github.com/NextThought","followers_url":"https://api.github.com/users/NextThought/followers","following_url":"https://api.github.com/users/NextThought/following{/other_user}","gists_url":"https://api.github.com/users/NextThought/gists{/gist_id}","starred_url":"https://api.github.com/users/NextThought/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NextThought/subscriptions","organizations_url":"https://api.github.com/users/NextThought/orgs","repos_url":"https://api.github.com/users/NextThought/repos","events_url":"https://api.github.com/users/NextThought/events{/privacy}","received_events_url":"https://api.github.com/users/NextThought/received_events","type":"Organization","site_admin":false},"repo":{"id":16959884,"name":"persistent","full_name":"NextThought/persistent","owner":{"login":"NextThought","id":2072248,"avatar_url":"https://avatars.githubusercontent.com/u/2072248?v=3","gravatar_id":"","url":"https://api.github.com/users/NextThought","html_url":"https://github.com/NextThought","followers_url":"https://api.github.com/users/NextThought/followers","following_url":"https://api.github.com/users/NextThought/following{/other_user}","gists_url":"https://api.github.com/users/NextThought/gists{/gist_id}","starred_url":"https://api.github.com/users/NextThought/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NextThought/subscriptions","organizations_url":"https://api.github.com/users/NextThought/orgs","repos_url":"https://api.github.com/users/NextThought/repos","events_url":"https://api.github.com/users/NextThought/events{/privacy}","received_events_url":"https://api.github.com/users/NextThought/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/NextThought/persistent","description":"automatic persistence for Python objects","fork":true,"url":"https://api.github.com/repos/NextThought/persistent","forks_url":"https://api.github.com/repos/NextThought/persistent/forks","keys_url":"https://api.github.com/repos/NextThought/persistent/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NextThought/persistent/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NextThought/persistent/teams","hooks_url":"https://api.github.com/repos/NextThought/persistent/hooks","issue_events_url":"https://api.github.com/repos/NextThought/persistent/issues/events{/number}","events_url":"https://api.github.com/repos/NextThought/persistent/events","assignees_url":"https://api.github.com/repos/NextThought/persistent/assignees{/user}","branches_url":"https://api.github.com/repos/NextThought/persistent/branches{/branch}","tags_url":"https://api.github.com/repos/NextThought/persistent/tags","blobs_url":"https://api.github.com/repos/NextThought/persistent/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NextThought/persistent/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NextThought/persistent/git/refs{/sha}","trees_url":"https://api.github.com/repos/NextThought/persistent/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NextThought/persistent/statuses/{sha}","languages_url":"https://api.github.com/repos/NextThought/persistent/languages","stargazers_url":"https://api.github.com/repos/NextThought/persistent/stargazers","contributors_url":"https://api.github.com/repos/NextThought/persistent/contributors","subscribers_url":"https://api.github.com/repos/NextThought/persistent/subscribers","subscription_url":"https://api.github.com/repos/NextThought/persistent/subscription","commits_url":"https://api.github.com/repos/NextThought/persistent/commits{/sha}","git_commits_url":"https://api.github.com/repos/NextThought/persistent/git/commits{/sha}","comments_url":"https://api.github.com/repos/NextThought/persistent/comments{/number}","issue_comment_url":"https://api.github.com/repos/NextThought/persistent/issues/comments{/number}","contents_url":"https://api.github.com/repos/NextThought/persistent/contents/{+path}","compare_url":"https://api.github.com/repos/NextThought/persistent/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NextThought/persistent/merges","archive_url":"https://api.github.com/repos/NextThought/persistent/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NextThought/persistent/downloads","issues_url":"https://api.github.com/repos/NextThought/persistent/issues{/number}","pulls_url":"https://api.github.com/repos/NextThought/persistent/pulls{/number}","milestones_url":"https://api.github.com/repos/NextThought/persistent/milestones{/number}","notifications_url":"https://api.github.com/repos/NextThought/persistent/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NextThought/persistent/labels{/name}","releases_url":"https://api.github.com/repos/NextThought/persistent/releases{/id}","created_at":"2014-02-18T18:53:24Z","updated_at":"2015-04-09T13:54:44Z","pushed_at":"2015-05-06T14:13:58Z","git_url":"git://github.com/NextThought/persistent.git","ssh_url":"git@github.com:NextThought/persistent.git","clone_url":"https://github.com/NextThought/persistent.git","svn_url":"https://github.com/NextThought/persistent","homepage":"https://pypi.python.org/pypi/persistent","size":656,"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":"zopefoundation:master","ref":"master","sha":"7f673b5397fbfe423bec612c1f42f57eac0e9634","user":{"login":"zopefoundation","id":1163184,"avatar_url":"https://avatars.githubusercontent.com/u/1163184?v=3","gravatar_id":"","url":"https://api.github.com/users/zopefoundation","html_url":"https://github.com/zopefoundation","followers_url":"https://api.github.com/users/zopefoundation/followers","following_url":"https://api.github.com/users/zopefoundation/following{/other_user}","gists_url":"https://api.github.com/users/zopefoundation/gists{/gist_id}","starred_url":"https://api.github.com/users/zopefoundation/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zopefoundation/subscriptions","organizations_url":"https://api.github.com/users/zopefoundation/orgs","repos_url":"https://api.github.com/users/zopefoundation/repos","events_url":"https://api.github.com/users/zopefoundation/events{/privacy}","received_events_url":"https://api.github.com/users/zopefoundation/received_events","type":"Organization","site_admin":false},"repo":{"id":7458003,"name":"persistent","full_name":"zopefoundation/persistent","owner":{"login":"zopefoundation","id":1163184,"avatar_url":"https://avatars.githubusercontent.com/u/1163184?v=3","gravatar_id":"","url":"https://api.github.com/users/zopefoundation","html_url":"https://github.com/zopefoundation","followers_url":"https://api.github.com/users/zopefoundation/followers","following_url":"https://api.github.com/users/zopefoundation/following{/other_user}","gists_url":"https://api.github.com/users/zopefoundation/gists{/gist_id}","starred_url":"https://api.github.com/users/zopefoundation/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zopefoundation/subscriptions","organizations_url":"https://api.github.com/users/zopefoundation/orgs","repos_url":"https://api.github.com/users/zopefoundation/repos","events_url":"https://api.github.com/users/zopefoundation/events{/privacy}","received_events_url":"https://api.github.com/users/zopefoundation/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zopefoundation/persistent","description":"automatic persistence for Python objects","fork":false,"url":"https://api.github.com/repos/zopefoundation/persistent","forks_url":"https://api.github.com/repos/zopefoundation/persistent/forks","keys_url":"https://api.github.com/repos/zopefoundation/persistent/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zopefoundation/persistent/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zopefoundation/persistent/teams","hooks_url":"https://api.github.com/repos/zopefoundation/persistent/hooks","issue_events_url":"https://api.github.com/repos/zopefoundation/persistent/issues/events{/number}","events_url":"https://api.github.com/repos/zopefoundation/persistent/events","assignees_url":"https://api.github.com/repos/zopefoundation/persistent/assignees{/user}","branches_url":"https://api.github.com/repos/zopefoundation/persistent/branches{/branch}","tags_url":"https://api.github.com/repos/zopefoundation/persistent/tags","blobs_url":"https://api.github.com/repos/zopefoundation/persistent/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zopefoundation/persistent/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zopefoundation/persistent/git/refs{/sha}","trees_url":"https://api.github.com/repos/zopefoundation/persistent/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zopefoundation/persistent/statuses/{sha}","languages_url":"https://api.github.com/repos/zopefoundation/persistent/languages","stargazers_url":"https://api.github.com/repos/zopefoundation/persistent/stargazers","contributors_url":"https://api.github.com/repos/zopefoundation/persistent/contributors","subscribers_url":"https://api.github.com/repos/zopefoundation/persistent/subscribers","subscription_url":"https://api.github.com/repos/zopefoundation/persistent/subscription","commits_url":"https://api.github.com/repos/zopefoundation/persistent/commits{/sha}","git_commits_url":"https://api.github.com/repos/zopefoundation/persistent/git/commits{/sha}","comments_url":"https://api.github.com/repos/zopefoundation/persistent/comments{/number}","issue_comment_url":"https://api.github.com/repos/zopefoundation/persistent/issues/comments{/number}","contents_url":"https://api.github.com/repos/zopefoundation/persistent/contents/{+path}","compare_url":"https://api.github.com/repos/zopefoundation/persistent/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zopefoundation/persistent/merges","archive_url":"https://api.github.com/repos/zopefoundation/persistent/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zopefoundation/persistent/downloads","issues_url":"https://api.github.com/repos/zopefoundation/persistent/issues{/number}","pulls_url":"https://api.github.com/repos/zopefoundation/persistent/pulls{/number}","milestones_url":"https://api.github.com/repos/zopefoundation/persistent/milestones{/number}","notifications_url":"https://api.github.com/repos/zopefoundation/persistent/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zopefoundation/persistent/labels{/name}","releases_url":"https://api.github.com/repos/zopefoundation/persistent/releases{/id}","created_at":"2013-01-05T17:32:44Z","updated_at":"2015-04-10T14:38:49Z","pushed_at":"2015-05-19T11:19:11Z","git_url":"git://github.com/zopefoundation/persistent.git","ssh_url":"git@github.com:zopefoundation/persistent.git","clone_url":"https://github.com/zopefoundation/persistent.git","svn_url":"https://github.com/zopefoundation/persistent","homepage":"https://pypi.python.org/pypi/persistent","size":795,"stargazers_count":4,"watchers_count":4,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5,"mirror_url":null,"open_issues_count":3,"forks":5,"open_issues":3,"watchers":4,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/20"},"html":{"href":"https://github.com/zopefoundation/persistent/pull/20"},"issue":{"href":"https://api.github.com/repos/zopefoundation/persistent/issues/20"},"comments":{"href":"https://api.github.com/repos/zopefoundation/persistent/issues/20/comments"},"review_comments":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/20/comments"},"review_comment":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/zopefoundation/persistent/pulls/20/commits"},"statuses":{"href":"https://api.github.com/repos/zopefoundation/persistent/statuses/d4d19019af1da75a276afe632decbaae5eacc8e5"}}}}