-
-
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.