{ "info": { "author": "Mike Rhodes", "author_email": "mike.rhodes@dx13.co.uk", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent" ], "description": "# Action Queues\n\n`actionqueues` is a lightweight way to queue up commands for execution and\nrollback on failures.\n\nThe idea is that it provides a framework for safely\nexecuting sequences of action with side-effects, like database writes, that\nmight need later rolling back if later actions fail. In addition, it provides\na standardised way for actions to be retried.\n\nFor example, a user sign up process may write to several different systems.\nIf one system is down, then the other systems modified so far need cleaning\nup before the failure is propagated back to the user. Using `actionqueues`\nwith an action for each external system to be modified enables this pattern,\nalong with simple retry semantics for likely-transient failures such as network\nblips.\n\n## Installing\n\n```\npip install actionqueues\n```\n\n## Using Action Queues\n\nIt's barebones, the main point is to provide a framework to work within for\nactions that have side effects. It's basically the Command pattern, with a\ntiny execution framework.\n\nAn `Action` is the lowest-level building block. It's any object with `execute`\nand `rollback` methods. The `Action` is what handles executing each step of the\noverall workflow, and rolling back any changes made to external state. It's\na single object so it can save state for rollback -- for example, primary\nkeys for any created database rows so they can be deleted during rollback.\n\nThe main task of a user of `actionqueues` is to create the `Action` classes\nwhich implement the tasks their workflows require.\n\nOnce `Action` classes are written, they can be executed. An `ActionQueue` holds `Actions` for execution and rollback. Add `Action` objects to an `ActionQueue`\nfor execution. Call `ActionQueue#execute` to start running each action's\n`execute` method in the order the `Action` objects were added to the\n`ActionQueue`. Behaviour after this point is controlled by the `execute` and\n`rollback` methods on the `Action` objects being executed by the queue.\n\n### Normal operation\n\nThe default case is that no exception is raised during an `execute` and the\nnext action in the queue is has `execute` called. This is shown below for a\nsequence of `Action` objects within an `ActionQueue`.\n\n![Happy path](https://raw.githubusercontent.com/mikerhodes/actionqueues/master/images/happy-path.png)\n\n### Exceptions during `execute` cause rollback\n\nIf an `Action#execute` raises an exception, the ActionQueue notes where it's\nup to in the Actions queued up and then propagates the exception\nback up to the caller.\n\nIt is then the caller's responsibility to catch the exception and then to call\n`ActionQueue#rollback`. This is so the caller can know that the queue of\nactions failed and is able to log the exception before calling `rollback`.\n\nCalling `ActionQueue#rollback` will execute the `rollback` method on all\nactions where the `execute` method was called, including the one raising the\nexception, in the reverse order to that which the `execute` method was called.\n\nRollback will not be called on actions where `execute` has not been called.\n\nAgain, the default case at this point is that `rollback` methods succeed and\ndon't throw exceptions, leading to each being executed in succession.\n\n![Rollback](https://raw.githubusercontent.com/mikerhodes/actionqueues/master/images/rollback.png)\n\n### Exceptions during `rollback`\n\nIn contrast to a raised exception from `execute`, if an exception is raised\nduring the `rollback` method, the `ActionQueue` will\nsilently swallow the exception and continue executing the `rollback` methods\nof earlier `Action` objects in the queue.\n\nThis is because, in the rollback scenario, it's most likely that all rollback\nactions should happen so the library assumes this. Therefore `rollback` methods\nshould do their own logging of exceptions before re-raising them.\n\n![Rollback exceptions](https://raw.githubusercontent.com/mikerhodes/actionqueues/master/images/rollback-exception.png)\n\n### Retrying failed operations\n\nThere is an exception to the above rules. If the `execute` or `rollback` method\nraises a `actionqueue.ActionRetryException` then the `execute` or `rollback`\nmethod will be called again. The `ActionRetryException` init method takes an\noptional `ms_backoff` argument to specify a time to sleep before trying the\nmethod again, in milliseconds.\n\nThe `ActionQueue` will retry as long as the action keeps raising\n`ActionRetryException`, so the action must maintain a retry count\nto avoid endless retries. See [below](#retry-exception-helpers) for some\nhelper classes which cover common cases.\n\n## Example\n\n```python\nimport random\nfrom actionqueues import actionqueue, action\n\nSUCCEED = 0\nRETRY = 1\nFAIL = 2\n\nclass MyAction(action.Action):\n\n def __init__(self, id):\n self._id = id\n self._value = 0\n\n def execute(self):\n \"\"\"Called if actions before it in the queue complete successfully.\n\n Raise any exception to indicate failure.\n \"\"\"\n action = random.choice([SUCCEED, RETRY, FAIL])\n if action == RETRY:\n print self._id, \"Throwing retry exception\"\n raise actionqueue.ActionRetryException(ms_backoff=0)\n elif action == FAIL:\n print self._id, \"Throwing failure exception\"\n raise Exception()\n else:\n print self._id, \"Executing success action\"\n self._value = 1\n\n\n def rollback(self):\n \"\"\"Called in reverse order for all actions queued whose execute\n method was called when the ActionQueue's rollback method is called.\n \"\"\"\n print self._id, \"Rolling back action\"\n if self._value == 1:\n self._value = 0\n\nq = actionqueue.ActionQueue()\nq.add(MyAction(\"a\"))\nq.add(MyAction(\"b\"))\n\ntry:\n q.execute()\nexcept:\n q.rollback()\n```\n\n## Retry exception helpers\n\nIt can be tedious to keep track of the backoff and retry count for an action.\nTherefore `actionqueues` provides helpers for this called exception factories.\nThese are created when the `Action` is initialised, and when an `execute`\nmethod hits a retriable exception, it calls the factory's `raise_exception()`\nmethod. In general, this will throw `ActionRetryException` exceptions for a\ngiven number of retries, then throw a generic exception, or one provided by\nthe `Action` object.\n\nUsing separate ExceptionFactory objects for `execute` and `rollback` is usually\nrequired.\n\nThe available exception factories are:\n\n- `DoublingBackoffExceptionFactory` which will throw a configurable number\n `ActionRetryException` exceptions, each doubling its backoff time.\n\nIn this example, the `ZeroDivisionError` will cause 5 retries, at 100, 200,\n400, 800 and 1600ms delays, by using a `DoublingBackoffExceptionFactory`:\n\n```python\nfrom actionqueues import actionqueue, action\nfrom actionqueues.exceptionfactory import DoublingBackoffExceptionFactory\n\nclass MyFailingAction(action.Action):\n\n def __init__(self):\n self._run = 1\n self._execute_ex_factory = DoublingBackoffExceptionFactory(\n retries=5,\n ms_backoff_initial=100\n )\n self._rollback_ex_factory = DoublingBackoffExceptionFactory(\n retries=10,\n ms_backoff_initial=100\n )\n\n def execute(self):\n \"\"\"Execute an always failing action, but have it retried 5 times.\"\"\"\n print \"Executing action\", self._run\n self._run += 1\n try:\n 1 / 0\n except ZeroDivisionError, ex:\n self._execute_ex_factory.raise_exception(original_exception=ex)\n\n def rollback(self):\n print \"Rollback action\", self._run\n self._run += 1\n try:\n 1 / 0\n except ZeroDivisionError, ex:\n self._rollback_ex_factory.raise_exception(original_exception=ex)\n\nq = actionqueue.ActionQueue()\nq.add(MyFailingAction())\n\ntry:\n q.execute()\nexcept:\n print \"boom\"\n```\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/mikerhodes/actionqueues", "keywords": "", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "actionqueues", "package_url": "https://pypi.org/project/actionqueues/", "platform": "", "project_url": "https://pypi.org/project/actionqueues/", "project_urls": { "Homepage": "http://github.com/mikerhodes/actionqueues" }, "release_url": "https://pypi.org/project/actionqueues/0.8.2/", "requires_dist": null, "requires_python": "", "summary": "Framework for executing actions and rollbacks", "version": "0.8.2" }, "last_serial": 4143578, "releases": { "0.7": [ { "comment_text": "", "digests": { "md5": "f332e9939009046696e14df4ef603518", "sha256": "9b5dd8bbe9caa2ad04e71d66e6a0ccadc5edb98e1e1a134b68489da1728aa2e1" }, "downloads": -1, "filename": "actionqueues-0.7-py2-none-any.whl", "has_sig": false, "md5_digest": "f332e9939009046696e14df4ef603518", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 5453, "upload_time": "2018-07-05T09:27:34", "url": "https://files.pythonhosted.org/packages/b4/12/34dd686d716a906384b8701689ced86e2188c49dd5237f6a5f4d27f789c4/actionqueues-0.7-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3a86fa7ce4e5f79b99562ea920df9243", "sha256": "df28ac3404a2c2b2adc1e3d5fc5ae0ad16e80b2e7af64a65312791d4f34b6603" }, "downloads": -1, "filename": "actionqueues-0.7.tar.gz", "has_sig": false, "md5_digest": "3a86fa7ce4e5f79b99562ea920df9243", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4986, "upload_time": "2018-07-05T09:27:35", "url": "https://files.pythonhosted.org/packages/5f/67/7d56639be57aaf8d14d52f9358db287b1e14e4a02419fdccec43da416869/actionqueues-0.7.tar.gz" } ], "0.8": [ { "comment_text": "", "digests": { "md5": "ea5911eeae53830435b14a7b1857e554", "sha256": "3064bff6ffdfa338520c9a98e15a409535f9c1972b2a9116b47a674d01d9c3ca" }, "downloads": -1, "filename": "actionqueues-0.8-py2-none-any.whl", "has_sig": false, "md5_digest": "ea5911eeae53830435b14a7b1857e554", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 6534, "upload_time": "2018-07-05T15:39:33", "url": "https://files.pythonhosted.org/packages/b5/f1/6c7d26a068526ec6fda90017d2e55977cc45f97004240dcd9fac6ec3a363/actionqueues-0.8-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8677c557edce221a19bdeb3a5a50147a", "sha256": "0bc73714a7ba146c8d6b22ab5d59b03ba96288bec79e45c7defe8357004f3f99" }, "downloads": -1, "filename": "actionqueues-0.8.tar.gz", "has_sig": false, "md5_digest": "8677c557edce221a19bdeb3a5a50147a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5914, "upload_time": "2018-07-05T15:39:34", "url": "https://files.pythonhosted.org/packages/b0/35/c7c2f543034e52088ded97e3016dafcdb31c39d0540d114ce4cdb8210c91/actionqueues-0.8.tar.gz" } ], "0.8.1": [ { "comment_text": "", "digests": { "md5": "4c598c28a40170ba715b44eba23c8ba4", "sha256": "2b17115ff31cd5221e32b8f6c9987e41ebc9373bbf2b6042382d0b35cf74ca0b" }, "downloads": -1, "filename": "actionqueues-0.8.1-py2-none-any.whl", "has_sig": false, "md5_digest": "4c598c28a40170ba715b44eba23c8ba4", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 6555, "upload_time": "2018-08-06T11:47:45", "url": "https://files.pythonhosted.org/packages/a7/ed/a2585f81f0b7d94ab2d2463974ac6d6b9baa20e127a71e4df7c1df674972/actionqueues-0.8.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "62f4f739bb852dc8592618f852150664", "sha256": "b9b3f5b1f14a2b837f19e9b07d3d6cd56c3723410ed5971dbaa4060ee9be9b77" }, "downloads": -1, "filename": "actionqueues-0.8.1.tar.gz", "has_sig": false, "md5_digest": "62f4f739bb852dc8592618f852150664", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5924, "upload_time": "2018-08-06T11:47:47", "url": "https://files.pythonhosted.org/packages/b3/3b/5b06afe92fe5973990bd2730068db75eb4b6f9096e00f7d6d0b02a066cb8/actionqueues-0.8.1.tar.gz" } ], "0.8.2": [ { "comment_text": "", "digests": { "md5": "4f9bd0e18ad20b54ef9bf74760feb192", "sha256": "489cadf5d9e7278094532a88374e3013f3ea9e4b47cbf35e72542c68befe83c2" }, "downloads": -1, "filename": "actionqueues-0.8.2-py2-none-any.whl", "has_sig": false, "md5_digest": "4f9bd0e18ad20b54ef9bf74760feb192", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 6560, "upload_time": "2018-08-07T10:02:54", "url": "https://files.pythonhosted.org/packages/85/4b/05952245174588fbe5682b0e10579da3703fb586cf9747dd4b681b7108c8/actionqueues-0.8.2-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9f1fbf64904ca27244639a1db20fd037", "sha256": "0d56506df379538be0fb123b115e60b62674651598f18fc4cbd5cfb708529251" }, "downloads": -1, "filename": "actionqueues-0.8.2.tar.gz", "has_sig": false, "md5_digest": "9f1fbf64904ca27244639a1db20fd037", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5922, "upload_time": "2018-08-07T10:02:55", "url": "https://files.pythonhosted.org/packages/aa/ed/9a77866c99d0c244d67f8267a3667e8f3a48a914f3f6b6031eb6b4c6f84b/actionqueues-0.8.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "4f9bd0e18ad20b54ef9bf74760feb192", "sha256": "489cadf5d9e7278094532a88374e3013f3ea9e4b47cbf35e72542c68befe83c2" }, "downloads": -1, "filename": "actionqueues-0.8.2-py2-none-any.whl", "has_sig": false, "md5_digest": "4f9bd0e18ad20b54ef9bf74760feb192", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 6560, "upload_time": "2018-08-07T10:02:54", "url": "https://files.pythonhosted.org/packages/85/4b/05952245174588fbe5682b0e10579da3703fb586cf9747dd4b681b7108c8/actionqueues-0.8.2-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9f1fbf64904ca27244639a1db20fd037", "sha256": "0d56506df379538be0fb123b115e60b62674651598f18fc4cbd5cfb708529251" }, "downloads": -1, "filename": "actionqueues-0.8.2.tar.gz", "has_sig": false, "md5_digest": "9f1fbf64904ca27244639a1db20fd037", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5922, "upload_time": "2018-08-07T10:02:55", "url": "https://files.pythonhosted.org/packages/aa/ed/9a77866c99d0c244d67f8267a3667e8f3a48a914f3f6b6031eb6b4c6f84b/actionqueues-0.8.2.tar.gz" } ] }