diff --git a/nimpy.nim b/nimpy.nim index 3053924b6..52bf3fb72 100644 --- a/nimpy.nim +++ b/nimpy.nim @@ -235,8 +235,7 @@ proc updateStackBottom() {.inline.} = setupForeignThreadGC() proc pythonException(e: ref Exception): PPyObject = - let err = pyLib.PyErr_NewException(cstring("nimpy" & "." & $(e.name)), pyLib.NimPyException, nil) - decRef err + let err = nimValueToPy(e) let errMsg: string = when compileOption("stackTrace"): "Unexpected error encountered: " & e.msg & "\nstack trace: (most recent call last)\n" & e.getStackTrace() diff --git a/nimpy/nim_py_marshalling.nim b/nimpy/nim_py_marshalling.nim index 781923592..eb79c4e4a 100644 --- a/nimpy/nim_py_marshalling.nim +++ b/nimpy/nim_py_marshalling.nim @@ -151,3 +151,38 @@ proc nimValueToPy*[T: tuple](o: T): PPyObject = for f in fields(o): discard pyLib.PyTuple_SetItem(result, i, nimValueToPy(f)) inc i + +proc nimValueToPy*(e: ref Exception): PPyObject = + let pyExcName = cstring("nimpy" & "." & $(e.name)) + var pyExc: PPyObject + if e of AssertionDefect: + pyExc = pyLib.PyExc_AssertionError + elif e of EOFError: + pyExc = pyLib.PyExc_EOFError + elif e of LibraryError: + pyExc = pyLib.PyExc_ImportError + elif e of IndexDefect: + pyExc = pyLib.PyExc_IndexError + elif e of IOError: + pyExc = pyLib.PyExc_IOError + elif e of KeyError: + pyExc = pyLib.PyExc_KeyError + elif e of ObjectConversionDefect: + pyExc = pyLib.PyExc_TypeError + elif e of StackOverflowDefect: + pyExc = pyLib.PyExc_RecursionError + elif e of DivByZeroDefect or e of FloatDivByZeroDefect: + pyExc = pyLib.PyExc_ZeroDivisionError + elif e of FloatingPointDefect: + pyExc = pyLib.PyExc_FloatingPointError + elif e of OsError: + pyExc = pyLib.PyExc_OSError + elif e of OutOfMemDefect: + pyExc = pyLib.PyExc_MemoryError + else: + return pyLib.PyErr_NewException(pyExcName, pyLib.NimPyException, nil) + decRef pyExc + let nimpyExceptionSubclass = pyLib.PyTuple_Pack(2, pyLib.NimPyException, pyExc) + result = pyLib.PyErr_NewException(pyExcName, nimpyExceptionSubclass, nil) + decref nimpyExceptionSubclass + decref result \ No newline at end of file diff --git a/nimpy/py_lib.nim b/nimpy/py_lib.nim index c0a35f9c7..8ce26f8ec 100644 --- a/nimpy/py_lib.nim +++ b/nimpy/py_lib.nim @@ -8,6 +8,7 @@ type Py_BuildValue*: proc(f: cstring): PPyObject {.pyfunc, varargs.} PyTuple_New*: proc(sz: Py_ssize_t): PPyObject {.pyfunc.} + PyTuple_Pack*: proc(sz: Py_ssize_t): PPyObject {.pyfunc, varargs.} PyTuple_Size*: proc(f: PPyObject): Py_ssize_t {.pyfunc.} PyTuple_GetItem*: proc(f: PPyObject, i: Py_ssize_t): PPyObject {.pyfunc.} PyTuple_SetItem*: proc(f: PPyObject, i: Py_ssize_t, v: PPyObject): cint {.pyfunc.} @@ -90,7 +91,6 @@ type PyErr_Clear*: proc() {.pyfunc.} PyErr_SetString*: proc(o: PPyObject, s: cstring) {.pyfunc.} PyErr_Occurred*: proc(): PPyObject {.pyfunc.} - PyExc_TypeError*: PPyObject PyCapsule_New*: proc(p: pointer, name: cstring, destr: proc(o: PPyObject) {.pyfunc.}): PPyObject {.pyfunc.} PyCapsule_GetPointer*: proc(c: PPyObject, name: cstring): pointer {.pyfunc.} @@ -114,17 +114,57 @@ type PyExc_BaseException*: PPyObject # should always match any exception? PyExc_Exception*: PPyObject PyExc_ArithmeticError*: PPyObject - PyExc_FloatingPointError*: PPyObject - PyExc_OverflowError*: PPyObject - PyExc_ZeroDivisionError*: PPyObject PyExc_AssertionError*: PPyObject - PyExc_OSError*: PPyObject - PyExc_IOError*: PPyObject # in Python 3 IOError *is* OSError - PyExc_ValueError*: PPyObject + #PyExc_AttributeError*: PPyObject + # PyExc_BlockingIOError # no + # PyExc_BrokenPipeError + # PyExc_BufferError + # PyExc_ChildProcessError + # PyExc_ConnectionAbortedError + # PyExc_ConnectionError + # PyExc_ConnectionRefusedError + # PyExc_ConnectionResetError PyExc_EOFError*: PPyObject - PyExc_MemoryError*: PPyObject + # PyExc_FileExistsError + # PyExc_FileNotFoundError + PyExc_FloatingPointError*: PPyObject + # PyExc_GeneratorExit + PyExc_ImportError*: PPyObject + # PyExc_IndentationError # no PyExc_IndexError*: PPyObject + # PyExc_InterruptedError + PyExc_IOError*: PPyObject # in Python 3 IOError *is* OSError + # PyExc_IsADirectoryError PyExc_KeyError*: PPyObject + # PyExc_KeyboardInterrupt + # PyExc_LookupError + PyExc_MemoryError*: PPyObject + # PyExc_ModuleNotFoundError + # PyExc_NameError + # PyExc_NotADirectoryError + # PyExc_NotImplementedError + PyExc_OSError*: PPyObject + PyExc_OverflowError*: PPyObject + # PyExc_PermissionError + # PyExc_ProcessLookupError + PyExc_RecursionError*: PPyObject + # PyExc_ReferenceError + # PyExc_RuntimeError + # PyExc_StopAsyncIteration + # PyExc_StopIteration + # PyExc_SyntaxError + # PyExc_SystemError + # PyExc_SystemExit + # PyExc_TabError + # PyExc_TimeoutError + PyExc_TypeError*: PPyObject + # PyExc_UnboundLocalError + # PyExc_UnicodeDecodeError + # PyExc_UnicodeEncodeError + # PyExc_UnicodeError + # PyExc_UnicodeTranslateError + PyExc_ValueError*: PPyObject + PyExc_ZeroDivisionError*: PPyObject NimPyException*: PPyObject @@ -212,6 +252,7 @@ proc loadPyLibFromModule(m: LibHandle): PyLib = load Py_BuildValue, "_Py_BuildValue_SizeT" load PyTuple_New + load PyTuple_Pack load PyTuple_Size load PyTuple_GetItem load PyTuple_SetItem @@ -327,17 +368,20 @@ proc loadPyLibFromModule(m: LibHandle): PyLib = load PyErr_NewException loadVar PyExc_ArithmeticError - loadVar PyExc_FloatingPointError - loadVar PyExc_OverflowError - loadVar PyExc_ZeroDivisionError loadVar PyExc_AssertionError - loadVar PyExc_OSError - loadVar PyExc_IOError - loadVar PyExc_ValueError loadVar PyExc_EOFError - loadVar PyExc_MemoryError + loadVar PyExc_FloatingPointError + loadVar PyExc_ImportError loadVar PyExc_IndexError + loadVar PyExc_IOError loadVar PyExc_KeyError + loadVar PyExc_MemoryError + loadVar PyExc_OSError + loadVar PyExc_OverflowError + loadVar PyExc_RecursionError + loadVar PyExc_TypeError + loadVar PyExc_ValueError + loadVar PyExc_ZeroDivisionError if pl.pythonVersion.major == 3: pl.PyDealloc = deallocPythonObj[PyTypeObject3] diff --git a/nimpy/py_types.nim b/nimpy/py_types.nim index 4481fb501..29fd6f659 100644 --- a/nimpy/py_types.nim +++ b/nimpy/py_types.nim @@ -307,19 +307,19 @@ type # the string value corresponds to the Python Exception # while the enum identifier corresponds to the Nim exception (excl. "pe") PythonErrorKind* = enum - peException = "Exception" # general exception, if no equivalent Nim Exception - peArithmeticError = "ArithmeticError" - peFloatingPointError = "FloatingPointError" - peOverflowError = "OverflowError" - peDivByZeroError = "ZeroDivisionError" - peAssertionError = "AssertionError" + peCatchableError = "Exception" # general exception, if no equivalent Nim Exception + peArithmeticDefect = "ArithmeticError" + peFloatingPointDefect = "FloatingPointError" + peOverflowDefect = "OverflowError" + peDivByZeroDefect = "ZeroDivisionError" + peAssertionDefect = "AssertionError" peOSError = "OSError" peIOError = "IOError" peValueError = "ValueError" peEOFError = "EOFError" - peOutOfMemError = "MemoryError" + peOutOfMemDefect = "MemoryError" peKeyError = "KeyError" - peIndexError = "IndexError" + peIndexDefect = "IndexError" const # PyBufferProcs contains bf_getcharbuffer diff --git a/tests/nimfrompy.nim b/tests/nimfrompy.nim index 44158113a..363703993 100644 --- a/tests/nimfrompy.nim +++ b/tests/nimfrompy.nim @@ -1,12 +1,12 @@ import nimpy -import algorithm, complex, tables, json +import algorithm, complex, dynlib, tables, json from tpyfromnim import nil import modules/other_module type - JackError* = object of Exception + JackError* = object of CatchableError proc greet(name: string, greeting: string="Hello", suffix: string="!"): string {.exportpy.} = @@ -165,3 +165,61 @@ proc setMyFieldFromTt(self: AnotherTestType, value: TestType) {.exportpy.} = proc getMyField(self: AnotherTestType): int {.exportpy.} = self.myIntField + +# Raising Defects + +proc assertFalse(): void {.exportpy} = + # AssertionDefect + doAssert false + +proc invalidIndex(): int {.exportpy} = + # IndexDefect + let mySequence = @[1, 2, 3] + mySequence[4] + +proc endOfFile(): char {.exportpy} = + # EOFError + let file = open("/dev/null", fmRead) + readChar(file) + +proc readImpossibleFile(): void {.exportpy} = + # IOError + discard open("/dev/null/impossible", fmRead) + +proc invalidKey(): string {.exportpy} = + # KeyError + let myTable = {1: "one", 2: "two"}.toTable + myTable[3] + +proc invalidObjectConversion(): void {.exportpy} = + # ObjectConversionDefect + raise newException(ObjectConversionDefect, "Generic ObjectConversionDefect") + +proc intDivideByZero(): int {.exportpy} = + # DivByZeroDefect + 1 mod 0 + +proc floatDivideByZero(): float {.exportpy} = + # FloatDivByZeroDefect + raise newException(FloatDivByZeroDefect, "Generic FloatDivByZeroDefect") + +proc genericFloatingPointDefect(): float {.exportpy} = + # FloatOverflowDefect of FloatingPointDefect + 1 / 0 + +proc readFakeLibrary(): void {.exportpy} = + # LibraryError + discard checkedSymAddr(nil, "fake_library") + +proc stackOverflow(): void {.exportpy} = + # StackOverflowDefect + raise newException(StackOverflowDefect, "Generic StackOverflowDefect") + +proc osError(): void {.exportpy} = + # OSError + raise newException(OSError, "Generic OSError") + +proc outOfMemory(): void {.exportpy} = + # OutOfMemDefect + raise newException(OutOfMemDefect, "Generic OutOfMemDefect") + diff --git a/tests/tnimfrompy.py b/tests/tnimfrompy.py index c8fed5d19..fadc2e259 100644 --- a/tests/tnimfrompy.py +++ b/tests/tnimfrompy.py @@ -40,6 +40,75 @@ expected = "TypeError(\"Can't convert python obj of type 'int' to string\",)" assert(expected[:-2] in repr(e)) +# Generic exception raising +try: + s.assertFalse() +except AssertionError as e: + assert(isinstance(e, s.NimPyException)) + assert("`false`" in repr(e)) +else: + assert(False) +try: + s.endOfFile(); assert(False) +except EOFError as e: + assert(isinstance(e, s.NimPyException)) + assert("EOF reached" in repr(e)) +try: + s.invalidIndex(); assert(False) +except IndexError as e: + assert(isinstance(e, s.NimPyException)) + assert("index 4 not in 0" in repr(e)) +try: + s.readImpossibleFile(); assert(False) +except IOError as e: + assert(isinstance(e, s.NimPyException)) + assert("/dev/null/impossible" in repr(e)) +try: + s.invalidKey(); assert(False) +except KeyError as e: + assert(isinstance(e, s.NimPyException)) + assert("key not found" in repr(e)) +try: + x = s.invalidObjectConversion(); assert(False) +except TypeError as e: + assert(isinstance(e, s.NimPyException)) + assert("Generic ObjectConversionDefect" in repr(e)) +try: + s.intDivideByZero(); assert(False) +except ZeroDivisionError as e: + assert(isinstance(e, s.NimPyException)) + assert("division by zero" in repr(e)) +try: + x = s.floatDivideByZero(); assert(False) +except ZeroDivisionError as e: + assert(isinstance(e, s.NimPyException)) + assert("Generic FloatDivByZeroDefect" in repr(e)) +try: + s.genericFloatingPointDefect(); assert(False) +except FloatingPointError as e: + assert(isinstance(e, s.NimPyException)) + assert("FPU operation caused an overflow" in repr(e)) +try: + s.readFakeLibrary(); assert(False) +except ImportError as e: + assert(isinstance(e, s.NimPyException)) + assert("could not find symbol: fake_library" in repr(e)) +try: + s.stackOverflow(); assert(False) +except RecursionError as e: + assert(isinstance(e, s.NimPyException)) + assert("Generic StackOverflowDefect" in repr(e)) +try: + s.osError(); assert(False) +except OSError as e: + assert(isinstance(e, s.NimPyException)) + assert("Generic OSError" in repr(e)) +try: + s.outOfMemory(); assert(False) +except MemoryError as e: + assert(isinstance(e, s.NimPyException)) + assert("Generic OutOfMemDefect" in repr(e)) + assert(s.greetEveryoneExceptJack("world") == "Hello, world!") try: s.greetEveryoneExceptJack("Jack") diff --git a/tests/tpyfromnim.nim b/tests/tpyfromnim.nim index a6784198d..e722042d2 100644 --- a/tests/tpyfromnim.nim +++ b/tests/tpyfromnim.nim @@ -177,16 +177,16 @@ proc test*() {.gcsafe.} = check(pfn.testValueError, ValueError) check(pfn.testKeyError, KeyError) check(pfn.testEOFError, EOFError) - check(pfn.testArithmeticError,ArithmeticError) - check(pfn.testZeroDivisionError, DivByZeroError) - check(pfn.testOverflowError, OverflowError) - check(pfn.testAssertionError, AssertionError) - check(pfn.testMemoryError, OutOfMemError) - check(pfn.testIndexError, IndexError) - check(pfn.testFloatingPointError, FloatingPointError) + check(pfn.testArithmeticError,ArithmeticDefect) + check(pfn.testZeroDivisionError, DivByZeroDefect) + check(pfn.testOverflowError, OverflowDefect) + check(pfn.testAssertionError, AssertionDefect) + check(pfn.testMemoryError, OutOfMemDefect) + check(pfn.testIndexError, IndexDefect) + check(pfn.testFloatingPointError, FloatingPointDefect) check(pfn.testException, Exception) check(pfn.testUnsupportedException, Exception) - check(pfn.testCustomException, IndexError) + check(pfn.testCustomException, IndexDefect) block: # Function objects let pfn = pyImport("pyfromnim")