// ========================================================================== // This software is subject to the provisions of the Zope Public License, // Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. // THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED // WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS // FOR A PARTICULAR PURPOSE. // ========================================================================== using System; using System.Reflection; using System.Collections; using System.Runtime.InteropServices; namespace Python.Runtime { /// /// Base class for Python types that reflect managed exceptions based on /// System.Exception /// /// /// The Python wrapper for managed exceptions LIES about its inheritance /// tree. Although the real System.Exception is a subclass of /// System.Object the Python type for System.Exception does NOT claim that /// it subclasses System.Object. Instead TypeManager.CreateType() uses /// Python's exception.Exception class as base class for System.Exception. /// internal class ExceptionClassObject : ClassObject { internal ExceptionClassObject(Type tp) : base(tp) { } #if (PYTHON25 || PYTHON26 || PYTHON27) internal static Exception ToException(IntPtr ob) { CLRObject co = GetManagedObject(ob) as CLRObject; if (co == null) { return null; } Exception e = co.inst as Exception; if (e == null) { return null; } return e; } //==================================================================== // Exception __str__ implementation //==================================================================== public new static IntPtr tp_str(IntPtr ob) { Exception e = ToException(ob); if (e == null) { return Exceptions.RaiseTypeError("invalid object"); } string message = String.Empty; if (e.Message != String.Empty) { message = e.Message; } if ((e.StackTrace != null) && (e.StackTrace != String.Empty)) { message = message + "\n" + e.StackTrace; } return Runtime.PyUnicode_FromString(message); } //==================================================================== // Exception __repr__ implementation. //==================================================================== public static IntPtr tp_repr(IntPtr ob) { Exception e = ToException(ob); if (e == null) { return Exceptions.RaiseTypeError("invalid object"); } string name = e.GetType().Name; string message; if (e.Message != String.Empty) { message = String.Format("{0}('{1}',)", name, e.Message); } else { message = String.Format("{0}()", name); } return Runtime.PyUnicode_FromString(message); } //==================================================================== // Exceptions __getattribute__ implementation. // handles Python's args and message attributes //==================================================================== public static IntPtr tp_getattro(IntPtr ob, IntPtr key) { if (!Runtime.PyString_Check(key)) { Exceptions.SetError(Exceptions.TypeError, "string expected"); return IntPtr.Zero; } string name = Runtime.GetManagedString(key); if (name == "args") { Exception e = ToException(ob); IntPtr args; if (e.Message != String.Empty) { args = Runtime.PyTuple_New(1); IntPtr msg = Runtime.PyUnicode_FromString(e.Message); Runtime.PyTuple_SetItem(args, 0, msg); } else { args = Runtime.PyTuple_New(0); } return args; } if (name == "message") { return ExceptionClassObject.tp_str(ob); } return Runtime.PyObject_GenericGetAttr(ob, key); } #endif // (PYTHON25 || PYTHON26 || PYTHON27) } /// /// Encapsulates the Python exception APIs. /// /// /// Readability of the Exceptions class improvements as we look toward version 2.7 ... /// public class Exceptions { internal static IntPtr warnings_module; internal static IntPtr exceptions_module; private Exceptions() {} //=================================================================== // Initialization performed on startup of the Python runtime. //=================================================================== internal static void Initialize() { exceptions_module = Runtime.PyImport_ImportModule("exceptions"); Exceptions.ErrorCheck(exceptions_module); warnings_module = Runtime.PyImport_ImportModule("warnings"); Exceptions.ErrorCheck(warnings_module); Type type = typeof(Exceptions); foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.Static)) { IntPtr op = Runtime.PyObject_GetAttrString(exceptions_module, fi.Name); if (op != IntPtr.Zero) { fi.SetValue(type, op); } else { fi.SetValue(type, IntPtr.Zero); DebugUtil.Print("Unknown exception: " + fi.Name); } } Runtime.PyErr_Clear(); if (Runtime.wrap_exceptions) { SetupExceptionHack(); } } //=================================================================== // Cleanup resources upon shutdown of the Python runtime. //=================================================================== internal static void Shutdown() { Type type = typeof(Exceptions); foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.Static)) { IntPtr op = (IntPtr)fi.GetValue(type); if (op != IntPtr.Zero) { Runtime.Decref(op); } } Runtime.Decref(exceptions_module); Runtime.Decref(warnings_module); } /// /// Shortcut for (pointer == NULL) -> throw PythonException /// /// Pointer to a Python object internal unsafe static void ErrorCheck(IntPtr pointer) { if (pointer == IntPtr.Zero) { throw new PythonException(); } } /// /// Shortcut for (pointer == NULL or ErrorOccurred()) -> throw PythonException /// /// Shortcut for (pointer == NULL) -> throw PythonException internal unsafe static void ErrorOccurredCheck(IntPtr pointer) { if ((pointer == IntPtr.Zero) || Exceptions.ErrorOccurred()) { throw new PythonException(); } } // Versions of CPython up to 2.4 do not allow exceptions to be // new-style classes. To get around that restriction and provide // a consistent user experience for programmers, we wrap managed // exceptions in an old-style class that (through some dont-try- // this-at-home hackery) delegates to the managed exception and // obeys the conventions of both Python and managed exceptions. /// /// Conditionally initialized variables! /// static IntPtr ns_exc; // new-style class for System.Exception static IntPtr os_exc; // old-style class for System.Exception static Hashtable cache; /// /// the lines /// // XXX - hack to raise a compatible old-style exception ;( /// if (Runtime.wrap_exceptions) { /// CallOneOfTheseMethods(); /// /// internal static void SetupExceptionHack() { ns_exc = ClassManager.GetClass(typeof(Exception)).pyHandle; cache = new Hashtable(); string code = "import exceptions\n" + "class Exception(exceptions.Exception):\n" + " _class = None\n" + " _inner = None\n" + " \n" + " #@property\n" + " def message(self):\n" + " return self.Message\n" + " message = property(message)\n" + " \n" + " def __init__(self, *args, **kw):\n" + " inst = self.__class__._class(*args, **kw)\n" + " self.__dict__['_inner'] = inst\n" + " exceptions.Exception.__init__(self, *args, **kw)\n" + "\n" + " def __getattr__(self, name, _marker=[]):\n" + " inner = self.__dict__['_inner']\n" + " v = getattr(inner, name, _marker)\n" + " if v is not _marker:\n" + " return v\n" + " v = self.__dict__.get(name, _marker)\n" + " if v is not _marker:\n" + " return v\n" + " raise AttributeError(name)\n" + "\n" + " def __setattr__(self, name, value):\n" + " inner = self.__dict__['_inner']\n" + " setattr(inner, name, value)\n" + "\n" + " def __str__(self):\n" + " inner = self.__dict__.get('_inner')\n" + " msg = getattr(inner, 'Message', '')\n" + " st = getattr(inner, 'StackTrace', '')\n" + " st = st and '\\n' + st or ''\n" + " return msg + st\n" + " \n" + " def __repr__(self):\n" + " inner = self.__dict__.get('_inner')\n" + " msg = getattr(inner, 'Message', '')\n" + " name = self.__class__.__name__\n" + " return '%s(\\'%s\\',)' % (name, msg) \n" + "\n"; IntPtr dict = Runtime.PyDict_New(); IntPtr builtins = Runtime.PyEval_GetBuiltins(); Runtime.PyDict_SetItemString(dict, "__builtins__", builtins); IntPtr namestr = Runtime.PyString_FromString("System"); Runtime.PyDict_SetItemString(dict, "__name__", namestr); Runtime.Decref(namestr); Runtime.PyDict_SetItemString(dict, "__file__", Runtime.PyNone); Runtime.PyDict_SetItemString(dict, "__doc__", Runtime.PyNone); IntPtr flag = Runtime.Py_file_input; IntPtr result = Runtime.PyRun_String(code, flag, dict, dict); Exceptions.ErrorCheck(result); Runtime.Decref(result); os_exc = Runtime.PyDict_GetItemString(dict, "Exception"); Runtime.PyObject_SetAttrString(os_exc, "_class", ns_exc); Runtime.PyErr_Clear(); } internal static IntPtr GenerateExceptionClass(IntPtr real) { if (real == ns_exc) { return os_exc; } IntPtr nbases = Runtime.PyObject_GetAttrString(real, "__bases__"); if (Runtime.PyTuple_Size(nbases) != 1) { throw new SystemException("Invalid __bases__"); } IntPtr nsbase = Runtime.PyTuple_GetItem(nbases, 0); Runtime.Decref(nbases); IntPtr osbase = GetExceptionClassWrapper(nsbase); IntPtr baselist = Runtime.PyTuple_New(1); Runtime.Incref(osbase); Runtime.PyTuple_SetItem(baselist, 0, osbase); IntPtr name = Runtime.PyObject_GetAttrString(real, "__name__"); IntPtr dict = Runtime.PyDict_New(); IntPtr mod = Runtime.PyObject_GetAttrString(real, "__module__"); Runtime.PyDict_SetItemString(dict, "__module__", mod); Runtime.Decref(mod); IntPtr subc = Runtime.PyClass_New(baselist, dict, name); Runtime.Decref(baselist); Runtime.Decref(dict); Runtime.Decref(name); Runtime.PyObject_SetAttrString(subc, "_class", real); return subc; } internal static IntPtr GetExceptionClassWrapper(IntPtr real) { // Given the pointer to a new-style class representing a managed // exception, return an appropriate old-style class wrapper that // maintains all of the expectations and delegates to the wrapped // class. object ob = cache[real]; if (ob == null) { IntPtr op = GenerateExceptionClass(real); cache[real] = op; return op; } return (IntPtr)ob; } internal static IntPtr GetExceptionInstanceWrapper(IntPtr real) { // Given the pointer to a new-style class instance representing a // managed exception, return an appropriate old-style class // wrapper instance that delegates to the wrapped instance. IntPtr tp = Runtime.PyObject_TYPE(real); if (Runtime.PyObject_TYPE(tp) == Runtime.PyInstanceType) { return real; } // Get / generate a class wrapper, instantiate it and set its // _inner attribute to the real new-style exception instance. IntPtr ct = GetExceptionClassWrapper(tp); Exceptions.ErrorCheck(ct); IntPtr op = Runtime.PyInstance_NewRaw(ct, IntPtr.Zero); Exceptions.ErrorCheck(op); IntPtr d = Runtime.PyObject_GetAttrString(op, "__dict__"); Exceptions.ErrorCheck(d); Runtime.PyDict_SetItemString(d, "_inner", real); Runtime.Decref(d); return op; } internal static IntPtr UnwrapExceptionClass(IntPtr op) { // In some cases its necessary to recognize an exception *class*, // and obtain the inner (wrapped) exception class. This method // returns the inner class if found, or a null pointer. IntPtr d = Runtime.PyObject_GetAttrString(op, "__dict__"); if (d == IntPtr.Zero) { Exceptions.Clear(); return IntPtr.Zero; } IntPtr c = Runtime.PyDict_GetItemString(d, "_class"); Runtime.Decref(d); if (c == IntPtr.Zero) { Exceptions.Clear(); } return c; } /// /// GetException Method /// /// /// /// Retrieve Python exception information as a PythonException /// instance. The properties of the PythonException may be used /// to access the exception type, value and traceback info. /// public static PythonException GetException() { // TODO: implement this. return null; } /// /// ExceptionMatches Method /// /// /// /// Returns true if the current Python exception matches the given /// Python object. This is a wrapper for PyErr_ExceptionMatches. /// public static bool ExceptionMatches(IntPtr ob) { return Runtime.PyErr_ExceptionMatches(ob) != 0; } /// /// ExceptionMatches Method /// /// /// /// Returns true if the given Python exception matches the given /// Python object. This is a wrapper for PyErr_GivenExceptionMatches. /// public static bool ExceptionMatches(IntPtr exc, IntPtr ob) { int i = Runtime.PyErr_GivenExceptionMatches(exc, ob); return (i != 0); } /// /// SetError Method /// /// /// /// Sets the current Python exception given a native string. /// This is a wrapper for the Python PyErr_SetString call. /// public static void SetError(IntPtr ob, string value) { Runtime.PyErr_SetString(ob, value); } /// /// SetError Method /// /// /// /// Sets the current Python exception given a Python object. /// This is a wrapper for the Python PyErr_SetObject call. /// public static void SetError(IntPtr ob, IntPtr value) { Runtime.PyErr_SetObject(ob, value); } /// /// SetError Method /// /// /// /// Sets the current Python exception given a CLR exception /// object. The CLR exception instance is wrapped as a Python /// object, allowing it to be handled naturally from Python. /// public static void SetError(Exception e) { // Because delegates allow arbitrary nestings of Python calling // managed calling Python calling... etc. it is possible that we // might get a managed exception raised that is a wrapper for a // Python exception. In that case we'd rather have the real thing. PythonException pe = e as PythonException; if (pe != null) { Runtime.PyErr_SetObject(pe.PyType, pe.PyValue); return; } IntPtr op = CLRObject.GetInstHandle(e); // XXX - hack to raise a compatible old-style exception ;( if (Runtime.wrap_exceptions) { op = GetExceptionInstanceWrapper(op); } IntPtr etype = Runtime.PyObject_GetAttrString(op, "__class__"); Runtime.PyErr_SetObject(etype, op); Runtime.Decref(etype); Runtime.Decref(op); } /// /// ErrorOccurred Method /// /// /// /// Returns true if an exception occurred in the Python runtime. /// This is a wrapper for the Python PyErr_Occurred call. /// public static bool ErrorOccurred() { return Runtime.PyErr_Occurred() != 0; } /// /// Clear Method /// /// /// /// Clear any exception that has been set in the Python runtime. /// public static void Clear() { Runtime.PyErr_Clear(); } //==================================================================== // helper methods for raising warnings //==================================================================== /// /// Alias for Python's warnings.warn() function. /// public static void warn(string message, IntPtr exception, int stacklevel) { if ((exception == IntPtr.Zero) || (Runtime.PyObject_IsSubclass(exception, Exceptions.Warning) != 1)) { Exceptions.RaiseTypeError("Invalid exception"); } Runtime.Incref(warnings_module); IntPtr warn = Runtime.PyObject_GetAttrString(warnings_module, "warn"); Runtime.Decref(warnings_module); Exceptions.ErrorCheck(warn); IntPtr args = Runtime.PyTuple_New(3); IntPtr msg = Runtime.PyString_FromString(message); Runtime.Incref(exception); // PyTuple_SetItem steals a reference IntPtr level = Runtime.PyInt_FromInt32(stacklevel); Runtime.PyTuple_SetItem(args, 0, msg); Runtime.PyTuple_SetItem(args, 1, exception); Runtime.PyTuple_SetItem(args, 2, level); IntPtr result = Runtime.PyObject_CallObject(warn, args); Exceptions.ErrorCheck(result); Runtime.Decref(warn); Runtime.Decref(result); Runtime.Decref(args); } public static void warn(string message, IntPtr exception) { warn(message, exception, 1); } public static void deprecation(string message, int stacklevel) { warn(message, Exceptions.DeprecationWarning, stacklevel); } public static void deprecation(string message) { deprecation(message, 1); } //==================================================================== // Internal helper methods for common error handling scenarios. //==================================================================== internal static IntPtr RaiseTypeError(string message) { Exceptions.SetError(Exceptions.TypeError, message); return IntPtr.Zero; } // 2010-11-16: Arranged in python (2.6 & 2.7) source header file order /* Predefined exceptions are puplic static variables on the Exceptions class filled in from the python class using reflection in Initialize() looked up by name, not posistion. */ #if (PYTHON25 || PYTHON26 || PYTHON27) public static IntPtr BaseException; #endif public static IntPtr Exception; public static IntPtr StopIteration; #if (PYTHON25 || PYTHON26 || PYTHON27) public static IntPtr GeneratorExit; #endif public static IntPtr StandardError; public static IntPtr ArithmeticError; public static IntPtr LookupError; public static IntPtr AssertionError; public static IntPtr AttributeError; public static IntPtr EOFError; public static IntPtr FloatingPointError; public static IntPtr EnvironmentError; public static IntPtr IOError; public static IntPtr OSError; public static IntPtr ImportError; public static IntPtr IndexError; public static IntPtr KeyError; public static IntPtr KeyboardInterrupt; public static IntPtr MemoryError; public static IntPtr NameError; public static IntPtr OverflowError; public static IntPtr RuntimeError; public static IntPtr NotImplementedError; public static IntPtr SyntaxError; public static IntPtr IndentationError; public static IntPtr TabError; public static IntPtr ReferenceError; public static IntPtr SystemError; public static IntPtr SystemExit; public static IntPtr TypeError; public static IntPtr UnboundLocalError; public static IntPtr UnicodeError; public static IntPtr UnicodeEncodeError; public static IntPtr UnicodeDecodeError; public static IntPtr UnicodeTranslateError; public static IntPtr ValueError; public static IntPtr ZeroDivisionError; //#ifdef MS_WINDOWS //public static IntPtr WindowsError; //#endif //#ifdef __VMS //public static IntPtr VMSError; //#endif //PyAPI_DATA(PyObject *) PyExc_BufferError; //PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst; //PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst; /* Predefined warning categories */ public static IntPtr Warning; public static IntPtr UserWarning; public static IntPtr DeprecationWarning; public static IntPtr PendingDeprecationWarning; public static IntPtr SyntaxWarning; public static IntPtr RuntimeWarning; public static IntPtr FutureWarning; #if (PYTHON25 || PYTHON26 || PYTHON27) public static IntPtr ImportWarning; public static IntPtr UnicodeWarning; //PyAPI_DATA(PyObject *) PyExc_BytesWarning; #endif } }