Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Improve Python <-> .NET exception integration#1134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
lostmsu merged 42 commits intopythonnet:masterfromlosttech:PR/ExceptionsImprovement
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
42 commits
Select commitHold shift + click to select a range
663df73
reworked PythonException to simplify memory management, and enable .N…
lostmsuMay 5, 2020
d45c39a
allows codecs to encode and decode thrown exceptions
lostmsuFeb 25, 2020
0d941c5
turned on CLR exception marshaling
lostmsuMay 5, 2020
02cd234
revert to C# 7.3
lostmsuMay 5, 2020
defb200
fixed Restore when not all exception fields are set
lostmsuMay 5, 2020
945dc85
cleaned PythonException code up a bit
lostmsuMay 5, 2020
3d95e60
remove unused overloads of FromPyErr
lostmsuMay 5, 2020
d667998
capture exception on Exceptions.SetError, restore in ThrowLastAsClrEx…
lostmsuMay 5, 2020
bec8b7d
fixed failure in ExceptionEncoded test case caused by ExceptionDispat…
lostmsuMay 5, 2020
0961c94
added tests for __cause__/InnerException #893 #1098
lostmsuMay 6, 2020
2cd8627
introduced StolenReference type
lostmsuMay 14, 2020
e4c1b9b
prevent undebuggable StackOverflowException during exception interop
lostmsuMay 29, 2020
81cd197
Merge branch 'master' into PR/ExceptionsImprovement
lostmsuJun 22, 2020
3c0b737
README: added summary of new exception handling features
lostmsuJun 22, 2020
257a765
merge latest master
lostmsuApr 9, 2021
c0fe430
reworked `PythonException`:
lostmsuApr 9, 2021
e58411d
rum embedding tests before Python tests
lostmsuApr 9, 2021
00653dc
PythonException.StackTrace is GIL-safe
lostmsuApr 9, 2021
3433201
separate .Steal() and .StealNullable()
lostmsuApr 11, 2021
95cc52f
can't test exception type when runtime is down
lostmsuApr 11, 2021
63ad42c
PythonException: dispose intermediate values used in stack trace gene…
lostmsuApr 11, 2021
faec7fc
Point users to Message property of PythonException
lostmsuApr 11, 2021
dfc70f6
minor change in PythonEngine.With
lostmsuApr 11, 2021
d976acf
simplified code of PythonException and added a lot more checks
lostmsuApr 11, 2021
146ebf3
fixed type of reference in PyException_SetCause
lostmsuApr 11, 2021
272687b
minor fixes to Converter.ToArray:
lostmsuApr 11, 2021
2cd3f61
added a few debug checks to Exceptions.SetError
lostmsuApr 11, 2021
e79f041
method binding failure now supports non-Python exception causes
lostmsuApr 11, 2021
d068f36
XDecref now checks, that refcount is positive in debug builds
lostmsuApr 11, 2021
4877fe7
fixed __cause__ on overload bind failure and array conversion
lostmsuApr 11, 2021
ed594c1
cache PythonException message
lostmsuApr 11, 2021
e5bce06
minor code cleanup
lostmsuApr 11, 2021
6819e7b
improved handling of dict offset in object instances
lostmsuApr 11, 2021
6679d1c
added a few debug checks
lostmsuApr 11, 2021
67bd1c2
merge latest master
lostmsuMay 15, 2021
2e57b04
+ class diagram for ManagedType and subclasses
lostmsuMay 23, 2021
539ce81
added OverloadMapper to ManagedTypes class diagram
lostmsuMay 23, 2021
25e3864
refactored tp_dealloc in ExtensionType and descendants
lostmsuMay 23, 2021
5bca333
refactored tp_clear in ExtensionType and descendants into a virtual C…
lostmsuMay 23, 2021
7271d88
all Dealloc overrides simply duplicate Clear, so just call both from …
lostmsuMay 23, 2021
4f3f648
fixup! merge latest master
SDEIIMay 29, 2021
c500a39
fixup! reworked `PythonException`:
lostmsuMay 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions.github/workflows/main.yml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,9 @@ jobs:
run: |
python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- name: Embedding tests
run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/

- name: Python Tests (Mono)
if: ${{ matrix.os != 'windows' }}
run: pytest --runtime mono
Expand All@@ -67,9 +70,6 @@ jobs:
if: ${{ matrix.os == 'windows' }}
run: pytest --runtime netfx

- name: Embedding tests
run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/

- name: Python tests run from .NET
run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/

Expand Down
6 changes: 6 additions & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Add GetPythonThreadID and Interrupt methods in PythonEngine
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`
- Improved exception handling:
- exceptions can now be converted with codecs
- `InnerException` and `__cause__` are propagated properly

### Changed
- Drop support for Python 2, 3.4, and 3.5
Expand DownExpand Up@@ -44,7 +47,9 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru
- Sign Runtime DLL with a strong name
- Implement loading through `clr_loader` instead of the included `ClrModule`, enables
support for .NET Core
- .NET and Python exceptions are preserved when crossing Python/.NET boundary
- BREAKING: custom encoders are no longer called for instances of `System.Type`
- `PythonException.Restore` no longer clears `PythonException` instance.

### Fixed

Expand All@@ -70,6 +75,7 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru
### Removed

- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import)
- messages in `PythonException` no longer start with exception type
- support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0
(see [the matrix](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support))

Expand Down
59 changes: 59 additions & 0 deletionssrc/embed_tests/Codecs.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -322,6 +322,65 @@ def CanEncode(self, clr_type):

PythonEngine.Exec(PyCode);
}

const string TestExceptionMessage = "Hello World!";
[Test]
public void ExceptionEncoded()
{
PyObjectConversions.RegisterEncoder(new ValueErrorCodec());
void CallMe() => throw new ValueErrorWrapper(TestExceptionMessage);
var callMeAction = new Action(CallMe);
using var _ = Py.GIL();
using var scope = Py.CreateScope();
scope.Exec(@"
def call(func):
try:
func()
except ValueError as e:
return str(e)
");
var callFunc = scope.Get("call");
string message = callFunc.Invoke(callMeAction.ToPython()).As<string>();
Assert.AreEqual(TestExceptionMessage, message);
}

[Test]
public void ExceptionDecoded()
{
PyObjectConversions.RegisterDecoder(new ValueErrorCodec());
using var _ = Py.GIL();
using var scope = Py.CreateScope();
var error = Assert.Throws<ValueErrorWrapper>(()
=> PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')"));
Assert.AreEqual(TestExceptionMessage, error.Message);
}

class ValueErrorWrapper : Exception
{
public ValueErrorWrapper(string message) : base(message) { }
}

class ValueErrorCodec : IPyObjectEncoder, IPyObjectDecoder
{
public bool CanDecode(PyObject objectType, Type targetType)
=> this.CanEncode(targetType) && objectType.Equals(PythonEngine.Eval("ValueError"));

public bool CanEncode(Type type) => type == typeof(ValueErrorWrapper)
|| typeof(ValueErrorWrapper).IsSubclassOf(type);

public bool TryDecode<T>(PyObject pyObj, out T value)
{
var message = pyObj.GetAttr("args")[0].As<string>();
value = (T)(object)new ValueErrorWrapper(message);
return true;
}

public PyObject TryEncode(object value)
{
var error = (ValueErrorWrapper)value;
return PythonEngine.Eval("ValueError").Invoke(error.Message.ToPython());
}
}
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletionsrc/embed_tests/TestCallbacks.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@ public void TestNoOverloadException() {
using (Py.GIL()) {
dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])");
var error = Assert.Throws<PythonException>(() => callWith42(aFunctionThatCallsIntoPython.ToPython()));
Assert.AreEqual("TypeError", error.PythonTypeName);
Assert.AreEqual("TypeError", error.Type.Name);
string expectedArgTypes = "(<class 'list'>)";
StringAssert.EndsWith(expectedArgTypes, error.Message);
}
Expand Down
4 changes: 2 additions & 2 deletionssrc/embed_tests/TestPyFloat.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,7 +95,7 @@ public void StringBadCtor()

var ex = Assert.Throws<PythonException>(() => a = new PyFloat(i));

StringAssert.StartsWith("ValueError :could not convert string to float", ex.Message);
StringAssert.StartsWith("could not convert string to float", ex.Message);
Assert.IsNull(a);
}

Expand DownExpand Up@@ -132,7 +132,7 @@ public void AsFloatBad()
PyFloat a = null;

var ex = Assert.Throws<PythonException>(() => a = PyFloat.AsFloat(s));
StringAssert.StartsWith("ValueError :could not convert string to float", ex.Message);
StringAssert.StartsWith("could not convert string to float", ex.Message);
Assert.IsNull(a);
}
}
Expand Down
4 changes: 2 additions & 2 deletionssrc/embed_tests/TestPyInt.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,7 +128,7 @@ public void TestCtorBadString()

var ex = Assert.Throws<PythonException>(() => a = new PyInt(i));

StringAssert.StartsWith("ValueError :invalid literal for int", ex.Message);
StringAssert.StartsWith("invalid literal for int", ex.Message);
Assert.IsNull(a);
}

Expand DownExpand Up@@ -161,7 +161,7 @@ public void TestAsIntBad()
PyInt a = null;

var ex = Assert.Throws<PythonException>(() => a = PyInt.AsInt(s));
StringAssert.StartsWith("ValueError :invalid literal for int", ex.Message);
StringAssert.StartsWith("invalid literal for int", ex.Message);
Assert.IsNull(a);
}

Expand Down
2 changes: 1 addition & 1 deletionsrc/embed_tests/TestPyList.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ public void TestStringAsListType()

var ex = Assert.Throws<PythonException>(() => t = PyList.AsList(i));

Assert.AreEqual("TypeError :'int' object is not iterable", ex.Message);
Assert.AreEqual("'int' object is not iterable", ex.Message);
Assert.IsNull(t);
}

Expand Down
4 changes: 2 additions & 2 deletionssrc/embed_tests/TestPyLong.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -144,7 +144,7 @@ public void TestCtorBadString()

var ex = Assert.Throws<PythonException>(() => a = new PyLong(i));

StringAssert.StartsWith("ValueError :invalid literal", ex.Message);
StringAssert.StartsWith("invalid literal", ex.Message);
Assert.IsNull(a);
}

Expand DownExpand Up@@ -177,7 +177,7 @@ public void TestAsLongBad()
PyLong a = null;

var ex = Assert.Throws<PythonException>(() => a = PyLong.AsLong(s));
StringAssert.StartsWith("ValueError :invalid literal", ex.Message);
StringAssert.StartsWith("invalid literal", ex.Message);
Assert.IsNull(a);
}

Expand Down
4 changes: 2 additions & 2 deletionssrc/embed_tests/TestPyTuple.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -104,7 +104,7 @@ public void TestPyTupleInvalidAppend()

var ex = Assert.Throws<PythonException>(() => t.Concat(s));

StringAssert.StartsWith("TypeError :can only concatenate tuple", ex.Message);
StringAssert.StartsWith("can only concatenate tuple", ex.Message);
Assert.AreEqual(0, t.Length());
Assert.IsEmpty(t);
}
Expand DownExpand Up@@ -164,7 +164,7 @@ public void TestInvalidAsTuple()

var ex = Assert.Throws<PythonException>(() => t = PyTuple.AsTuple(i));

Assert.AreEqual("TypeError :'int' object is not iterable", ex.Message);
Assert.AreEqual("'int' object is not iterable", ex.Message);
Assert.IsNull(t);
}
}
Expand Down
1 change: 1 addition & 0 deletionssrc/embed_tests/TestPyType.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ public void CanCreateHeapType()

using var type = new PyType(spec);
Assert.AreEqual(name, type.GetAttr("__name__").As<string>());
Assert.AreEqual(name, type.Name);
Assert.AreEqual(docStr, type.GetAttr("__doc__").As<string>());
}
}
Expand Down
2 changes: 1 addition & 1 deletionsrc/embed_tests/TestPyWith.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,7 @@ def fail(self):
catch (PythonException e)
{
TestContext.Out.WriteLine(e.Message);
Assert.IsTrue(e.Message.Contains("ZeroDivisionError"));
Assert.IsTrue(e.Type.Name =="ZeroDivisionError");
}
}

Expand Down
78 changes: 49 additions & 29 deletionssrc/embed_tests/TestPythonException.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,31 +30,61 @@ public void TestMessage()

var ex = Assert.Throws<PythonException>(() => foo = list[0]);

Assert.AreEqual("IndexError : list index out of range", ex.Message);
Assert.AreEqual("list index out of range", ex.Message);
Assert.IsNull(foo);
}

[Test]
public void TestType()
{
var list = new PyList();
PyObject foo = null;

var ex = Assert.Throws<PythonException>(() => foo = list[0]);

Assert.AreEqual("IndexError", ex.Type.Name);
Assert.IsNull(foo);
}

[Test]
public void TestNoError()
{
var e = new PythonException(); // There is no PyErr to fetch
Assert.AreEqual("", e.Message);
// There is no PyErr to fetch
Assert.Throws<InvalidOperationException>(() => PythonException.FetchCurrentRaw());
var currentError = PythonException.FetchCurrentOrNullRaw();
Assert.IsNull(currentError);
}

[Test]
public voidTestPythonErrorTypeName()
public voidTestNestedExceptions()
{
try
{
var module = PyModule.Import("really____unknown___module");
Assert.Fail("Unknown module should not be loaded");
PythonEngine.Exec(@"
try:
raise Exception('inner')
except Exception as ex:
raise Exception('outer') from ex
");
}
catch (PythonException ex)
{
Assert.That(ex.PythonTypeName, Is.EqualTo("ModuleNotFoundError").Or.EqualTo("ImportError"));
Assert.That(ex.InnerException, Is.InstanceOf<PythonException>());
Assert.That(ex.InnerException.Message, Is.EqualTo("inner"));
}
}

[Test]
public void InnerIsEmptyWithNoCause()
{
var list = new PyList();
PyObject foo = null;

var ex = Assert.Throws<PythonException>(() => foo = list[0]);

Assert.IsNull(ex.InnerException);
}

[Test]
public void TestPythonExceptionFormat()
{
Expand DownExpand Up@@ -83,13 +113,6 @@ public void TestPythonExceptionFormat()
}
}

[Test]
public void TestPythonExceptionFormatNoError()
{
var ex = new PythonException();
Assert.AreEqual(ex.StackTrace, ex.Format());
}

[Test]
public void TestPythonExceptionFormatNoTraceback()
{
Expand DownExpand Up@@ -132,30 +155,27 @@ def __init__(self, val):
Assert.IsTrue(scope.TryGet("TestException", out PyObject type));

PyObject str = "dummy string".ToPython();
IntPtr typePtr = type.Handle;
IntPtr strPtr = str.Handle;
IntPtr tbPtr = Runtime.Runtime.None.Handle;
Runtime.Runtime.XIncref(typePtr);
Runtime.Runtime.XIncref(strPtr);
Runtime.Runtime.XIncref(tbPtr);
var typePtr = new NewReference(type.Reference);
var strPtr = new NewReference(str.Reference);
var tbPtr = new NewReference(Runtime.Runtime.None.Reference);
Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr);

using(PyObject typeObj =new PyObject(typePtr), strObj = new PyObject(strPtr), tbObj = new PyObject(tbPtr))
{
// the type returned from PyErr_NormalizeException should not be the same type since a new
// exception was raised by initializingtheexception
Assert.AreNotEqual(type.Handle, typePtr);
// the message should now be the string from the throw exception during normalization
Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString());
}
usingvar typeObj = typePtr.MoveToPyObject();
using var strObj = strPtr.MoveToPyObject();
using var tbObj = tbPtr.MoveToPyObject();
// the type returned from PyErr_NormalizeException should not bethesame type since a new
// exception was raised by initializing the exception
Assert.AreNotEqual(type.Handle, typeObj.Handle);
// the message should now be the string from the throw exception during normalization
Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString());
}
}

[Test]
public void TestPythonException_Normalize_ThrowsWhenErrorSet()
{
Exceptions.SetError(Exceptions.TypeError, "Error!");
var pythonException =newPythonException();
var pythonException = PythonException.FetchCurrentRaw();
Exceptions.SetError(Exceptions.TypeError, "Another error");
Assert.Throws<InvalidOperationException>(() => pythonException.Normalize());
}
Expand Down
3 changes: 1 addition & 2 deletionssrc/embed_tests/TestRuntime.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@
using System.Collections.Generic;
using NUnit.Framework;
using Python.Runtime;
using Python.Runtime.Platform;

namespace Python.EmbeddingTest
{
Expand DownExpand Up@@ -102,7 +101,7 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test()
Exceptions.ErrorCheck(threadingDict);
var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock");
if (lockType.IsNull)
thrownewPythonException();
throw PythonException.ThrowLastAsClrException();

using var args = NewReference.DangerousFromPointer(Runtime.Runtime.PyTuple_New(0));
using var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, args);
Expand Down
3 changes: 1 addition & 2 deletionssrc/embed_tests/pyimport.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,8 +102,7 @@ import clr
clr.AddReference('{path}')
";

var error = Assert.Throws<PythonException>(() => PythonEngine.Exec(code));
Assert.AreEqual(nameof(FileLoadException), error.PythonTypeName);
Assert.Throws<FileLoadException>(() => PythonEngine.Exec(code));
}
}
}
3 changes: 2 additions & 1 deletionsrc/embed_tests/pyinitialize.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -158,9 +158,10 @@ public static void TestRunExitFuncs()
catch (PythonException e)
{
string msg = e.ToString();
bool isImportError = e.Is(Exceptions.ImportError);
Runtime.Runtime.Shutdown();

if (e.IsMatches(Exceptions.ImportError))
if (isImportError)
{
Assert.Ignore("no atexit module");
}
Expand Down
2 changes: 2 additions & 0 deletionssrc/runtime/BorrowedReference.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,8 @@ public IntPtr DangerousGetAddress()
/// <summary>Gets a raw pointer to the Python object</summary>
public IntPtr DangerousGetAddressOrNull() => this.pointer;

public static BorrowedReference Null => new BorrowedReference();

/// <summary>
/// Creates new instance of <see cref="BorrowedReference"/> from raw pointer. Unsafe.
/// </summary>
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp