{ "info": { "author": "", "author_email": "", "bugtrack_url": null, "classifiers": [ "Framework :: Zope3", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy" ], "description": "=================\r\nPersistent Queues\r\n=================\r\n\r\nPersistent queues are simply queues that are optimized for persistency via the\r\nZODB. They assume that the ZODB is using MVCC to avoid read conflicts. They\r\nattempt to resolve write conflicts so that transactions that add and remove\r\nobjects simultaneously are merged, unless the transactions are trying to\r\nremove the same value from the queue.\r\n\r\nAn important characteristic of these queues is that they do not expect to\r\nhold more than one reference to any given equivalent item at a time. For\r\ninstance, some of the conflict resolution features will not perform\r\ndesirably if it is reasonable for your application to hold two copies of the\r\nstring \"hello\" within the same queue at once [#why]_.\r\n\r\nThe module provides two flavors: a simple persistent queue that keeps all\r\ncontained objects in one persistent object (`Queue`), and a\r\npersistent queue that divides up its contents into multiple composite\r\nelements (`CompositeQueue`). They should be equivalent in terms of\r\nAPI and so are mostly examined in the abstract in this document: we'll generate\r\ninstances with a representative `Queue` factory, that could be either class.\r\nThey only differ in an aspect of their write conflict resolution behavior,\r\nwhich is discussed below.\r\n\r\nQueues can be instantiated with no arguments.\r\n\r\n >>> q = Queue()\r\n >>> from zc.queue.interfaces import IQueue\r\n >>> from zope.interface.verify import verifyObject\r\n >>> verifyObject(IQueue, q)\r\n True\r\n\r\nThe basic API is simple: use `put` to add items to the back of the queue, and\r\n`pull` to pull things off the queue, defaulting to the front of the queue.\r\nNote that `Item` could be either persistent or non persistent object.\r\n\r\n >>> q.put(Item(1))\r\n >>> q.put(Item(2))\r\n >>> q.pull()\r\n 1\r\n >>> q.put(Item(3))\r\n >>> q.pull()\r\n 2\r\n >>> q.pull()\r\n 3\r\n\r\nThe `pull` method takes an optional zero-based index argument, and can accept\r\nnegative values.\r\n\r\n >>> q.put(Item(4))\r\n >>> q.put(Item(5))\r\n >>> q.put(Item(6))\r\n >>> q.pull(-1)\r\n 6\r\n >>> q.pull(1)\r\n 5\r\n >>> q.pull(0)\r\n 4\r\n\r\nRequesting an item from an empty queue raises an IndexError.\r\n\r\n >>> q.pull() # doctest: +ELLIPSIS\r\n Traceback (most recent call last):\r\n ...\r\n IndexError: ...\r\n\r\nRequesting an invalid index value does the same.\r\n\r\n >>> q.put(Item(7))\r\n >>> q.put(Item(8))\r\n >>> q.pull(2) # doctest: +ELLIPSIS\r\n Traceback (most recent call last):\r\n ...\r\n IndexError: ...\r\n\r\nBeyond these core queue operations, queues support len...\r\n\r\n >>> len(q)\r\n 2\r\n >>> q.pull()\r\n 7\r\n >>> len(q)\r\n 1\r\n >>> q.pull()\r\n 8\r\n >>> len(q)\r\n 0\r\n\r\n...iter (which does *not* empty the queue)...\r\n\r\n >>> next(iter(q))\r\n Traceback (most recent call last):\r\n ...\r\n StopIteration\r\n >>> q.put(Item(9))\r\n >>> q.put(Item(10))\r\n >>> q.put(Item(11))\r\n >>> next(iter(q))\r\n 9\r\n >>> [i for i in q]\r\n [9, 10, 11]\r\n >>> q.pull()\r\n 9\r\n >>> [i for i in q]\r\n [10, 11]\r\n\r\n...bool...\r\n\r\n >>> bool(q)\r\n True\r\n >>> q.pull()\r\n 10\r\n >>> q.pull()\r\n 11\r\n >>> bool(q)\r\n False\r\n\r\n...and list-like bracket access (which again does *not* empty the queue).\r\n\r\n >>> q.put(Item(12))\r\n >>> q[0]\r\n 12\r\n >>> q.pull()\r\n 12\r\n >>> q[0] # doctest: +ELLIPSIS\r\n Traceback (most recent call last):\r\n ...\r\n IndexError: ...\r\n >>> for i in range (13, 23):\r\n ... q.put(Item(i))\r\n ...\r\n >>> q[0]\r\n 13\r\n >>> q[1]\r\n 14\r\n >>> q[2]\r\n 15\r\n >>> q[-1]\r\n 22\r\n >>> q[-10]\r\n 13\r\n\r\nThat's it--there's no additional way to add anything beyond `put`, and no\r\nadditional way to remove anything beyond `pull`.\r\n\r\nThe only other wrinkle is the conflict resolution code. Conflict\r\nresolution in ZODB has some general caveats of which you should be aware\r\n[#caveats]_.\r\n\r\nThese general caveats aside, we'll now examine some examples of zc.queue\r\nconflict resolution at work. To show this, we will have to have two\r\ncopies of the same queue, from two different connections.\r\n\r\nNOTE: this testing approach has known weaknesses. See discussion in tests.py.\r\n\r\n >>> import transaction\r\n >>> from zc.queue.tests import ConflictResolvingMappingStorage\r\n >>> from ZODB import DB\r\n >>> db = DB(ConflictResolvingMappingStorage('test'))\r\n >>> transactionmanager_1 = transaction.TransactionManager()\r\n >>> transactionmanager_2 = transaction.TransactionManager()\r\n >>> connection_1 = db.open(transaction_manager=transactionmanager_1)\r\n >>> root_1 = connection_1.root()\r\n\r\n >>> q_1 = root_1[\"queue\"] = Queue()\r\n >>> transactionmanager_1.commit()\r\n\r\n >>> transactionmanager_2 = transaction.TransactionManager()\r\n >>> connection_2 = db.open(transaction_manager=transactionmanager_2)\r\n >>> root_2 = connection_2.root()\r\n >>> q_2 = root_2['queue']\r\n\r\nNow we have two copies of the same queue, with separate transaction managers\r\nand connections about the same way two threads would have them. The '_1'\r\nsuffix identifies the objects for user 1, in thread 1; and the '_2' suffix\r\nidentifies the objects for user 2, in a concurrent thread 2.\r\n\r\nFirst, let's have the two users add some items to the queue concurrently.\r\nFor concurrent commits of only putting a single new item (one each in two\r\ntransactions), in both types of queue the user who commits first gets the\r\nlower position in the queue--that is, the position that will leave the queue\r\nsooner using default `pull` calls.\r\n\r\nIn this example, even though q_1 is modified first, q_2's transaction is\r\ncommitted first, so q_2's addition is first after the merge.\r\n\r\n >>> q_1.put(Item(1001))\r\n >>> q_2.put(Item(1000))\r\n >>> transactionmanager_2.commit()\r\n >>> transactionmanager_1.commit()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n >>> list(q_1)\r\n [1000, 1001]\r\n >>> list(q_2)\r\n [1000, 1001]\r\n\r\nFor commits of more than one additions per connection of two, or of more than\r\ntwo concurrent adding transactions, the behavior is the same for the\r\nQueue: the first commit's additions will go before the second\r\ncommit's.\r\n\r\n >>> from zc import queue\r\n >>> if isinstance(q_1, queue.Queue):\r\n ... for i in range(5):\r\n ... q_1.put(Item(i))\r\n ... for i in range(1002, 1005):\r\n ... q_2.put(Item(i))\r\n ... transactionmanager_2.commit()\r\n ... transactionmanager_1.commit()\r\n ... connection_1.sync()\r\n ... connection_2.sync()\r\n ...\r\n\r\nAs we'll see below, that will again reliably put all the values from the first\r\ncommit earlier in the queue, to result in\r\n[1000, 1001, 1002, 1003, 1004, 0, 1, 2, 3, 4].\r\n\r\nFor the CompositeQueue, the behavior is different. The order\r\nwill be maintained with a set of additions in a transaction, but the values\r\nmay be merged between the two transactions' additions. We will compensate\r\nfor that here to get a reliable queue state.\r\n\r\n >>> if isinstance(q_1, queue.CompositeQueue):\r\n ... for i1, i2 in ((1002, 1003), (1004, 0), (1, 2), (3, 4)):\r\n ... q_1.put(Item(i1))\r\n ... q_2.put(Item(i2))\r\n ... transactionmanager_1.commit()\r\n ... transactionmanager_2.commit()\r\n ... connection_1.sync()\r\n ... connection_2.sync()\r\n ...\r\n\r\nWhichever kind of queue we have, we now have the following values.\r\n\r\n >>> list(q_1)\r\n [1000, 1001, 1002, 1003, 1004, 0, 1, 2, 3, 4]\r\n >>> list(q_2)\r\n [1000, 1001, 1002, 1003, 1004, 0, 1, 2, 3, 4]\r\n\r\nIf two users try to add the same item, then a conflict error is raised.\r\n\r\n >>> five = Item(5)\r\n >>> q_1.put(five)\r\n >>> q_2.put(five)\r\n >>> transactionmanager_1.commit()\r\n >>> from ZODB.POSException import ConflictError, InvalidObjectReference\r\n >>> try:\r\n ... transactionmanager_2.commit() # doctest: +ELLIPSIS\r\n ... except (ConflictError, InvalidObjectReference):\r\n ... print(\"Conflict Error\")\r\n Conflict Error\r\n >>> transactionmanager_2.abort()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n >>> list(q_1)\r\n [1000, 1001, 1002, 1003, 1004, 0, 1, 2, 3, 4, 5]\r\n >>> list(q_2)\r\n [1000, 1001, 1002, 1003, 1004, 0, 1, 2, 3, 4, 5]\r\n\r\nUsers can also concurrently remove items from a queue...\r\n\r\n >>> q_1.pull()\r\n 1000\r\n >>> q_1[0]\r\n 1001\r\n\r\n >>> q_2.pull(5)\r\n 0\r\n >>> q_2[5]\r\n 1\r\n\r\n >>> q_2[0] # 1000 value still there in this connection\r\n 1000\r\n\r\n >>> q_1[4] # 0 value still there in this connection.\r\n 0\r\n\r\n >>> transactionmanager_1.commit()\r\n >>> transactionmanager_2.commit()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n >>> list(q_1)\r\n [1001, 1002, 1003, 1004, 1, 2, 3, 4, 5]\r\n >>> list(q_2)\r\n [1001, 1002, 1003, 1004, 1, 2, 3, 4, 5]\r\n\r\n...as long as they don't remove the same item.\r\n\r\n >>> q_1.pull()\r\n 1001\r\n >>> q_2.pull()\r\n 1001\r\n >>> transactionmanager_1.commit()\r\n >>> transactionmanager_2.commit() # doctest: +ELLIPSIS\r\n Traceback (most recent call last):\r\n ...\r\n ConflictError: ...\r\n >>> transactionmanager_2.abort()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n >>> list(q_1)\r\n [1002, 1003, 1004, 1, 2, 3, 4, 5]\r\n >>> list(q_2)\r\n [1002, 1003, 1004, 1, 2, 3, 4, 5]\r\n\r\nThere's actually a special case: the composite queue's buckets will refuse to\r\nmerge if they started with a non-empty state, and one of the two new states\r\nis empty. This is to prevent the loss of an addition to the queue. See\r\ntests.py for an example.\r\n\r\nAlso importantly, users can concurrently remove and add items to a queue.\r\n\r\n >>> q_1.pull()\r\n 1002\r\n >>> q_1.pull()\r\n 1003\r\n >>> q_1.pull()\r\n 1004\r\n >>> q_2.put(Item(6))\r\n >>> q_2.put(Item(7))\r\n >>> transactionmanager_1.commit()\r\n >>> transactionmanager_2.commit()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n >>> list(q_1)\r\n [1, 2, 3, 4, 5, 6, 7]\r\n >>> list(q_2)\r\n [1, 2, 3, 4, 5, 6, 7]\r\n\r\nAs a final example, we'll show the conflict resolution code under extreme\r\nduress, with multiple simultaneous puts and pulls.\r\n\r\n >>> res_1 = []\r\n >>> for i in range(6, -1, -2):\r\n ... res_1.append(q_1.pull(i))\r\n ...\r\n >>> res_1\r\n [7, 5, 3, 1]\r\n >>> res_2 = []\r\n >>> for i in range(5, 0, -2):\r\n ... res_2.append(q_2.pull(i))\r\n ...\r\n >>> res_2\r\n [6, 4, 2]\r\n >>> for i in range(8, 12):\r\n ... q_1.put(Item(i))\r\n ...\r\n >>> for i in range(12, 16):\r\n ... q_2.put(Item(i))\r\n ...\r\n >>> list(q_1)\r\n [2, 4, 6, 8, 9, 10, 11]\r\n >>> list(q_2)\r\n [1, 3, 5, 7, 12, 13, 14, 15]\r\n >>> transactionmanager_1.commit()\r\n >>> transactionmanager_2.commit()\r\n >>> connection_1.sync()\r\n >>> connection_2.sync()\r\n\r\nAfter these commits, if the queue is a Queue, the new values are\r\nin the order of their commit. However, as discussed above, if the queue is\r\na CompositeQueue the behavior is different. While the order will be\r\nmaintained comparitively--so if object `A` is ahead of object `B` in the queue\r\non commit then `A` will still be ahead of `B` after a merge of the conflicting\r\ntransactions--values may be interspersed between the two transactions.\r\n\r\nFor instance, if our example queue were a Queue, the values would\r\nbe [8, 9, 10, 11, 12, 13, 14, 15]. However, if it were a\r\nCompositeQueue, the values might be the same, or might be any\r\ncombination in which [8, 9, 10, 11] and [12, 13, 14, 15], from the two\r\ntransactions, are still in order. One ordering might be\r\n[8, 9, 12, 13, 10, 11, 14, 15], for instance.\r\n\r\n >>> if isinstance(q_1, queue.Queue):\r\n ... res_1 = list(q_1)\r\n ... res_2 = list(q_2)\r\n ... elif isinstance(q_1, queue.CompositeQueue):\r\n ... firstsrc_1 = list(q_1)\r\n ... firstsrc_2 = list(q_2)\r\n ... secondsrc_1 = firstsrc_1[:]\r\n ... secondsrc_2 = firstsrc_2[:]\r\n ... for val in [12, 13, 14, 15]:\r\n ... firstsrc_1.remove(Item(val))\r\n ... firstsrc_2.remove(Item(val))\r\n ... for val in [8, 9, 10, 11]:\r\n ... secondsrc_1.remove(Item(val))\r\n ... secondsrc_2.remove(Item(val))\r\n ... res_1 = firstsrc_1 + secondsrc_1\r\n ... res_2 = firstsrc_2 + secondsrc_2\r\n ...\r\n >>> res_1\r\n [8, 9, 10, 11, 12, 13, 14, 15]\r\n >>> res_2\r\n [8, 9, 10, 11, 12, 13, 14, 15]\r\n\r\n >>> db.close() # cleanup\r\n\r\n\r\n========================\r\nPersistentReferenceProxy\r\n========================\r\n\r\nAs `ZODB.ConflictResolution.PersistentReference` doesn't get handled\r\nproperly in `set` due to lack of `__hash__` method, we define a class\r\nutilizing `__cmp__` method of contained items [#workaround]_.\r\n\r\n\r\nLet's make some stub persistent reference objects. Also make some\r\nobjects that have same oid to simulate different transaction states.\r\n\r\n >>> from zc.queue.tests import StubPersistentReference\r\n >>> pr1 = StubPersistentReference(1)\r\n >>> pr2 = StubPersistentReference(2)\r\n >>> pr3 = StubPersistentReference(3)\r\n >>> pr_c1 = StubPersistentReference(1)\r\n >>> pr_c2 = StubPersistentReference(2)\r\n >>> pr_c3 = StubPersistentReference(3)\r\n\r\n >>> pr1 == pr_c1\r\n True\r\n >>> pr2 == pr_c2\r\n True\r\n >>> pr3 == pr_c3\r\n True\r\n >>> id(pr1) == id(pr_c1)\r\n False\r\n >>> id(pr2) == id(pr_c2)\r\n False\r\n >>> id(pr3) == id(pr_c3)\r\n False\r\n\r\n >>> set1 = set((pr1, pr2))\r\n >>> set1\r\n set([SPR (1), SPR (2)])\r\n >>> len(set1)\r\n 2\r\n >>> set2 = set((pr_c1, pr_c3))\r\n >>> set2\r\n set([SPR (1), SPR (3)])\r\n >>> len(set2)\r\n 2\r\n >>> set_c1 = set((pr_c1, pr_c2))\r\n >>> set_c1\r\n set([SPR (1), SPR (2)])\r\n >>> len(set_c1)\r\n 2\r\n\r\n`set` doesn't handle persistent reference objects properly. All\r\nfollowing set operations produce wrong results.\r\n\r\nDeduplication (notice that for items longer than length two we're only\r\nchecking the length and contents, not the ordering of the\r\nrepresentation, because that varies among different versions of Python):\r\n\r\n >>> set((pr1, pr_c1))\r\n set([SPR (1), SPR (1)])\r\n >>> set((pr2, pr_c2))\r\n set([SPR (2), SPR (2)])\r\n >>> set4 = set((pr1, pr_c1, pr2))\r\n >>> len(set4)\r\n 3\r\n >>> pr1 in set4 and pr_c1 in set4 and pr2 in set4\r\n True\r\n >>> set4 = set((pr1, pr2, pr3, pr_c1, pr_c2, pr_c3))\r\n >>> len(set4)\r\n 6\r\n\r\nMinus operation:\r\n\r\n >>> set3 = set1 - set2\r\n >>> len(set3)\r\n 2\r\n >>> set3\r\n set([SPR (1), SPR (2)])\r\n\r\nContains:\r\n\r\n >>> pr3 in set2\r\n False\r\n\r\nIntersection:\r\n\r\n >>> set1 & set2\r\n set([])\r\n\r\nCompare:\r\n\r\n >>> set1 == set_c1\r\n False\r\n\r\nSo we made `PersistentReferenceProxy` wrapping `PersistentReference`\r\nto work with sets.\r\n\r\n >>> from zc.queue._queue import PersistentReferenceProxy\r\n >>> prp1 = PersistentReferenceProxy(pr1)\r\n >>> prp2 = PersistentReferenceProxy(pr2)\r\n >>> prp3 = PersistentReferenceProxy(pr3)\r\n >>> prp_c1 = PersistentReferenceProxy(pr_c1)\r\n >>> prp_c2 = PersistentReferenceProxy(pr_c2)\r\n >>> prp_c3 = PersistentReferenceProxy(pr_c3)\r\n >>> prp1 == prp_c1\r\n True\r\n >>> prp2 == prp_c2\r\n True\r\n >>> prp3 == prp_c3\r\n True\r\n >>> id(prp1) == id(prp_c1)\r\n False\r\n >>> id(prp2) == id(prp_c2)\r\n False\r\n >>> id(prp3) == id(prp_c3)\r\n False\r\n\r\n >>> set1 = set((prp1, prp2))\r\n >>> set1\r\n set([SPR (1), SPR (2)])\r\n >>> len(set1)\r\n 2\r\n >>> set2 = set((prp_c1, prp_c3))\r\n >>> set2\r\n set([SPR (1), SPR (3)])\r\n >>> len(set2)\r\n 2\r\n >>> set_c1 = set((prp_c1, prp_c2))\r\n >>> set_c1\r\n set([SPR (1), SPR (2)])\r\n >>> len(set_c1)\r\n 2\r\n\r\n`set` handles persistent reference properly now. All following set\r\noperations produce correct results.\r\n\r\nDeduplication:\r\n\r\n >>> set4 = set((prp1, prp2, prp3, prp_c1, prp_c2, prp_c3))\r\n >>> len(set4)\r\n 3\r\n >>> set((prp1, prp_c1))\r\n set([SPR (1)])\r\n >>> set((prp2, prp_c2))\r\n set([SPR (2)])\r\n >>> set((prp1, prp_c1, prp2))\r\n set([SPR (1), SPR (2)])\r\n\r\nMinus operation:\r\n\r\n >>> set3 = set1 - set2\r\n >>> len(set3)\r\n 1\r\n >>> set3\r\n set([SPR (2)])\r\n >>> set1 - set1\r\n set([])\r\n >>> set2 - set3\r\n set([SPR (1), SPR (3)])\r\n >>> set3 - set2\r\n set([SPR (2)])\r\n\r\nContains:\r\n\r\n >>> prp3 in set2\r\n True\r\n >>> prp1 in set2\r\n True\r\n >>> prp_c1 in set2\r\n True\r\n >>> prp2 not in set2\r\n True\r\n\r\nIntersection:\r\n\r\n >>> set1 & set2\r\n set([SPR (1)])\r\n >>> set1 & set_c1\r\n set([SPR (1), SPR (2)])\r\n >>> set2 & set3\r\n set([])\r\n\r\nCompare:\r\n\r\n >>> set1 == set_c1\r\n True\r\n >>> set1 == set2\r\n False\r\n >>> set1 == set4\r\n False\r\n\r\n\r\n-----\r\n\r\n.. [#why] The queue's `pull` method is actually the interesting part in why\r\n this constraint is used, and it becomes more so when you allow an\r\n arbitrary pull. By asserting that you do not support having equal\r\n items in the queue at once, you can simply say that when you remove\r\n equal objects in the current state and the contemporary, conflicting\r\n state, it's a conflict error. Ideally you don't enter another equal\r\n item in that queue again, or else in fact this is still an\r\n error-prone heuristic:\r\n\r\n - start queue == [X];\r\n - begin transactions A and B;\r\n - B removes X and commits;\r\n - transaction C adds X and Y and commits;\r\n - transaction A removes X and tries to commit, and conflict resolution\r\n code believes that it is ok to remove the new X from transaction C\r\n because it looks like it was just an addition of Y). Commit succeeds,\r\n and should not.\r\n\r\n If you don't assert that you can use equality to examine conflicts,\r\n then you have to come up with another heuristic. Given that the\r\n conflict resolution code only gets three states to resolve, I don't\r\n know of a reliable one.\r\n\r\n Therefore, zc.queue has a policy of assuming that it can use\r\n equality to distinguish items. It's limiting, but the code can have\r\n a better confidence of doing the right thing.\r\n\r\n Also, FWIW, this is policy I want: for my use cases, it would be\r\n possible to put in two items in a queue that handle the same issue.\r\n With the right equality code, this can be avoided with the policy\r\n the queue has now.\r\n\r\n.. [#caveats] Here are a few caveats about the state (as of this\r\n writing) of ZODB conflict resolution in general.\r\n\r\n The biggest is that, if you store persistent.Persistent subclass\r\n objects in a queue (or any other collection with conflict resolution\r\n code, such as a BTree), the collection will get a placeholder object\r\n (ZODB.ConflictResolution.PersistentReference), rather than the real\r\n contained object. This object has __cmp__ method, but doesn't have\r\n __hash__ method, The same oid will get different placeholder in the\r\n different states because of different identity in memory (e.g. `id(obj)`)\r\n for conflict resolution, which is wrong behavior in a queue.\r\n\r\n Another is that, in ZEO, conflict resolution is currently done on\r\n the server, so the ZEO server must have a copy of the classes\r\n (software) necessary to instantiate any non-persistent object in the\r\n collection.\r\n\r\n A corollary to the above is that objects such as\r\n zope.app.keyreference.persistent, which are not persistent\r\n themselves but rely on a persistent object for their __cmp__, will\r\n fail during conflict resolution. A reasonable solution in the case\r\n of zope.app.keyreference.persistent code is to have the object store\r\n the information it needs to do the comparison on itself, so the\r\n absence of the persistent object during conflict resolution is\r\n unimportant.\r\n\r\n.. [#workaround] The reason why we defined\r\n `PersistentReferenceProxy` is that there would be a significant risk\r\n of unintended consequenses for some ZODB users if we add __hash__\r\n method to PersistentReference.\r\n\r\n\r\n=======\r\nCHANGES\r\n=======\r\n\r\n2.0.1 (unreleased)\r\n==================\r\n\r\n- Nothing changed yet.\r\n\r\n\r\n2.0.0 (2017-05-11)\r\n==================\r\n\r\n- Dropped support for Python 2.6 and 3.3.\r\n\r\n- Added support for Python 3.4, 3.5, 3.6 and PyPy.\r\n\r\n- Fix using complex slices (e.g., negative strides) in\r\n ``CompositeQueue``. The cost is higher memory usage.\r\n\r\n\r\n2.0.0a1 (2013-03-01)\r\n====================\r\n\r\n- Added support for Python 3.3.\r\n\r\n- Replaced deprecated ``zope.interface.implements`` usage with equivalent\r\n ``zope.interface.implementer`` decorator.\r\n\r\n- Dropped support for Python 2.4 and 2.5.\r\n\r\n- Fixed an issue where slicing a composite queue would fail due to a\r\n programming error.\r\n [malthe]\r\n\r\n\r\n1.3 (2012-01-11)\r\n================\r\n\r\n- Fixed a conflict resolution bug that didn't handle\r\n `ZODB.ConflictResolution.PersistentReference` correctly.\r\n Note that due to syntax we require Python 2.5 or higher now.\r\n\r\n\r\n1.2.1 (2011-12-17)\r\n==================\r\n\r\n- Fixed ImportError in setup.py.\r\n [maurits]\r\n\r\n\r\n1.2 (2011-12-17)\r\n================\r\n\r\n- Fixed undefined ZODB.POSException.StorageTransactionError in tests.\r\n\r\n- Let tests pass with ZODB 3.8 and ZODB 3.9.\r\n\r\n- Added test extra to declare test dependency on ``zope.testing``.\r\n\r\n- Using Python's ``doctest`` module instead of deprecated\r\n ``zope.testing.doctest``.\r\n\r\n- Clean up the generation of reST docs.\r\n\r\n\r\n1.1\r\n===\r\n\r\n- Fixed a conflict resolution bug in CompositeQueue\r\n\r\n- Renamed PersistentQueue to Queue, CompositePersistentQueue to\r\n CompositeQueue. The old names are nominally deprecated, although no\r\n warnings are generated and there are no current plans to eliminate\r\n them. The PersistentQueue class has more conservative conflict\r\n resolution than it used to. (The Queue class has the same conflict\r\n resolution as the PersistentQueue used to have.)\r\n\r\n1.0.1\r\n=====\r\n\r\n- Minor buildout changes\r\n\r\n- Initial release to PyPI\r\n\r\n1.0\r\n===\r\n\r\n- Initial release to zope.org", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "zc.queue", "package_url": "https://pypi.org/project/zc.queue/", "platform": "", "project_url": "https://pypi.org/project/zc.queue/", "project_urls": null, "release_url": "https://pypi.org/project/zc.queue/2.0.0/", "requires_dist": [ "setuptools", "ZODB", "persistent", "zope.interface", "zope.testing; extra == 'test'", "zope.testrunner; extra == 'test'" ], "requires_python": "", "summary": "Queues that are optimized for persistency via the ZODB.", "version": "2.0.0" }, "last_serial": 2896833, "releases": { "1.0.1": [ { "comment_text": "", "digests": { "md5": "09fedd6226920bb4fdc008acddf32f38", "sha256": "27f1180f7fc5378f847881b00abca19498e3b3c97ba7afa13fc126e98501d353" }, "downloads": -1, "filename": "zc.queue-1.0.1-py2.4.egg", "has_sig": false, "md5_digest": "09fedd6226920bb4fdc008acddf32f38", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 18187, "upload_time": "2006-12-01T14:48:56", "url": "https://files.pythonhosted.org/packages/01/d2/7cbec12083a9d4b83840ca3fa6fb7748fcf34d0a05c01dc22acc9ebe8a39/zc.queue-1.0.1-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "3e9dd84de33ef60fc63c0d6834180397", "sha256": "e0da74b2b0bd537c1f2e14bc13bdca50cd6adf11f1fc7b0e8c10aac897f6ea15" }, "downloads": -1, "filename": "zc.queue-1.0.1.tar.gz", "has_sig": false, "md5_digest": "3e9dd84de33ef60fc63c0d6834180397", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11054, "upload_time": "2006-12-01T04:23:52", "url": "https://files.pythonhosted.org/packages/f8/2c/fba91f5e89223b4fc9e40675db35616d03bf09567735e491deb3cb05f3c5/zc.queue-1.0.1.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "603b9dd9927ab71e9aebbdad11e7d015", "sha256": "c4503b82699d4947e899ba0591e623df8ba97d94b80d1e7e6f03b8f2f26a443c" }, "downloads": -1, "filename": "zc.queue-1.1-py2.4.egg", "has_sig": false, "md5_digest": "603b9dd9927ab71e9aebbdad11e7d015", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 23014, "upload_time": "2007-06-26T02:56:38", "url": "https://files.pythonhosted.org/packages/62/ce/5be9a8064e3c05dfeb490baaab38db5a2c8adce364180e7fdc342e35692b/zc.queue-1.1-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "a84dd7dc7b390fd9256c05a18f4b79ea", "sha256": "ca34389e61cd772155cf02a325d299c9e23348d188ebc87237af5790789dec9f" }, "downloads": -1, "filename": "zc.queue-1.1.tar.gz", "has_sig": false, "md5_digest": "a84dd7dc7b390fd9256c05a18f4b79ea", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17310, "upload_time": "2007-06-26T02:56:41", "url": "https://files.pythonhosted.org/packages/41/a8/816bb4cde552ed0316e8353960d44f6d25f61ea12df06550414990032007/zc.queue-1.1.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "18f8c8028c935247286919b092cd8674", "sha256": "b44ba1ce642c3cf09c0d6733e2626ccdf95e192c7466bf8a3f1fd21807c0f12c" }, "downloads": -1, "filename": "zc.queue-1.2.tar.gz", "has_sig": false, "md5_digest": "18f8c8028c935247286919b092cd8674", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16960, "upload_time": "2011-12-18T01:02:35", "url": "https://files.pythonhosted.org/packages/c9/5d/4ff0cbc654313208a14ea72cea7e6062b5f5642f2babac7bab039ac66064/zc.queue-1.2.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "5a2e3484d521296235e016db9d045efe", "sha256": "45db19db648ac030b23ee80d5113af86e4b82d18e84d43da84d685f7b3deee14" }, "downloads": -1, "filename": "zc.queue-1.2.1.tar.gz", "has_sig": false, "md5_digest": "5a2e3484d521296235e016db9d045efe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17003, "upload_time": "2011-12-18T01:26:48", "url": "https://files.pythonhosted.org/packages/f5/0c/947800e3278ff5f743f4de2d2c3cc8a98ffaaa74cf6e289ef1cbc7350920/zc.queue-1.2.1.tar.gz" } ], "1.3": [ { "comment_text": "", "digests": { "md5": "93113bec10a9ddb51b774607fe2a683d", "sha256": "26a3e23d06570f83b9ae687918dfc199c93e56726b4b7e2fcd3c6d6976e69ca7" }, "downloads": -1, "filename": "zc.queue-1.3.tar.gz", "has_sig": false, "md5_digest": "93113bec10a9ddb51b774607fe2a683d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24962, "upload_time": "2012-01-12T00:33:26", "url": "https://files.pythonhosted.org/packages/46/ec/e1381af7baa8384269ae37ffdeade3c643c9116b3646a655ff6bd2faca56/zc.queue-1.3.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "7f843608e61fcead505f8d115c381520", "sha256": "6c492e4fc7440b01693f52c0b22e23e4d7dbe5f6eb40fec06e8e54231e4f4f3a" }, "downloads": -1, "filename": "zc.queue-2.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7f843608e61fcead505f8d115c381520", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 29142, "upload_time": "2017-05-11T13:34:23", "url": "https://files.pythonhosted.org/packages/bb/6b/74abe8cbfedd1df08277a3958ee09e553bbba2e66b8a66cae42cd7e93b79/zc.queue-2.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6055315f3fb74648a22368e04ef79585", "sha256": "fe12171e05b177506b97666507659dc4bb3babd74869dc6000291baefe1a8940" }, "downloads": -1, "filename": "zc.queue-2.0.0.tar.gz", "has_sig": false, "md5_digest": "6055315f3fb74648a22368e04ef79585", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29702, "upload_time": "2017-05-11T13:34:25", "url": "https://files.pythonhosted.org/packages/60/70/95c7c8ade25ff3cad0f872b00c0f93f191c1e17b6de73634c6fcb9c8ec26/zc.queue-2.0.0.tar.gz" } ], "2.0.0a1": [ { "comment_text": "", "digests": { "md5": "e17379d412be5fc44f9fe55c99d77537", "sha256": "07a5af7f9897849c9dcb1bb7e53be115b52b3a242e2ca0681fb97a00f4e2330c" }, "downloads": -1, "filename": "zc.queue-2.0.0a1.tar.gz", "has_sig": false, "md5_digest": "e17379d412be5fc44f9fe55c99d77537", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24336, "upload_time": "2013-03-01T21:44:36", "url": "https://files.pythonhosted.org/packages/3a/6f/03cc4224aee0907f0f6697ead40c1a8f62dffbfe362c6fb84176fe611610/zc.queue-2.0.0a1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7f843608e61fcead505f8d115c381520", "sha256": "6c492e4fc7440b01693f52c0b22e23e4d7dbe5f6eb40fec06e8e54231e4f4f3a" }, "downloads": -1, "filename": "zc.queue-2.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7f843608e61fcead505f8d115c381520", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 29142, "upload_time": "2017-05-11T13:34:23", "url": "https://files.pythonhosted.org/packages/bb/6b/74abe8cbfedd1df08277a3958ee09e553bbba2e66b8a66cae42cd7e93b79/zc.queue-2.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6055315f3fb74648a22368e04ef79585", "sha256": "fe12171e05b177506b97666507659dc4bb3babd74869dc6000291baefe1a8940" }, "downloads": -1, "filename": "zc.queue-2.0.0.tar.gz", "has_sig": false, "md5_digest": "6055315f3fb74648a22368e04ef79585", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29702, "upload_time": "2017-05-11T13:34:25", "url": "https://files.pythonhosted.org/packages/60/70/95c7c8ade25ff3cad0f872b00c0f93f191c1e17b6de73634c6fcb9c8ec26/zc.queue-2.0.0.tar.gz" } ] }