{ "info": { "author": "Zhou Zheng Sheng", "author_email": "zhshzhou@linux.vnet.ibm.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "=================\n RollbackContext \n=================\n------------------------------------------------\n A Context Manager to Do Rollback Automatically\n------------------------------------------------\n\nPurpose\n=======\nSometimes we need to perform a series of operations::\n\n op[0], op[1], ... op[N]\n\nThese operations may allocate files, locks, connections, and ``op[K]`` may depend on result of ``op[K-1]``. Each operation creates a context. When the operations are done, we want to destroy those contexts. Sometimes it's not feasible to use standard Python Context Manager Protocol, because the number of the resource involved in a transaction can be a variable. There is no way to use a variable number of the ``with`` statement, and ``contextlib.nested`` is being deprecated. Sometimes it's just too verbose to create standard Python Context Manager for each context.\n\nThis library implements a concise Context Manager and proposes an idiom for rollback.\n\nHow It Works\n============\nRollbackContext is a context manager to be used in a ``with`` statement. Once instantiated, it creates a undo stack. It allows the programme to register undo function after each successful operation. Each new undo function is push to the top of the stack. When the the operations are successful and execution flow leaves the ``with`` statement, it tries to pop the undo functions and run them. In all, it runs the following operation series::\n\n op[0], op[1], ... op[N], DONE, -op[N], ... , -op[1], -op[0]\n\nIf there is an exception from ``op[i]``, it aborts the execution of the op series and starts to run undos registered so far. If there is an exception from the undos, it ignores the exception temporary and continues to run the rest of the undos. At last, it re-raises the earliest exception it sees. This is because latter exceptions may be caused by an earlier exception, so the most helpful exception for diagnosing the problem is the earliest one. Meanwhile, it should execute all the undos to destroy all the contexts as much as possible.\n\nHow to Use\n==========\n0. Run as superuser, ``easy_install rollbackcontext`` or ``pip install rollbackcontext``.\n1. In Python, ``from rollbackcontext import RollbackContext``.\n2. Use it in a ``with`` statement as following:\n\n::\n\n with RollbackContext() as rollback:\n\n3. Write code for the operations, after each operation success, register a reverse operation by calling the ``push`` method.\n\n::\n\n with RollbackContext() as rollback:\n op0()\n rollbcak.push(op0Reverse) # op0Reverse is a callable.\n op1(args, ...)\n rollbcak.push(op1Reverse, argsForOp1Reverse, ...)\n\nThe ``push`` method accepts callable and the optional arguments that would be passed to the callable. In the above case, RollbackContext runs ``op0()``, then ``op1(args, ...)``, then ``op1Reverse(argsForOp1Reverse, ...)``, and at last ``op0Reverse()``.\n\nExamples\n========\nA most simple example may be the following::\n\n from sys import stdout\n \n from rollbackcontext import RollbackContext\n \n with RollbackContext() as rollback:\n print \"Op 0\"\n rollback.push(lambda: stdout.write(\"Undo 0\\n\"))\n print \"Op 1\"\n rollback.push(lambda: stdout.write(\"Undo 1\\n\"))\n print \"Op 2\"\n rollback.push(lambda: stdout.write(\"Undo 2\\n\"))\n # Prints the following\n # Op 0\n # Op 1\n # Op 2\n # Undo 2\n # Undo 1\n # Undo 0\n\nYou can refer to unit test code to find many more examples.\n\nHere are some examples from simplified production code::\n\n def vm_lifecycle(...):\n ''' The task is to create the VM, perform some tests and\n destroy the VM and related resources. '''\n with RollbackContext() as rollback:\n templates_create('testTemplate', ...)\n rollback.push(template_delete, 'testTemplate')\n \n vms_create('testVM', ...)\n rollback.push(vm_delete, 'testVM')\n \n vm_start('testVM', ...)\n rollback.push(vm_stop, 'testVM')\n \n # Do whatever with the VM\n\nAnother one::\n\n def prepare(...):\n ''' The task is to detect if a NFS export could be mounted or not. '''\n with RollbackContext() as rollback:\n mnt_point = tempfile.mkdtemp(dir='/tmp')\n rollback.push(os.rmdir, mnt_point)\n \n mount_cmd = [\"mount\", ..., mnt_point]\n try:\n run_command(mount_cmd, 30) # Wait for 30 seconds\n except TimeoutError:\n return False\n umount_cmd = [\"umount\", \"-f\", mnt_point]\n rollback.push(run_command, umount_cmd)\n \n # Do whatever with the mounted filesystem\n return True\n\nYet another one::\n\n def probe_user(self):\n ''' The task is to start a libvirt domain and detect the user id of the\n VM process. '''\n user = None\n with RollbackContext() as rollback:\n conn = libvirt.open('qemu:///system')\n rollback.push(conn.close)\n dom = conn.defineXML('...')\n rollback.push(dom.undefine)\n dom.create()\n rollback.push(dom.destroy)\n with open('/var/run/libvirt/qemu/%s.pid' % self.vm_name) as f:\n pidStr = f.read()\n p = psutil.Process(int(pidStr))\n user = p.username\n return user\n\nThe above code comes from `project kimchi `_, a HTML5 based management tool for KVM.\n\nMore Helpful Features\n=====================\n\nCancel All Rollbacks\n--------------------\nMost of the time we need to run all the undos, but sometimes we want to cancel the undos if all operations are successful. In this case, call the ``commitAll`` method to cancel all the undos as following::\n\n with RollbackContext() as rollback:\n print 'Op 0'\n rollback.push(op0Reverse)\n print 'Op 1'\n rollback.push(op1Reverse)\n rollback.commitAll()\n\nIf the ``with`` statement ends successfully, ``commitAll()`` cancels all undo, so that the contexts created in the ``with`` statement will be left untouched for future use. If there is exception from the ``with`` statement, ``commitAll()`` will not be run, so it still runs all the undo functions. Sounds like \"start transaction\", \"commit transaction\" and \"automatic rollback\" in a database stored procedure, isn't it?\n\nCancel a Particular Rollback\n----------------------------\nSometimes we want to cancel a particular undo if all operations are successful. In this case, call the ``setAutoCommit`` method of the object returned from the ``push`` method.\n\n::\n\n with RollbackContext() as rollback:\n print 'Op 0'\n rollback.push(op0Reverse).setAutoCommit()\n print 'Op 1'\n rollback.push(op1Reverse)\n\nIf any exception would be raised within the ``with`` statement, ``op1Reverse()`` and ``op2Reverse()`` would be run. If the ``with`` statement was successful, only ``op1Reverse()`` would be run.\n\nRegister Undo Function to the Bottom of the Stack\n-------------------------------------------------\nNormally the ``push`` method adds the undo function to the top of the undo stack. In case you want to insert undo function to the bottom of the undo stack, use the ``pushBottom`` method.\n\n::\n\n from sys import stdout\n \n \n with RollbackContext() as rollback:\n rollback.pushBottom(lambda: stdout.write(\"0\\n\"))\n rollback.pushBottom(lambda: stdout.write(\"1\\n\"))\n rollback.pushBottom(lambda: stdout.write(\"2\\n\"))\n # Should print\n # 0\n # 1\n # 2\n\nAnti-pattern Examples\n=====================\nUnfortunately, C programmers can not enjoy the delight from our RollbackContext, they have to detect error code of each operation and use ``goto out0``, ``goto out1``, and so on, to simulate our RollbackContext manually. The following function comes from Linux kernel source code::\n\n static int __init init_nfs_fs(void)\n {\n \tint err;\n \n \terr = register_pernet_subsys(&nfs_net_ops);\n \tif (err < 0)\n \t\tgoto out9;\n \n \terr = nfs_fscache_register();\n \tif (err < 0)\n \t\tgoto out8;\n \n \terr = nfsiod_start();\n \tif (err)\n \t\tgoto out7;\n \n \terr = nfs_fs_proc_init();\n \tif (err)\n \t\tgoto out6;\n \n \terr = nfs_init_nfspagecache();\n \tif (err)\n \t\tgoto out5;\n \n \terr = nfs_init_inodecache();\n \tif (err)\n \t\tgoto out4;\n \n \terr = nfs_init_readpagecache();\n \tif (err)\n \t\tgoto out3;\n \n \terr = nfs_init_writepagecache();\n \tif (err)\n \t\tgoto out2;\n \n \terr = nfs_init_directcache();\n \tif (err)\n \t\tgoto out1;\n \n #ifdef CONFIG_PROC_FS\n \trpc_proc_register(&init_net, &nfs_rpcstat);\n #endif\n \tif ((err = register_nfs_fs()) != 0)\n \t\tgoto out0;\n \n \treturn 0;\n out0:\n #ifdef CONFIG_PROC_FS\n \trpc_proc_unregister(&init_net, \"nfs\");\n #endif\n \tnfs_destroy_directcache();\n out1:\n \tnfs_destroy_writepagecache();\n out2:\n \tnfs_destroy_readpagecache();\n out3:\n \tnfs_destroy_inodecache();\n out4:\n \tnfs_destroy_nfspagecache();\n out5:\n \tnfs_fs_proc_exit();\n out6:\n \tnfsiod_stop();\n out7:\n \tnfs_fscache_unregister();\n out8:\n \tunregister_pernet_subsys(&nfs_net_ops);\n out9:\n \treturn err;\n }\n\nIf this function was to be written in Python (of course it never would), without RollbackContext, you have to write as the following::\n\n def init_nfs_fs():\n with op0() as context0: # Suppose you wrap opX into context managers\n with op1() as context1:\n # ...\n for c in [contextN, ... , context1, context0]:\n c.cancelDestroy()\n\nOr the following::\n\n def init_nfs_fs():\n try:\n op0()\n try:\n op1()\n # ...\n except Exception:\n op1Reverse()\n raise\n except Exception:\n op0Reverse()\n raise\n\nWith the help of RollbackContext, we can re-structure it as the following::\n\n def init_nfs_fs():\n with RollbackContext() as rollback:\n op0() # Suppose op0() raises exception when it fails\n rollback.push(op0Reverse)\n op1()\n rollback.push(op1Reverse)\n # ...\n rollback.commitAll()\n\nIt's cleaner. Whenever you find yourself dealing with similar case in Python, nesting ``try...except...finally`` or ``with`` blocks, you might want to have a try on RollbackContext.\n\nFor more anti-pattern examples, you can just ``git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git``, and ``git grep 'goto out5'``, ``git grep 'goto out6'`` and more. Currently the worst case is ``bfin_lq035q1_probe`` function in ``drivers/video/bfin-lq035q1-fb.c``, it ``goto out10``. Think of when a developer wants to add a new operation and cleanup code into the existing operation series, he has to manually change all the ``X`` in ``goto outX`` and ``outX:``. \u6709\u591a\u75db\u82e6\uff0c\u4f60\u4eec\u611f\u53d7\u4e00\u4e0b ``;-)``.", "description_content_type": null, "docs_url": "https://pythonhosted.org/rollbackcontext/", "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/edwardbadboy/rollbackcontext", "keywords": "rollback context manager", "license": "LGPL2+", "maintainer": null, "maintainer_email": null, "name": "rollbackcontext", "package_url": "https://pypi.org/project/rollbackcontext/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/rollbackcontext/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/edwardbadboy/rollbackcontext" }, "release_url": "https://pypi.org/project/rollbackcontext/0.1-2/", "requires_dist": null, "requires_python": null, "summary": "A context manager to do rollbacks automatically", "version": "0.1-2" }, "last_serial": 994234, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "1fc2fa5f14177d6083223c3efc26cd13", "sha256": "32bc503765d3955cd24437dd997230dde1ea96e54ead1d9b3e2ee0c375458b7f" }, "downloads": -1, "filename": "rollbackcontext-0.1.tar.gz", "has_sig": false, "md5_digest": "1fc2fa5f14177d6083223c3efc26cd13", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6606, "upload_time": "2014-02-07T16:16:57", "url": "https://files.pythonhosted.org/packages/c0/93/a32b24ecf9cbda1b42a77a3e24b725119f32fa98eedd8eb646fa98d5784f/rollbackcontext-0.1.tar.gz" } ], "0.1-1": [ { "comment_text": "", "digests": { "md5": "ba84fe633e41cd3e1e7734a42c8579cf", "sha256": "e2f47c2fad1a66b3396a581e17cc5dd43bcfd443bfbbfc855016ca589dc997af" }, "downloads": -1, "filename": "rollbackcontext-0.1-1.tar.gz", "has_sig": false, "md5_digest": "ba84fe633e41cd3e1e7734a42c8579cf", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6608, "upload_time": "2014-02-08T07:38:38", "url": "https://files.pythonhosted.org/packages/6d/fa/d752ce5211b02b8199eccc7d0fd82dbd26da62e87032137fabf8302a6c6d/rollbackcontext-0.1-1.tar.gz" } ], "0.1-2": [ { "comment_text": "", "digests": { "md5": "ce67f571afa07d001e9ad3e39eb59bc9", "sha256": "ec7bcf5742e6a9e7958d4ad26366f081569c496645e351dc7fa6d3c2a4ae8d7c" }, "downloads": -1, "filename": "rollbackcontext-0.1-2.tar.gz", "has_sig": false, "md5_digest": "ce67f571afa07d001e9ad3e39eb59bc9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7345, "upload_time": "2014-02-08T09:10:51", "url": "https://files.pythonhosted.org/packages/65/99/b86d0ed90c257dc18a8c6aecb52ea1131a060d5c39f791a09fddf2ec2e2b/rollbackcontext-0.1-2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ce67f571afa07d001e9ad3e39eb59bc9", "sha256": "ec7bcf5742e6a9e7958d4ad26366f081569c496645e351dc7fa6d3c2a4ae8d7c" }, "downloads": -1, "filename": "rollbackcontext-0.1-2.tar.gz", "has_sig": false, "md5_digest": "ce67f571afa07d001e9ad3e39eb59bc9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7345, "upload_time": "2014-02-08T09:10:51", "url": "https://files.pythonhosted.org/packages/65/99/b86d0ed90c257dc18a8c6aecb52ea1131a060d5c39f791a09fddf2ec2e2b/rollbackcontext-0.1-2.tar.gz" } ] }