using System;
namespace Python.Runtime
{
///
/// Provides a managed interface to exceptions thrown by the Python
/// runtime.
///
public class PythonException : System.Exception
{
private readonly PyReferenceDecrementer _referenceDecrementer;
private IntPtr _pyType = IntPtr.Zero;
private IntPtr _pyValue = IntPtr.Zero;
private IntPtr _pyTB = IntPtr.Zero;
private string _tb = "";
private string _message = "";
private bool disposed = false;
public PythonException()
{
_referenceDecrementer = PythonEngine.CurrentRefDecrementer;
IntPtr gs = PythonEngine.AcquireLock();
Runtime.PyErr_Fetch(ref _pyType, ref _pyValue, ref _pyTB);
// Those references already owned by the caller
////Runtime.XIncref(_pyType);
////Runtime.XIncref(_pyValue);
////Runtime.XIncref(_pyTB);
if (_pyType != IntPtr.Zero && _pyValue != IntPtr.Zero)
{
string type;
string message;
Runtime.XIncref(_pyType);
using (var pyType = new PyObject(_pyType))
using (PyObject pyTypeName = pyType.GetAttr("__name__"))
{
type = pyTypeName.ToString();
}
Runtime.XIncref(_pyValue);
using (var pyValue = new PyObject(_pyValue))
{
message = pyValue.ToString();
}
_message = type + " : " + message;
}
if (_pyTB != IntPtr.Zero)
{
if (_tbModule == null)
{
_tbModule = PythonEngine.ImportModule("traceback");
}
PyObject tb_module = _tbModule;
Runtime.XIncref(_pyTB);
using (var pyTB = new PyObject(_pyTB))
{
_tb = tb_module.InvokeMethod("format_tb", pyTB).ToString();
}
}
PythonEngine.ReleaseLock(gs);
}
private static PyObject _tbModule;
// Ensure that encapsulated Python objects are decref'ed appropriately
// when the managed exception wrapper is garbage-collected.
~PythonException()
{
Dispose(false);
}
///
/// Restores python error.
///
public void Restore()
{
IntPtr gs = PythonEngine.AcquireLock();
Runtime.PyErr_Restore(_pyType, _pyValue, _pyTB);
_pyType = IntPtr.Zero;
_pyValue = IntPtr.Zero;
_pyTB = IntPtr.Zero;
PythonEngine.ReleaseLock(gs);
}
///
/// PyType Property
///
///
/// Returns the exception type as a Python object.
///
public IntPtr PyType
{
get { return _pyType; }
}
///
/// PyValue Property
///
///
/// Returns the exception value as a Python object.
///
public IntPtr PyValue
{
get { return _pyValue; }
}
///
/// PyTB Property
///
///
/// Returns the TraceBack as a Python object.
///
public IntPtr PyTB
{
get { return _pyTB; }
}
///
/// Message Property
///
///
/// A string representing the python exception message.
///
public override string Message
{
get { return _message; }
}
///
/// StackTrace Property
///
///
/// A string representing the python exception stack trace.
///
public override string StackTrace
{
get { return _tb; }
}
///
/// Dispose Method
///
///
/// The Dispose method provides a way to explicitly release the
/// Python objects represented by a PythonException.
/// If object not properly disposed can cause AppDomain unload issue.
/// See GH#397 and GH#400.
///
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
protected void Dispose(bool disposing)
{
if (!disposed)
{
disposed = true;
IntPtr pyTypeToDispose = _pyType;
_pyType = IntPtr.Zero;
IntPtr pyValueToDispose = _pyValue;
_pyValue = IntPtr.Zero;
IntPtr pyTBToDispose = _pyTB;
_pyTB = IntPtr.Zero;
if (disposing)
{
if (Runtime.Py_IsInitialized() > 0 && !Runtime.IsFinalizing)
{
IntPtr gs = PythonEngine.AcquireLock();
try
{
Runtime.XDecref(pyTypeToDispose);
Runtime.XDecref(pyValueToDispose);
// XXX Do we ever get TraceBack? //
if (pyTBToDispose != IntPtr.Zero)
{
Runtime.XDecref(pyTBToDispose);
}
}
finally
{
PythonEngine.ReleaseLock(gs);
}
}
}
else
{
if (pyTypeToDispose != IntPtr.Zero)
{
_referenceDecrementer?.ScheduleDecRef(pyTypeToDispose);
}
if (pyValueToDispose != IntPtr.Zero)
{
_referenceDecrementer?.ScheduleDecRef(pyValueToDispose);
}
if (pyTBToDispose != IntPtr.Zero)
{
_referenceDecrementer?.ScheduleDecRef(pyTBToDispose);
}
}
}
}
///
/// Matches Method
///
///
/// Returns true if the Python exception type represented by the
/// PythonException instance matches the given exception type.
///
public static bool Matches(IntPtr ob)
{
return Runtime.PyErr_ExceptionMatches(ob) != 0;
}
}
}