{ "info": { "author": "Ryan Kelly", "author_email": "ryan@rfk.id.au", "bugtrack_url": null, "classifiers": [], "description": "withrestart: structured error recovery using named restart functions\n\nThis is a Pythonisation (Lispers might rightly say \"bastardisation\") of the\nrestart-based condition system of Common Lisp. It's designed to make error\nrecovery simpler and easier by removing the assumption that unhandled errors\nmust be fatal.\n\nA \"restart\" represents a named strategy for resuming execution of a function\nafter the occurrence of an error. At any point during its execution a\nfunction can push a Restart object onto its call stack. If an exception\noccurs within the scope of that Restart, code higher-up in the call chain can\ninvoke it to recover from the error and let the function continue execution.\nBy providing several restarts, functions can offer several different strategies\nfor recovering from errors.\n\nA \"handler\" represents a higher-level strategy for dealing with the occurrence\nof an error. It is conceptually similar to an \"except\" clause, in that one\nestablishes a suite of Handler objects to be invoked if an error occurs during\nthe execution of some code. There is, however, a crucial difference: handlers\nare executed without unwinding the call stack. They thus have the opportunity\nto take corrective action and then resume execution of whatever function\nraised the error.\n\nFor example, consider a function that reads the contents of all files from a \ndirectory into a dict in memory::\n\n def readall(dirname):\n data = {}\n for filename in os.listdir(dirname):\n filepath = os.path.join(dirname,filename)\n data[filename] = open(filepath).read()\n return data\n\nIf one of the files goes missing after the call to os.listdir() then the\nsubsequent open() will raise an IOError. While we could catch and handle the\nerror inside this function, what would be the appropriate action? Should\nfiles that go missing be silently ignored? Should they be re-created with\nsome default contents? Should a special sentinel value be placed in the\ndata dictionary? What value? The readall() function does not have enough\ninformation to decide on an appropriate recovery strategy.\n\nInstead, readall() can provide the *infrastructure* for doing error recovery\nand leave the final decision up to the calling code. The following definition\nuses three pre-defined restarts to let the calling code (a) skip the missing\nfile completely, (2) retry the call to open() after taking some corrective\naction, or (3) use some other value in place of the missing file::\n\n def readall(dirname):\n data = {}\n for filename in os.listdir(dirname):\n filepath = os.path.join(dirname,filename)\n with restarts(skip,retry,use_value) as invoke:\n data[filename] = invoke(open,filepath).read()\n return data\n\nOf note here is the use of the \"with\" statement to establish a new context\nin the scope of restarts, and use of the \"invoke\" wrapper when calling a\nfunction that might fail. The latter allows restarts to inject an alternate\nreturn value for the failed function.\n\nHere's how the calling code would look if it wanted to silently skip the\nmissing file::\n\n def concatenate(dirname):\n with Handler(IOError,\"skip\"):\n data = readall(dirname)\n return \"\".join(data.itervalues())\n\nThis pushes a Handler instance into the execution context, which will detect\nIOError instances and respond by invoking the \"skip\" restart point. If this\nhandler is invoked in response to an IOError, execution of the readall()\nfunction will continue immediately following the \"with restarts(...)\" block.\n\nNote that there is no way to achieve this skip-and-continue behaviour using an\nordinary try-except block; by the time the IOError has propagated up to the\nconcatenate() function for processing, all context from the execution of \nreadall() will have been unwound and cannot be resumed.\n\nCalling code that wanted to re-create the missing file would simply push a\ndifferent error handler::\n\n def concatenate(dirname):\n def handle_IOError(e):\n open(e.filename,\"w\").write(\"MISSING\")\n raise InvokeRestart(\"retry\")\n with Handler(IOError,handle_IOError):\n data = readall(dirname)\n return \"\".join(data.itervalues())\n\nBy raising InvokeRestart, this handler transfers control back to the restart\nthat was established by the readall() function. This particular restart\nwill re-execute the failing function call and let readall() continue with its\noperation.\n\nCalling code that wanted to use a special sentinel value would use a handler\nto pass the required value to the \"use_value\" restart::\n\n def concatenate(dirname):\n class MissingFile:\n def read():\n return \"MISSING\"\n def handle_IOError(e):\n raise InvokeRestart(\"use_value\",MissingFile())\n with Handler(IOError,handle_IOError):\n data = readall(dirname)\n return \"\".join(data.itervalues())\n\n\nBy separating the low-level details of recovering from an error from the\nhigh-level strategy of what action to take, it's possible to create quite\npowerful recovery mechanisms.\n\nWhile this module provides a handful of pre-built restarts, functions will\nusually want to create their own. This can be done by passing a callback\ninto the Restart object constructor::\n\n def readall(dirname):\n data = {}\n for filename in os.listdir(dirname):\n filepath = os.path.join(dirname,filename)\n def log_error():\n print \"an error occurred\"\n with Restart(log_error):\n data[filename] = open(filepath).read()\n return data\n\n\nOr by using a decorator to define restarts inline::\n\n def readall(dirname):\n data = {}\n for filename in os.listdir(dirname):\n filepath = os.path.join(dirname,filename)\n with restarts() as invoke:\n @invoke.add_restart\n def log_error():\n print \"an error occurred\"\n data[filename] = open(filepath).read()\n return data\n\nHandlers can also be defined inline using a similar syntax::\n\n def concatenate(dirname):\n with handlers() as h:\n @h.add_handler\n def IOError(e):\n open(e.filename,\"w\").write(\"MISSING\")\n raise InvokeRestart(\"retry\")\n data = readall(dirname)\n return \"\".join(data.itervalues())\n\n\nNow finally, a disclaimer. I've never written any Common Lisp. I've only read\nabout the Common Lisp condition system and how awesome it is. I'm sure there\nare many things that it can do that this module simply cannot. For example:\n\n * Since this is built on top of a standard exception-throwing system, the\n handlers can only be executed after the stack has been unwound to the\n most recent restart context; in Common Lisp they're executed without\n unwinding the stack at all.\n * Since this is built on top of a standard exception-throwing system, it's\n probably too heavyweight to use for generic condition signalling system.\n\nNevertheless, there's no shame in pinching a good idea when you see one...", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/rfk/withrestart", "keywords": "condition restart error exception", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "withrestart", "package_url": "https://pypi.org/project/withrestart/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/withrestart/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/rfk/withrestart" }, "release_url": "https://pypi.org/project/withrestart/0.2.7/", "requires_dist": null, "requires_python": null, "summary": "a Pythonisation of the restart-based condition system from Common Lisp", "version": "0.2.7" }, "last_serial": 801692, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "ac48d7dc43db84a4d1d45919cce5a155", "sha256": "8497dca1c63575a01e96b1028c28437b6aff1491b8b74e9f0eaf3d5bef637d7d" }, "downloads": -1, "filename": "withrestart-0.1.0.tar.gz", "has_sig": false, "md5_digest": "ac48d7dc43db84a4d1d45919cce5a155", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7203, "upload_time": "2009-12-16T04:35:52", "url": "https://files.pythonhosted.org/packages/c0/56/7bdb381645e6b51e2ad33c7a555ab78e1ab7b23343e1c7ffa5c3ddf1fa81/withrestart-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "a717f54b6fa1453c4541ad8994fd1126", "sha256": "d25c3ec279e1ddb7d01287edad0caa34d9fb04db696f0884618f8516bd8b1220" }, "downloads": -1, "filename": "withrestart-0.2.0.tar.gz", "has_sig": false, "md5_digest": "a717f54b6fa1453c4541ad8994fd1126", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10137, "upload_time": "2009-12-17T03:12:18", "url": "https://files.pythonhosted.org/packages/ad/04/39ddf0e6ce5010f90cde15287232a05062ecff8ed208186a4ca6c6d71f9e/withrestart-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "6938d0a6965e5f6fb0f3281b53b4bde1", "sha256": "2551521cd4ac0e0387d99476a9a45dcc992b7b140f85a7282d796a37cf6e0a2c" }, "downloads": -1, "filename": "withrestart-0.2.1.tar.gz", "has_sig": false, "md5_digest": "6938d0a6965e5f6fb0f3281b53b4bde1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10295, "upload_time": "2009-12-18T06:26:44", "url": "https://files.pythonhosted.org/packages/23/91/bb40754cac54eab76d6f622552dd8609905d7983782048740bd515909d6c/withrestart-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "3e93a5e92fb384e1d4e3fa35b3bdfa97", "sha256": "f6e61769cbd10987d2cd9d5f04490aac8c72014587ded30a9074a42379227350" }, "downloads": -1, "filename": "withrestart-0.2.2.tar.gz", "has_sig": false, "md5_digest": "3e93a5e92fb384e1d4e3fa35b3bdfa97", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10752, "upload_time": "2010-01-01T06:04:11", "url": "https://files.pythonhosted.org/packages/6b/e3/6cd47805719d061547cb68e30f7898a9bcf84b4707af5495149a0840118d/withrestart-0.2.2.tar.gz" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "ea034b9325f4ec1921f5b49ca5d17502", "sha256": "557e00201b82fe606ab238739cd8e9c0acaf6076664ee9e5614c75b9cfdfce3e" }, "downloads": -1, "filename": "withrestart-0.2.3.tar.gz", "has_sig": false, "md5_digest": "ea034b9325f4ec1921f5b49ca5d17502", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11750, "upload_time": "2010-01-02T01:22:33", "url": "https://files.pythonhosted.org/packages/92/b3/d2691098611793896bec5a82627a7c3a5c2477598692bf67b8a2e50d20cc/withrestart-0.2.3.tar.gz" } ], "0.2.4": [ { "comment_text": "", "digests": { "md5": "4c2b368a215387beec77750d91196028", "sha256": "ba82f7b21581f7d85889f270ac39032a5ead64170f893fe24f1f9dfa387f3eb5" }, "downloads": -1, "filename": "withrestart-0.2.4.tar.gz", "has_sig": false, "md5_digest": "4c2b368a215387beec77750d91196028", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12526, "upload_time": "2010-01-17T14:36:37", "url": "https://files.pythonhosted.org/packages/6b/82/6f4163263f436a384f8b29b0b7818a9f1a154e6726440c9e45381d852f7b/withrestart-0.2.4.tar.gz" } ], "0.2.5": [ { "comment_text": "", "digests": { "md5": "8f3b5c57ecfef49b035d9f391be20247", "sha256": "6362873c819800063f57b102df2533d8acd24aaa7c159e4636dbfd2fd79932e9" }, "downloads": -1, "filename": "withrestart-0.2.5.tar.gz", "has_sig": false, "md5_digest": "8f3b5c57ecfef49b035d9f391be20247", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13146, "upload_time": "2010-01-25T02:02:32", "url": "https://files.pythonhosted.org/packages/24/93/6be9fa63df3279ede9e7c82630f8dc15dd995c64d04dd4491da447e81ef9/withrestart-0.2.5.tar.gz" } ], "0.2.6": [ { "comment_text": "", "digests": { "md5": "97c5f53ed4412b125053634f6e165943", "sha256": "426f6cdbd4941124d0d0eda8feb13971c7c7069ea69e8797c633a66be9294122" }, "downloads": -1, "filename": "withrestart-0.2.6.tar.gz", "has_sig": false, "md5_digest": "97c5f53ed4412b125053634f6e165943", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13316, "upload_time": "2010-02-01T02:36:33", "url": "https://files.pythonhosted.org/packages/53/07/b713ce24d8c6d8f4db4f750acfefc9f444b35be92507be326e11594a11b5/withrestart-0.2.6.tar.gz" } ], "0.2.7": [ { "comment_text": "", "digests": { "md5": "0d76f4ce694228c1ef575a152f51d039", "sha256": "831a8b3d1e2495564b68b0031b70166d39a08d48c7843c23e2471587996d96cf" }, "downloads": -1, "filename": "withrestart-0.2.7.tar.gz", "has_sig": false, "md5_digest": "0d76f4ce694228c1ef575a152f51d039", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13480, "upload_time": "2010-10-19T06:49:24", "url": "https://files.pythonhosted.org/packages/52/9d/4941d94d8e3591d557dc9a52c54e65f72fffad36bfd1eafc7c07dc2b7dc1/withrestart-0.2.7.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0d76f4ce694228c1ef575a152f51d039", "sha256": "831a8b3d1e2495564b68b0031b70166d39a08d48c7843c23e2471587996d96cf" }, "downloads": -1, "filename": "withrestart-0.2.7.tar.gz", "has_sig": false, "md5_digest": "0d76f4ce694228c1ef575a152f51d039", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13480, "upload_time": "2010-10-19T06:49:24", "url": "https://files.pythonhosted.org/packages/52/9d/4941d94d8e3591d557dc9a52c54e65f72fffad36bfd1eafc7c07dc2b7dc1/withrestart-0.2.7.tar.gz" } ] }