-
-
Save brycepg/b10de7804c77f547ba3b6a32aad94e24 to your computer and use it in GitHub Desktop.
| { | |
| "cells": [ | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "\"\"\"Use decorators to capture exceptions from a procedural call.\n", | |
| "\n", | |
| "Return a structure containing information about the call.\"\"\"\n", | |
| "\n", | |
| "\n", | |
| "class TestInfo:\n", | |
| " \"\"\"Contains data from function call.\n", | |
| " \n", | |
| " Includes status and any exception that occured\"\"\"\n", | |
| " def __init__(self):\n", | |
| " self.exc = None\n", | |
| " self.status = None\n", | |
| " self.info = None\n", | |
| " def __repr__(self):\n", | |
| " \"\"\"for IPython\"\"\"\n", | |
| " return str(self.__dict__)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 10, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "import sys\n", | |
| "from functools import wraps\n", | |
| "def exc_info(func):\n", | |
| " \"\"\"Decorator to return status information\"\"\"\n", | |
| " @wraps(func)\n", | |
| " def pack_exception(*args, **kwargs):\n", | |
| " \"\"\"Return function status\n", | |
| " \n", | |
| " Requires test_info kwarg.\n", | |
| " Pack the decorated function with an extra argument with test_info obj.\n", | |
| " Catch exception to determine test_info status.\n", | |
| " Return test_info object\"\"\"\n", | |
| " t = TestInfo()\n", | |
| " kwargs['test_info'] = t\n", | |
| " try:\n", | |
| " func(*args, **kwargs)\n", | |
| " except Error:\n", | |
| " t.status = \"failure\"\n", | |
| " t.exc = sys.exc_info()\n", | |
| " else:\n", | |
| " t.status = \"good\"\n", | |
| " return t\n", | |
| " return pack_exception\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "class Error(Exception):\n", | |
| " pass\n", | |
| "\n", | |
| "@exc_info\n", | |
| "def bad_parse(dir_, test_name, test_info):\n", | |
| " \"\"\"In this test an exception occurs before the side-effect happens\"\"\"\n", | |
| " test_info.info = \"Specific info2\"\n", | |
| " raise Error(\"Something happened\")\n", | |
| " print(\"Did stuff on %s for %s\" % (dir_, test_name))\n", | |
| " \n", | |
| "@exc_info \n", | |
| "def good_parse(dir_, test_name, test_info):\n", | |
| " \"\"\"This test completes successfully\"\"\"\n", | |
| " test_info.info = \"Specific information\"\n", | |
| " print(\"Did stuff on %s for %s\" % (dir_, test_name))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Did stuff on mydir for test1\n" | |
| ] | |
| }, | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "{'info': 'Specific information', 'status': 'good', 'exc': None}" | |
| ] | |
| }, | |
| "execution_count": 12, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "good_parse(\"mydir\", \"test1\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 13, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "{'info': 'Specific info2', 'status': 'failure', 'exc': (<class '__main__.Error'>, Error('Something happened',), <traceback object at 0x7f5768845648>)}" | |
| ] | |
| }, | |
| "execution_count": 13, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "bad_parse(\"mydir\", \"test2\")" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "Python 3", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.4.5" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 0 | |
| } |
Also, you might be able to clean / compact things a bit by using a decorator class rather than a function and moving TestInfo stuff into the decorator class ( again this would be more applicable if TestInfo was a function attribute rather than a returned object ).
Here is an example of what I mean for the first comment
"""Use decorators to capture exceptions from a procedural call.
Return a structure containing information about the call."""
class TestInfo:
"""Contains data from function call.
Includes status and any exception that occured"""
def __init__(self):
self.exc = None
self.status = None
self.info = None
def __repr__(self):
"""for IPython"""
return str(self.__dict__)
import sys
from functools import wraps
def exc_info(func):
"""Decorator to return status information"""
func.t = TestInfo()
@wraps(func)
def pack_exception(*args, **kwargs):
"""Return function status
Requires test_info kwarg.
Pack the decorated function with an extra argument with test_info obj.
Catch exception to determine test_info status.
Return test_info object"""
try:
return func(*args, **kwargs)
except Error:
func.t.status = "failure"
func.t.exc = sys.exc_info()
else:
func.t.status = "good"
return pack_exception
class Error(Exception):
pass
@exc_info
def bad_parse(dir_, test_name):
"""In this test an exception occurs before the side-effect happens"""
#test_info.info = "Specific info2"
raise Error("Something happened")
print("Did stuff on %s for %s" % (dir_, test_name))
@exc_info
def good_parse(dir_, test_name):
"""This test completes successfully"""
#test_info.info = "Specific information"
print("Did stuff on %s for %s" % (dir_, test_name))
return 5
In the final version I pack the decorated functions return value, along with TestInfo object on return, if the decorated functions return value isn't None.
In this simplified version, I use the repr for readability in iPython. I need the TestInfo object to be returned even if there is a success to affirm that all functions were called without an exception.
I'm not sure how making the decorator a class will help. I need TestInfo to be a value-object returned to the caller to do analysis(this example is a proof of concept). I can define TestInfo class inside the decorator function exc_info however, which I think makes logical sense since nowhere else does it need to be used.
Looks good. One thing I might recommend is changing repr to actually print the traceback. Also, will this work if good_parse returned something? If not, I wonder if it might not be better to simply attach the test_info as an attribute of the function, which could be checked. IE. within the wrapper, call good_parse (which no longer takes a test_info object) and get whatever it returns. Then the user can check good_parse.test_info to see how the last call to it ran.