From 5e041afadbb119bd2816bd483adf62a347b9c639 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 4 Mar 2022 10:09:25 -0800 Subject: [PATCH 001/183] on runtime shutdown from Python release all slot holders, not only the ones, that belong to managed types --- src/runtime/TypeManager.cs | 16 ++++++++++------ src/runtime/Types/ModuleObject.cs | 1 - tests/test_engine.py | 4 ++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 6057ca830..4b1e28fc2 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -51,18 +51,21 @@ internal static void Initialize() internal static void RemoveTypes() { - foreach (var type in cache.Values) + if (Runtime.HostedInPython) { - if (Runtime.HostedInPython - && _slotsHolders.TryGetValue(type, out var holder)) + foreach (var holder in _slotsHolders) { // If refcount > 1, it needs to reset the managed slot, // otherwise it can dealloc without any trick. - if (Runtime.Refcount(type) > 1) + if (holder.Key.Refcount > 1) { - holder.ResetSlots(); + holder.Value.ResetSlots(); } } + } + + foreach (var type in cache.Values) + { type.Dispose(); } cache.Clear(); @@ -507,7 +510,7 @@ internal static PyType CreateMetaType(Type impl, out SlotsHolder slotsHolder) { throw PythonException.ThrowLastAsClrException(); } - + BorrowedReference dict = Util.ReadRef(type, TypeOffset.tp_dict); using (var mod = Runtime.PyString_FromString("clr._internal")) Runtime.PyDict_SetItemString(dict, "__module__", mod.Borrow()); @@ -726,6 +729,7 @@ internal static void CopySlot(BorrowedReference from, BorrowedReference to, int internal static SlotsHolder CreateSlotsHolder(PyType type) { + type = new PyType(type); var holder = new SlotsHolder(type); _slotsHolders.Add(type, holder); return holder; diff --git a/src/runtime/Types/ModuleObject.cs b/src/runtime/Types/ModuleObject.cs index 1e86d4472..1cc9f04b2 100644 --- a/src/runtime/Types/ModuleObject.cs +++ b/src/runtime/Types/ModuleObject.cs @@ -542,7 +542,6 @@ public static Assembly AddReference(string name) /// The Type object [ModuleFunction] - [ForbidPythonThreads] public static Type GetClrType(Type type) { return type; diff --git a/tests/test_engine.py b/tests/test_engine.py index 60fdbf45d..06a44d561 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -41,3 +41,7 @@ def test_run_string(): assert sys.multiline_worked == 1 PythonEngine.ReleaseLock() + +def test_leak_type(): + import clr + sys._leaked_intptr = clr.GetClrType(System.IntPtr) From e9cbb87073e75500b3392ad057c06a132fb5ed3c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 7 Apr 2022 15:55:58 -0700 Subject: [PATCH 002/183] do not attempt to manually delete derived class instances after TypeManager is terminated and Python is shutting down --- pythonnet.sln | 1 + src/runtime/Runtime.cs | 9 ++++++--- src/runtime/TypeManager.cs | 1 + src/runtime/Types/ClassDerived.cs | 20 ++++++++++---------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/pythonnet.sln b/pythonnet.sln index 3b509518f..eb97cfbd0 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" ProjectSection(SolutionItems) = preProject + .github\workflows\ARM.yml = .github\workflows\ARM.yml .github\workflows\main.yml = .github\workflows\main.yml .github\workflows\nuget-preview.yml = .github\workflows\nuget-preview.yml EndProjectSection diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index e33c4624c..e358c0135 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -54,8 +54,9 @@ private static string GetDefaultDllName(Version version) } private static bool _isInitialized = false; - internal static bool IsInitialized => _isInitialized; + private static bool _typesInitialized = false; + internal static bool TypeManagerInitialized => _typesInitialized; internal static readonly bool Is32Bit = IntPtr.Size == 4; // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) @@ -151,6 +152,7 @@ internal static void Initialize(bool initSigs = false) ClassManager.Reset(); ClassDerivedObject.Reset(); TypeManager.Initialize(); + _typesInitialized = true; // Initialize modules that depend on the runtime class. AssemblyManager.Initialize(); @@ -272,6 +274,7 @@ internal static void Shutdown() NullGCHandles(ExtensionType.loadedExtensions); ClassManager.RemoveClasses(); TypeManager.RemoveTypes(); + _typesInitialized = false; MetaType.Release(); PyCLRMetaType.Dispose(); @@ -291,9 +294,10 @@ internal static void Shutdown() Finalizer.Shutdown(); InternString.Shutdown(); + ResetPyMembers(); + if (!HostedInPython) { - ResetPyMembers(); GC.Collect(); GC.WaitForPendingFinalizers(); PyGILState_Release(state); @@ -310,7 +314,6 @@ internal static void Shutdown() } else { - ResetPyMembers(); PyGILState_Release(state); } } diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 4b1e28fc2..84618df64 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -832,6 +832,7 @@ public void ResetSlots() var metatype = Runtime.PyObject_TYPE(Type); ManagedType.TryFreeGCHandle(Type, metatype); } + Runtime.PyType_Modified(Type); } public static IntPtr GetDefaultSlot(int offset) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index da1bf0f9a..6c2c81b13 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -59,10 +59,7 @@ protected override NewReference NewObjectToPython(object obj, BorrowedReference // Decrement the python object's reference count. // This doesn't actually destroy the object, it just sets the reference to this object // to be a weak reference and it will be destroyed when the C# object is destroyed. - if (!self.IsNull()) - { - Runtime.XDecref(self.Steal()); - } + Runtime.XDecref(self.Steal()); return Converter.ToPython(obj, type.Value); } @@ -942,13 +939,16 @@ internal static void Finalize(IntPtr derived) var type = Runtime.PyObject_TYPE(@ref.Borrow()); - // rare case when it's needed - // matches correspdonging PyObject_GC_UnTrack - // in ClassDerivedObject.tp_dealloc - Runtime.PyObject_GC_Del(@ref.Steal()); + if (!Runtime.HostedInPython || Runtime.TypeManagerInitialized) + { + // rare case when it's needed + // matches correspdonging PyObject_GC_UnTrack + // in ClassDerivedObject.tp_dealloc + Runtime.PyObject_GC_Del(@ref.Steal()); - // must decref our type - Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress())); + // must decref our type + Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress())); + } } internal static FieldInfo? GetPyObjField(Type type) => type.GetField(PyObjName, PyObjFlags); From 0e57cdd4c5f0200523ec9130fd6d869ed84cfa72 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 8 Apr 2022 03:21:22 -0700 Subject: [PATCH 003/183] support for BigInteger <-> PyInt (#1710) fixes https://github.com/pythonnet/pythonnet/issues/1642#issuecomment-1058412759 --- CHANGELOG.md | 1 + src/embed_tests/TestConverter.cs | 20 +++++++++++++++ src/embed_tests/TestPyInt.cs | 34 ++++++++++++++++++++++++ src/runtime/Converter.cs | 8 ++++++ src/runtime/PythonTypes/PyInt.cs | 44 +++++++++++++++++++++++++++----- src/runtime/Runtime.cs | 16 ++++++++---- src/runtime/Util/Util.cs | 7 +++++ 7 files changed, 119 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b303dc6..1e706b866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). - .NET arrays implement Python buffer protocol +- Python integer interoperability with `System.Numerics.BigInteger` - Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, and other `PyObject` derived types when called from Python. - .NET classes, that have `__call__` method are callable from Python diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 8f7cd381d..e586eda1b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using NUnit.Framework; @@ -131,6 +132,25 @@ public void ToNullable() Assert.AreEqual(Const, ni); } + [Test] + public void BigIntExplicit() + { + BigInteger val = 42; + var i = new PyInt(val); + var ni = i.As(); + Assert.AreEqual(val, ni); + var nullable = i.As(); + Assert.AreEqual(val, nullable); + } + + [Test] + public void PyIntImplicit() + { + var i = new PyInt(1); + var ni = (PyObject)i.As(); + Assert.AreEqual(i.rawPtr, ni.rawPtr); + } + [Test] public void ToPyList() { diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 03a368ed8..822fe0715 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -1,4 +1,8 @@ using System; +using System.Globalization; +using System.Linq; +using System.Numerics; + using NUnit.Framework; using Python.Runtime; @@ -179,5 +183,35 @@ public void TestConvertToInt64() Assert.IsInstanceOf(typeof(long), a.ToInt64()); Assert.AreEqual(val, a.ToInt64()); } + + [Test] + public void ToBigInteger() + { + int[] simpleValues = + { + 0, 1, 2, + 0x10, + 0x123, + 0x1234, + }; + simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); + + foreach (var val in simpleValues) + { + var pyInt = new PyInt(val); + Assert.AreEqual((BigInteger)val, pyInt.ToBigInteger()); + } + } + + [Test] + public void ToBigIntegerLarge() + { + BigInteger val = BigInteger.Pow(2, 1024) + 3; + var pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); + val = -val; + pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); + } } } diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index ff1f01a64..a90f31513 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -455,6 +455,14 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, } } + if (obType == typeof(System.Numerics.BigInteger) + && Runtime.PyInt_Check(value)) + { + using var pyInt = new PyInt(value); + result = pyInt.ToBigInteger(); + return true; + } + return ToPrimitive(value, obType, out result, setError); } diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index d503c15f3..3dcc6ddb2 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -1,21 +1,21 @@ using System; +using System.Globalization; +using System.Numerics; using System.Runtime.Serialization; namespace Python.Runtime { /// - /// Represents a Python integer object. See the documentation at - /// PY2: https://docs.python.org/2/c-api/int.html - /// PY3: No equivalent - /// for details. + /// Represents a Python integer object. + /// See the documentation at https://docs.python.org/3/c-api/long.html /// - public class PyInt : PyNumber + public class PyInt : PyNumber, IFormattable { internal PyInt(in StolenReference ptr) : base(ptr) { } - internal PyInt(BorrowedReference reference): base(reference) + internal PyInt(BorrowedReference reference) : base(reference) { if (!Runtime.PyInt_Check(reference)) throw new ArgumentException("object is not an int"); } @@ -135,6 +135,8 @@ public PyInt(string value) : base(Runtime.PyLong_FromString(value, 0).StealOrThr { } + public PyInt(BigInteger value) : this(value.ToString(CultureInfo.InvariantCulture)) { } + protected PyInt(SerializationInfo info, StreamingContext context) : base(info, context) { } @@ -198,5 +200,35 @@ public long ToInt64() } return val.Value; } + + public BigInteger ToBigInteger() + { + using var pyHex = Runtime.HexCallable.Invoke(this); + string hex = pyHex.As(); + int offset = 0; + bool neg = false; + if (hex[0] == '-') + { + offset++; + neg = true; + } + byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2]; + for (; offset < hex.Length; offset++) + { + int littleEndianHexIndex = hex.Length - 1 - offset; + int byteIndex = littleEndianHexIndex / 2; + int isByteTopHalf = littleEndianHexIndex & 1; + int valueShift = isByteTopHalf * 4; + littleEndianBytes[byteIndex] += (byte)(Util.HexToInt(hex[offset]) << valueShift); + } + var result = new BigInteger(littleEndianBytes); + return neg ? -result : result; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + using var _ = Py.GIL(); + return ToBigInteger().ToString(format, formatProvider); + } } } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index e33c4624c..b48ba92e3 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -179,6 +179,7 @@ internal static void Initialize(bool initSigs = false) clrInterop = GetModuleLazy("clr.interop"); inspect = GetModuleLazy("inspect"); + hexCallable = new(() => new PyString("%x").GetAttr("__mod__")); } static void NewRun() @@ -279,8 +280,9 @@ internal static void Shutdown() Exceptions.Shutdown(); PythonEngine.InteropConfiguration.Dispose(); - DisposeLazyModule(clrInterop); - DisposeLazyModule(inspect); + DisposeLazyObject(clrInterop); + DisposeLazyObject(inspect); + DisposeLazyObject(hexCallable); PyObjectConversions.Reset(); PyGC_Collect(); @@ -352,11 +354,11 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) public static bool TryCollectingGarbage(int runs) => TryCollectingGarbage(runs, forceBreakLoops: false); - static void DisposeLazyModule(Lazy module) + static void DisposeLazyObject(Lazy pyObject) { - if (module.IsValueCreated) + if (pyObject.IsValueCreated) { - module.Value.Dispose(); + pyObject.Value.Dispose(); } } @@ -489,8 +491,12 @@ private static void NullGCHandles(IEnumerable objects) private static Lazy inspect; internal static PyObject InspectModule => inspect.Value; + private static Lazy clrInterop; internal static PyObject InteropModule => clrInterop.Value; + + private static Lazy hexCallable; + internal static PyObject HexCallable => hexCallable.Value; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. internal static BorrowedReference CLRMetaType => PyCLRMetaType; diff --git a/src/runtime/Util/Util.cs b/src/runtime/Util/Util.cs index f5f0d2957..89f5bdf4c 100644 --- a/src/runtime/Util/Util.cs +++ b/src/runtime/Util/Util.cs @@ -141,6 +141,13 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb return reader.ReadToEnd(); } + public static int HexToInt(char hex) => hex switch + { + >= '0' and <= '9' => hex - '0', + >= 'a' and <= 'f' => hex - 'a' + 10, + _ => throw new ArgumentOutOfRangeException(nameof(hex)), + }; + public static IEnumerator GetEnumerator(this IEnumerator enumerator) => enumerator; public static IEnumerable WhereNotNull(this IEnumerable source) From 58bd58c01c3ebe917c274d56737495a98eecb265 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 8 Apr 2022 08:42:03 -0700 Subject: [PATCH 004/183] Ensure that shutdown is called from Python --- pythonnet/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 7eec90f27..10dc403e4 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -16,7 +16,7 @@ def set_runtime(runtime): def set_default_runtime() -> None: - if sys.platform == 'win32': + if sys.platform == "win32": set_runtime(clr_loader.get_netfx()) else: set_runtime(clr_loader.get_mono()) @@ -36,14 +36,15 @@ def load(): set_default_runtime() dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll") - + _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path) func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] - if func(''.encode("utf8")) != 0: + if func(b"") != 0: raise RuntimeError("Failed to initialize Python.Runtime.dll") import atexit + atexit.register(unload) @@ -51,7 +52,7 @@ def unload(): global _RUNTIME if _LOADER_ASSEMBLY is not None: func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] - if func(b"") != 0: + if func(b"full_shutdown") != 0: raise RuntimeError("Failed to call Python.NET shutdown") if _RUNTIME is not None: From 50da522c3b9098ebe10dde456efa81319083d6ba Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 9 Apr 2022 07:10:34 -0700 Subject: [PATCH 005/183] clear weakref list when reflected object is destroyed (#1758) this was missed when https://github.com/pythonnet/pythonnet/pull/1267 was superseded --- src/embed_tests/TestInstanceWrapping.cs | 10 ++++++++++ src/runtime/Runtime.Delegates.cs | 2 ++ src/runtime/Runtime.cs | 12 ++++++++++++ src/runtime/Types/ClassBase.cs | 6 ++++++ 4 files changed, 30 insertions(+) diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs index 8be207c00..0a441c823 100644 --- a/src/embed_tests/TestInstanceWrapping.cs +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -34,6 +34,16 @@ public void OverloadResolution_UnknownToObject() } } + [Test] + public void WeakRefIsNone_AfterObjectIsGone() + { + using var makeref = Py.Import("weakref").GetAttr("ref"); + var ub = new UriBuilder().ToPython(); + using var weakref = makeref.Invoke(ub); + ub.Dispose(); + Assert.IsTrue(weakref.Invoke().IsNone()); + } + class Base {} class Derived: Base { } diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 6388bde9f..0b6b75872 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -78,6 +78,7 @@ static Delegates() PyObject_RichCompareBool = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_RichCompareBool), GetUnmanagedDll(_PythonDll)); PyObject_IsInstance = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_IsInstance), GetUnmanagedDll(_PythonDll)); PyObject_IsSubclass = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_IsSubclass), GetUnmanagedDll(_PythonDll)); + PyObject_ClearWeakRefs = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_ClearWeakRefs), GetUnmanagedDll(_PythonDll)); PyCallable_Check = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyCallable_Check), GetUnmanagedDll(_PythonDll)); PyObject_IsTrue = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_IsTrue), GetUnmanagedDll(_PythonDll)); PyObject_Not = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyObject_Not), GetUnmanagedDll(_PythonDll)); @@ -361,6 +362,7 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyObject_RichCompareBool { get; } internal static delegate* unmanaged[Cdecl] PyObject_IsInstance { get; } internal static delegate* unmanaged[Cdecl] PyObject_IsSubclass { get; } + internal static delegate* unmanaged[Cdecl] PyObject_ClearWeakRefs { get; } internal static delegate* unmanaged[Cdecl] PyCallable_Check { get; } internal static delegate* unmanaged[Cdecl] PyObject_IsTrue { get; } internal static delegate* unmanaged[Cdecl] PyObject_Not { get; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index aeead7915..d92f45afb 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -993,6 +993,18 @@ internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference internal static int PyObject_IsSubclass(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsSubclass(ob, type); + internal static void PyObject_ClearWeakRefs(BorrowedReference ob) => Delegates.PyObject_ClearWeakRefs(ob); + + internal static BorrowedReference PyObject_GetWeakRefList(BorrowedReference ob) + { + Debug.Assert(ob != null); + var type = PyObject_TYPE(ob); + int offset = Util.ReadInt32(type, TypeOffset.tp_weaklistoffset); + if (offset == 0) return BorrowedReference.Null; + Debug.Assert(offset > 0); + return Util.ReadRef(ob, offset); + } + internal static int PyCallable_Check(BorrowedReference o) => Delegates.PyCallable_Check(o); diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 2493fd970..6066e5fec 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -346,6 +346,12 @@ public static void tp_dealloc(NewReference lastRef) public static int tp_clear(BorrowedReference ob) { + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); + if (weakrefs != null) + { + Runtime.PyObject_ClearWeakRefs(ob); + } + if (TryFreeGCHandle(ob)) { IntPtr addr = ob.DangerousGetAddress(); From 86c6a7f40c3f62bb1b085e2645ba9e2729439a39 Mon Sep 17 00:00:00 2001 From: Victor Date: Sun, 10 Apr 2022 00:24:38 -0700 Subject: [PATCH 006/183] clear weak reference list when an extension type is destroyed (#1761) According to official documentation for defining extension types, if they are to support weak references, they must clear weak reference list associated with the instance in tp_clear. Extension types were missed in https://github.com/pythonnet/pythonnet/pull/1758 ( 50da522c3b9098ebe10dde456efa81319083d6ba ) --- src/embed_tests/ExtensionTypes.cs | 32 ++++++++++++++++++++++++++++++ src/runtime/Types/ExtensionType.cs | 6 ++++++ src/runtime/Types/MetaType.cs | 6 ++++++ 3 files changed, 44 insertions(+) create mode 100644 src/embed_tests/ExtensionTypes.cs diff --git a/src/embed_tests/ExtensionTypes.cs b/src/embed_tests/ExtensionTypes.cs new file mode 100644 index 000000000..803845960 --- /dev/null +++ b/src/embed_tests/ExtensionTypes.cs @@ -0,0 +1,32 @@ +using System; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest; + +public class ExtensionTypes +{ + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void WeakrefIsNone_AfterBoundMethodIsGone() + { + using var makeref = Py.Import("weakref").GetAttr("ref"); + var boundMethod = new UriBuilder().ToPython().GetAttr(nameof(UriBuilder.GetHashCode)); + var weakref = makeref.Invoke(boundMethod); + boundMethod.Dispose(); + Assert.IsTrue(weakref.Invoke().IsNone()); + } +} diff --git a/src/runtime/Types/ExtensionType.cs b/src/runtime/Types/ExtensionType.cs index d680067c2..439bd3314 100644 --- a/src/runtime/Types/ExtensionType.cs +++ b/src/runtime/Types/ExtensionType.cs @@ -86,6 +86,12 @@ public unsafe static void tp_dealloc(NewReference lastRef) public static int tp_clear(BorrowedReference ob) { + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); + if (weakrefs != null) + { + Runtime.PyObject_ClearWeakRefs(ob); + } + if (TryFreeGCHandle(ob)) { bool deleted = loadedExtensions.Remove(ob.DangerousGetAddress()); diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 7558269b4..1543711f6 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -273,6 +273,12 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference /// public static void tp_dealloc(NewReference lastRef) { + var weakrefs = Runtime.PyObject_GetWeakRefList(lastRef.Borrow()); + if (weakrefs != null) + { + Runtime.PyObject_ClearWeakRefs(lastRef.Borrow()); + } + // Fix this when we dont cheat on the handle for subclasses! var flags = PyType.GetFlags(lastRef.Borrow()); From 5af19614d836fda853be922bde3d928d2a86eefc Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sun, 10 Apr 2022 15:32:08 -0700 Subject: [PATCH 007/183] fixed NativeTypeSpec on 32 bit platforms --- src/runtime/Native/NativeTypeSpec.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/Native/NativeTypeSpec.cs b/src/runtime/Native/NativeTypeSpec.cs index c57bd9363..3771061dd 100644 --- a/src/runtime/Native/NativeTypeSpec.cs +++ b/src/runtime/Native/NativeTypeSpec.cs @@ -10,7 +10,7 @@ struct NativeTypeSpec : IDisposable public readonly StrPtr Name; public readonly int BasicSize; public readonly int ItemSize; - public readonly TypeFlags Flags; + public readonly IntPtr Flags; public IntPtr Slots; public NativeTypeSpec(TypeSpec spec) @@ -20,7 +20,7 @@ public NativeTypeSpec(TypeSpec spec) this.Name = new StrPtr(spec.Name, Encoding.UTF8); this.BasicSize = spec.BasicSize; this.ItemSize = spec.ItemSize; - this.Flags = spec.Flags; + this.Flags = new IntPtr((long)spec.Flags); unsafe { From 85037267f87bb44d8e8e43784b3aa2e06dd0a469 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sun, 10 Apr 2022 15:32:28 -0700 Subject: [PATCH 008/183] enable x86 testing on Windows in CI --- .github/workflows/main.yml | 10 ++++++++-- src/runtime/Native/NativeTypeSpec.cs | 4 ++-- tests/conftest.py | 6 +++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 218796192..11d8699e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,12 @@ jobs: matrix: os: [windows, ubuntu, macos] python: ["3.6", "3.7", "3.8", "3.9", "3.10"] - platform: [x64] + platform: [x64, x86] + exclude: + - os: ubuntu + platform: x86 + - os: macos + platform: x86 steps: - name: Set Environment on macOS @@ -67,6 +72,7 @@ jobs: run: pytest --runtime mono - name: Python Tests (.NET Core) + if: ${{ matrix.platform == 'x64' }} run: pytest --runtime netcore - name: Python Tests (.NET Framework) @@ -77,7 +83,7 @@ jobs: run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ - name: Perf tests - if: ${{ matrix.python == '3.8' }} + if: ${{ (matrix.python == '3.8') && (matrix.platform == 'x64') }} run: | pip install --force --no-deps --target src/perf_tests/baseline/ pythonnet==2.5.2 dotnet test --configuration Release --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/perf_tests/ diff --git a/src/runtime/Native/NativeTypeSpec.cs b/src/runtime/Native/NativeTypeSpec.cs index 3771061dd..8b84df536 100644 --- a/src/runtime/Native/NativeTypeSpec.cs +++ b/src/runtime/Native/NativeTypeSpec.cs @@ -10,7 +10,7 @@ struct NativeTypeSpec : IDisposable public readonly StrPtr Name; public readonly int BasicSize; public readonly int ItemSize; - public readonly IntPtr Flags; + public readonly int Flags; public IntPtr Slots; public NativeTypeSpec(TypeSpec spec) @@ -20,7 +20,7 @@ public NativeTypeSpec(TypeSpec spec) this.Name = new StrPtr(spec.Name, Encoding.UTF8); this.BasicSize = spec.BasicSize; this.ItemSize = spec.ItemSize; - this.Flags = new IntPtr((long)spec.Flags); + this.Flags = (int)spec.Flags; unsafe { diff --git a/tests/conftest.py b/tests/conftest.py index 99ee07f2f..89db46eca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -86,7 +86,11 @@ def pytest_configure(config): else: domain_tests_dir = os.path.join(os.path.dirname(__file__), "domain_tests") bin_path = os.path.join(domain_tests_dir, "bin") - check_call(["dotnet", "build", domain_tests_dir, "-o", bin_path]) + build_cmd = ["dotnet", "build", domain_tests_dir, "-o", bin_path] + is_64bits = sys.maxsize > 2**32 + if not is_64bits: + build_cmd += ["/p:Prefer32Bit=True"] + check_call(build_cmd) From 80dc9f0ba061ed0dde51be0bce1a3d24c2b4f902 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 10 Apr 2022 18:12:51 +0200 Subject: [PATCH 009/183] Implement IConvertible interface on PyObject --- src/runtime/Converter.cs | 2 +- src/runtime/PythonTypes/PyFloat.cs | 2 + src/runtime/PythonTypes/PyInt.cs | 2 + .../PythonTypes/PyObject.IConvertible.cs | 53 +++++++++++++++++++ src/runtime/PythonTypes/PyString.cs | 2 + tests/test_conversion.py | 7 +++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/runtime/PythonTypes/PyObject.IConvertible.cs diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index a90f31513..a99961aaa 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -550,7 +550,7 @@ internal static int ToInt32(BorrowedReference value) /// /// Convert a Python value to an instance of a primitive managed type. /// - private static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError) + internal static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError) { result = null; if (obType.IsEnum) diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index 7fb9e8f4d..10104c10f 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -101,5 +101,7 @@ public static PyFloat AsFloat(PyObject value) PythonException.ThrowIfIsNull(op); return new PyFloat(op.Steal()); } + + public override TypeCode GetTypeCode() => TypeCode.Double; } } diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index 3dcc6ddb2..6b3dbf210 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -230,5 +230,7 @@ public string ToString(string format, IFormatProvider formatProvider) using var _ = Py.GIL(); return ToBigInteger().ToString(format, formatProvider); } + + public override TypeCode GetTypeCode() => TypeCode.Int64; } } diff --git a/src/runtime/PythonTypes/PyObject.IConvertible.cs b/src/runtime/PythonTypes/PyObject.IConvertible.cs new file mode 100644 index 000000000..503d3cab4 --- /dev/null +++ b/src/runtime/PythonTypes/PyObject.IConvertible.cs @@ -0,0 +1,53 @@ +using System; + +namespace Python.Runtime; + +public partial class PyObject : IConvertible +{ + public virtual TypeCode GetTypeCode() => TypeCode.Object; + + private T DoConvert() + { + using var _ = Py.GIL(); + if (Converter.ToPrimitive(Reference, typeof(T), out object? result, setError: false)) + { + return (T)result!; + } + else + { + throw new InvalidCastException(); + } + } + + public bool ToBoolean(IFormatProvider provider) => DoConvert(); + public byte ToByte(IFormatProvider provider) => DoConvert(); + public char ToChar(IFormatProvider provider) => DoConvert(); + public short ToInt16(IFormatProvider provider) => DoConvert(); + public int ToInt32(IFormatProvider provider) => DoConvert(); + public long ToInt64(IFormatProvider provider) => DoConvert(); + public sbyte ToSByte(IFormatProvider provider) => DoConvert(); + public ushort ToUInt16(IFormatProvider provider) => DoConvert(); + public uint ToUInt32(IFormatProvider provider) => DoConvert(); + public ulong ToUInt64(IFormatProvider provider) => DoConvert(); + + public float ToSingle(IFormatProvider provider) => DoConvert(); + public double ToDouble(IFormatProvider provider) => DoConvert(); + + public string ToString(IFormatProvider provider) => DoConvert(); + + public DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException(); + public decimal ToDecimal(IFormatProvider provider) => throw new InvalidCastException(); + + public object ToType(Type conversionType, IFormatProvider provider) + { + if (Converter.ToManaged(Reference, conversionType, out object? result, setError: false)) + { + return result!; + } + else + { + throw new InvalidCastException(); + } + } + +} \ No newline at end of file diff --git a/src/runtime/PythonTypes/PyString.cs b/src/runtime/PythonTypes/PyString.cs index cdd45e2c3..d54397fcf 100644 --- a/src/runtime/PythonTypes/PyString.cs +++ b/src/runtime/PythonTypes/PyString.cs @@ -59,5 +59,7 @@ public static bool IsStringType(PyObject value) { return Runtime.PyString_Check(value.obj); } + + public override TypeCode GetTypeCode() => TypeCode.String; } } diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 341b11b90..4de286b14 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -670,3 +670,10 @@ def test_int_param_resolution_required(): data = list(mri.MethodA(0x100000000, 10)) assert len(data) == 10 assert data[0] == 0 + +def test_iconvertible_conversion(): + change_type = System.Convert.ChangeType + + assert 1024 == change_type(1024, System.Int32) + assert 1024 == change_type(1024, System.Int64) + assert 1024 == change_type(1024, System.Int16) From 090ff9f460016b006f49c328115cc47f80be3b1f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 25 Apr 2022 10:51:52 +0200 Subject: [PATCH 010/183] Ensure that codec tests are run (#1763) * Move test_codec.py to the right directory * Call PyObjectConversions.Reset from Python via a proxy to keep it internal * Sign Python.Test DLL and make Python.Runtime internals visible for it --- src/runtime/Properties/AssemblyInfo.cs | 2 + src/testing/CodecTest.cs | 9 ++++ src/testing/Python.Test.csproj | 2 + {src/tests => tests}/test_codec.py | 74 +++++++++++++------------- 4 files changed, 50 insertions(+), 37 deletions(-) rename {src/tests => tests}/test_codec.py (88%) diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index 3417bccc8..b05fcc8bf 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -1,3 +1,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] + +[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] \ No newline at end of file diff --git a/src/testing/CodecTest.cs b/src/testing/CodecTest.cs index 74f77531b..f3c243dab 100644 --- a/src/testing/CodecTest.cs +++ b/src/testing/CodecTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Python.Runtime; namespace Python.Test { @@ -44,4 +45,12 @@ public int GetLength2(IList o) return o.Count; } } + + public static class CodecResetter + { + public static void Reset() + { + PyObjectConversions.Reset(); + } + } } diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 78f3a3169..1f40f4518 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -3,6 +3,8 @@ netstandard2.0;net6.0 true true + ..\pythonnet.snk + true diff --git a/src/tests/test_codec.py b/tests/test_codec.py similarity index 88% rename from src/tests/test_codec.py rename to tests/test_codec.py index 9fdbfbbf5..9744d3856 100644 --- a/src/tests/test_codec.py +++ b/tests/test_codec.py @@ -1,49 +1,49 @@ -# -*- coding: utf-8 -*- - -"""Test conversions using codecs from client python code""" -import clr -import System -import pytest -import Python.Runtime -from Python.Test import ListConversionTester, ListMember - -class int_iterable(): - def __init__(self): - self.counter = 0 - def __iter__(self): - return self - def __next__(self): - if self.counter == 3: - raise StopIteration - self.counter = self.counter + 1 - return self.counter +# -*- coding: utf-8 -*- + +"""Test conversions using codecs from client python code""" +import clr +import System +import pytest +import Python.Runtime +from Python.Test import ListConversionTester, ListMember, CodecResetter + +class int_iterable(): + def __init__(self): + self.counter = 0 + def __iter__(self): + return self + def __next__(self): + if self.counter == 3: + raise StopIteration + self.counter = self.counter + 1 + return self.counter class obj_iterable(): - def __init__(self): - self.counter = 0 - def __iter__(self): - return self - def __next__(self): - if self.counter == 3: - raise StopIteration + def __init__(self): + self.counter = 0 + def __iter__(self): + return self + def __next__(self): + if self.counter == 3: + raise StopIteration self.counter = self.counter + 1 return ListMember(self.counter, "Number " + str(self.counter)) - -def test_iterable(): - """Test that a python iterable can be passed into a function that takes an IEnumerable""" - - #Python.Runtime.Codecs.ListDecoder.Register() - #Python.Runtime.Codecs.SequenceDecoder.Register() - Python.Runtime.Codecs.IterableDecoder.Register() + +def test_iterable(): + """Test that a python iterable can be passed into a function that takes an IEnumerable""" + + #Python.Runtime.Codecs.ListDecoder.Register() + #Python.Runtime.Codecs.SequenceDecoder.Register() + Python.Runtime.Codecs.IterableDecoder.Register() ob = ListConversionTester() - iterable = int_iterable() + iterable = int_iterable() assert 3 == ob.GetLength(iterable) iterable2 = obj_iterable() assert 3 == ob.GetLength2(iterable2) - Python.Runtime.PyObjectConversions.Reset() + CodecResetter.Reset() def test_sequence(): Python.Runtime.Codecs.SequenceDecoder.Register() @@ -55,7 +55,7 @@ def test_sequence(): tup2 = (ListMember(1, "one"), ListMember(2, "two"), ListMember(3, "three")) assert 3 == ob.GetLength(tup2) - Python.Runtime.PyObjectConversions.Reset() + CodecResetter.Reset() def test_list(): Python.Runtime.Codecs.SequenceDecoder.Register() @@ -67,4 +67,4 @@ def test_list(): l2 = [ListMember(1, "one"), ListMember(2, "two"), ListMember(3, "three")] assert 3 == ob.GetLength(l2) - Python.Runtime.PyObjectConversions.Reset() + CodecResetter.Reset() From 7247da55c174be1b733e5f9fc4e1c356f6c42dc4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 26 Apr 2022 18:11:58 +0200 Subject: [PATCH 011/183] Fix decimal default parameters (#1773) * Drop outdated work around to fix decimal default parameters * Add test for Decimal default parameter * Update changelog --- CHANGELOG.md | 1 + src/runtime/MethodBinder.cs | 6 +----- src/testing/Python.Test.csproj | 1 + src/testing/methodtest.cs | 5 +++++ tests/test_method.py | 6 ++++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e706b866..766258c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Instead, `PyIterable` does that. - Unicode strings with surrogates were truncated when converting from Python - `Reload` mode now supports generic methods (previously Python would stop seeing them after reload) - Temporarily fixed issue resolving method overload when method signature has `out` parameters ([#1672](i1672)) +- Decimal default parameters are now correctly taken into account ### Removed diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 8b9ee9c00..be4e8d0e5 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -1072,11 +1072,7 @@ static internal class ParameterInfoExtensions { public static object? GetDefaultValue(this ParameterInfo parameterInfo) { - // parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0 - bool hasDefaultValue = (parameterInfo.Attributes & ParameterAttributes.HasDefault) == - ParameterAttributes.HasDefault; - - if (hasDefaultValue) + if (parameterInfo.HasDefaultValue) { return parameterInfo.DefaultValue; } diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 1f40f4518..3adc5c0c6 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -5,6 +5,7 @@ true ..\pythonnet.snk true + IDE0051;IDE0060 diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs index fe49de88d..ec05fef72 100644 --- a/src/testing/methodtest.cs +++ b/src/testing/methodtest.cs @@ -258,6 +258,11 @@ public static int TestSingleDefaultParam(int i = 5) return i; } + public static decimal TestDecimalDefaultParam(decimal n = 1m) + { + return n; + } + public static int TestTwoDefaultParam(int i = 5, int j = 6) { return i + j; diff --git a/tests/test_method.py b/tests/test_method.py index e2d8d5b06..b24b525aa 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -441,6 +441,12 @@ def test_single_default_param(): assert result == 5 +def test_decimal_default_param(): + """Test that decimal default parameters work.""" + result = MethodTest.TestDecimalDefaultParam() + assert result == System.Decimal(1) + + def test_one_arg_and_two_default_param(): """Test void method with single ref-parameter.""" result = MethodTest.TestOneArgAndTwoDefaultParam(11) From bbfa25263f5aad0639c06c7716854c442b65255a Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 3 May 2022 22:33:38 -0700 Subject: [PATCH 012/183] workaround for potentially buggy threading in Mono (#1779) https://github.com/mono/mono/issues/21466 --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 11d8699e4..284658620 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -66,6 +66,8 @@ jobs: - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ + env: + MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - name: Python Tests (Mono) if: ${{ matrix.os != 'windows' }} From 5eccd45a7ed9b4fddc9345fad1f7f63fa5d0adf3 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 10 Mar 2022 17:37:17 +0100 Subject: [PATCH 013/183] Use Py.GIL directly, now that it doesn't try to init anymore --- src/runtime/Loader.cs | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/runtime/Loader.cs b/src/runtime/Loader.cs index bfb6e0d6e..516b9ab9c 100644 --- a/src/runtime/Loader.cs +++ b/src/runtime/Loader.cs @@ -23,18 +23,8 @@ public unsafe static int Initialize(IntPtr data, int size) PythonDLL = null; } - var gs = PyGILState_Ensure(); - - try - { - // Console.WriteLine("Startup thread"); - PythonEngine.InitExt(); - // Console.WriteLine("Startup finished"); - } - finally - { - PyGILState_Release(gs); - } + using var _ = Py.GIL(); + PythonEngine.InitExt(); } catch (Exception exc) { @@ -55,15 +45,8 @@ public unsafe static int Shutdown(IntPtr data, int size) if (command == "full_shutdown") { - var gs = PyGILState_Ensure(); - try - { - PythonEngine.Shutdown(); - } - finally - { - PyGILState_Release(gs); - } + using var _ = Py.GIL(); + PythonEngine.Shutdown(); } } catch (Exception exc) From d2d3ba669f7f5a149d0077a7cc6b6e4872eb1bd7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 30 Mar 2022 15:29:44 +0200 Subject: [PATCH 014/183] Modernise syntax a bit --- src/runtime/AssemblyManager.cs | 6 +- src/runtime/ClassManager.cs | 3 +- src/runtime/Codecs/DecoderGroup.cs | 2 +- src/runtime/Codecs/EncoderGroup.cs | 2 +- src/runtime/Codecs/PyObjectConversions.cs | 6 +- src/runtime/Converter.cs | 23 ++- src/runtime/DelegateManager.cs | 6 +- src/runtime/Exceptions.cs | 3 +- src/runtime/Finalizer.cs | 2 +- src/runtime/InternString.cs | 3 +- src/runtime/Interop.cs | 2 +- src/runtime/InteropConfiguration.cs | 2 +- src/runtime/MethodBinder.cs | 88 ++++-------- src/runtime/Native/BorrowedReference.cs | 2 +- src/runtime/Native/CustomMarshaler.cs | 8 +- src/runtime/Native/NewReference.cs | 2 +- src/runtime/Native/PyIdentifier_.cs | 132 +++++++++--------- src/runtime/Native/PyIdentifier_.tt | 120 ++++++++-------- src/runtime/Native/TypeOffset.cs | 2 +- src/runtime/Py.cs | 3 +- src/runtime/PythonEngine.cs | 4 +- src/runtime/PythonException.cs | 9 +- src/runtime/PythonTypes/PyDict.cs | 6 +- src/runtime/PythonTypes/PyFloat.cs | 10 +- src/runtime/PythonTypes/PyModule.cs | 40 +++--- src/runtime/PythonTypes/PyObject.cs | 38 ++--- src/runtime/Runtime.cs | 12 +- .../StateSerialization/MaybeMemberInfo.cs | 4 +- .../StateSerialization/MaybeMethodBase.cs | 6 +- src/runtime/StateSerialization/MaybeType.cs | 6 +- src/runtime/TypeManager.cs | 51 ++++--- src/runtime/Types/ArrayObject.cs | 21 +-- src/runtime/Types/ClassBase.cs | 36 ++--- src/runtime/Types/ClassDerived.cs | 59 ++++---- src/runtime/Types/ClassObject.cs | 15 +- src/runtime/Types/DelegateObject.cs | 19 +-- src/runtime/Types/EventBinding.cs | 2 +- src/runtime/Types/EventObject.cs | 4 +- src/runtime/Types/ExtensionType.cs | 2 +- src/runtime/Types/InterfaceObject.cs | 11 +- src/runtime/Types/Iterator.cs | 4 +- src/runtime/Types/MetaType.cs | 56 +++----- src/runtime/Types/MethodBinding.cs | 9 +- src/runtime/Types/MethodObject.cs | 3 +- src/runtime/Types/ModuleObject.cs | 3 +- src/runtime/Types/MpLengthSlot.cs | 3 +- src/runtime/Types/OperatorMethod.cs | 18 ++- src/runtime/Types/OverloadMapper.cs | 4 +- src/runtime/Util/CodeGenerator.cs | 4 +- src/runtime/Util/DebugUtil.cs | 7 +- src/runtime/Util/GenericUtil.cs | 62 +++----- src/runtime/Util/OpsHelper.cs | 2 + src/runtime/Util/ReflectionPolyfills.cs | 2 +- 53 files changed, 420 insertions(+), 529 deletions(-) diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index 56c70c13a..d09d2d76e 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -23,8 +23,8 @@ internal class AssemblyManager // than it can end up referring to assemblies that are already unloaded (default behavior after unload appDomain - // unless LoaderOptimization.MultiDomain is used); // So for multidomain support it is better to have the dict. recreated for each app-domain initialization - private static ConcurrentDictionary> namespaces = - new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> namespaces = + new(); #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. // domain-level handlers are initialized in Initialize @@ -33,7 +33,7 @@ internal class AssemblyManager #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. // updated only under GIL? - private static Dictionary probed = new Dictionary(32); + private static readonly Dictionary probed = new(32); // modified from event handlers below, potentially triggered from different .NET threads private static readonly ConcurrentQueue assemblies = new(); diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 647cec3ed..6379f51de 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -247,10 +247,9 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow()); } - var co = impl as ClassObject; // If this is a ClassObject AND it has constructors, generate a __doc__ attribute. // required that the ClassObject.ctors be changed to internal - if (co != null) + if (impl is ClassObject co) { if (co.NumCtors > 0 && !co.HasCustomNew()) { diff --git a/src/runtime/Codecs/DecoderGroup.cs b/src/runtime/Codecs/DecoderGroup.cs index 5b3d127ad..41e1f0494 100644 --- a/src/runtime/Codecs/DecoderGroup.cs +++ b/src/runtime/Codecs/DecoderGroup.cs @@ -10,7 +10,7 @@ namespace Python.Runtime.Codecs /// public sealed class DecoderGroup: IPyObjectDecoder, IEnumerable, IDisposable { - readonly List decoders = new List(); + readonly List decoders = new(); /// /// Add specified decoder to the group diff --git a/src/runtime/Codecs/EncoderGroup.cs b/src/runtime/Codecs/EncoderGroup.cs index 32b550eb9..63abf35a3 100644 --- a/src/runtime/Codecs/EncoderGroup.cs +++ b/src/runtime/Codecs/EncoderGroup.cs @@ -10,7 +10,7 @@ namespace Python.Runtime.Codecs /// public sealed class EncoderGroup: IPyObjectEncoder, IEnumerable, IDisposable { - readonly List encoders = new List(); + readonly List encoders = new(); /// /// Add specified encoder to the group diff --git a/src/runtime/Codecs/PyObjectConversions.cs b/src/runtime/Codecs/PyObjectConversions.cs index 94ed4cdc3..cde97c8c9 100644 --- a/src/runtime/Codecs/PyObjectConversions.cs +++ b/src/runtime/Codecs/PyObjectConversions.cs @@ -15,8 +15,8 @@ namespace Python.Runtime /// public static class PyObjectConversions { - static readonly DecoderGroup decoders = new DecoderGroup(); - static readonly EncoderGroup encoders = new EncoderGroup(); + static readonly DecoderGroup decoders = new(); + static readonly EncoderGroup encoders = new(); /// /// Registers specified encoder (marshaller) @@ -62,7 +62,7 @@ public static void RegisterDecoder(IPyObjectDecoder decoder) } static readonly ConcurrentDictionary - clrToPython = new ConcurrentDictionary(); + clrToPython = new(); static IPyObjectEncoder[] GetEncoders(Type type) { lock (encoders) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index a99961aaa..f86ba7900 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -18,15 +18,15 @@ private Converter() { } - private static Type objectType; - private static Type stringType; - private static Type singleType; - private static Type doubleType; - private static Type int16Type; - private static Type int32Type; - private static Type int64Type; - private static Type boolType; - private static Type typeType; + private static readonly Type objectType; + private static readonly Type stringType; + private static readonly Type singleType; + private static readonly Type doubleType; + private static readonly Type int16Type; + private static readonly Type int32Type; + private static readonly Type int64Type; + private static readonly Type boolType; + private static readonly Type typeType; static Converter() { @@ -151,8 +151,7 @@ internal static NewReference ToPython(object? value, Type type) // it the type is a python subclass of a managed type then return the // underlying python object rather than construct a new wrapper object. - var pyderived = value as IPythonDerivedType; - if (null != pyderived) + if (value is IPythonDerivedType pyderived) { if (!IsTransparentProxy(pyderived)) return ClassDerivedObject.ToPython(pyderived); @@ -161,7 +160,7 @@ internal static NewReference ToPython(object? value, Type type) // ModuleObjects are created in a way that their wrapping them as // a CLRObject fails, the ClassObject has no tpHandle. Return the // pyHandle as is, do not convert. - if (value is ModuleObject modobj) + if (value is ModuleObject) { throw new NotImplementedException(); } diff --git a/src/runtime/DelegateManager.cs b/src/runtime/DelegateManager.cs index 092c9be1d..4343b9ab7 100644 --- a/src/runtime/DelegateManager.cs +++ b/src/runtime/DelegateManager.cs @@ -15,13 +15,13 @@ namespace Python.Runtime /// internal class DelegateManager { - private readonly Dictionary cache = new Dictionary(); + private readonly Dictionary cache = new(); private readonly Type basetype = typeof(Dispatcher); private readonly Type arrayType = typeof(object[]); private readonly Type voidtype = typeof(void); private readonly Type typetype = typeof(Type); private readonly Type pyobjType = typeof(PyObject); - private readonly CodeGenerator codeGenerator = new CodeGenerator(); + private readonly CodeGenerator codeGenerator = new(); private readonly ConstructorInfo arrayCtor; private readonly MethodInfo dispatch; @@ -309,7 +309,7 @@ protected Dispatcher(PyObject target, Type dtype) { tpName += $" of size {Runtime.PyTuple_Size(op)}"; } - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); if (!isVoid) sb.Append(rtype.FullName); for (int i = 0; i < pi.Length; i++) { diff --git a/src/runtime/Exceptions.cs b/src/runtime/Exceptions.cs index c3ac889ed..da095e030 100644 --- a/src/runtime/Exceptions.cs +++ b/src/runtime/Exceptions.cs @@ -196,8 +196,7 @@ public static bool SetError(Exception e) // might get a managed exception raised that is a wrapper for a // Python exception. In that case we'd rather have the real thing. - var pe = e as PythonException; - if (pe != null) + if (e is PythonException pe) { pe.Restore(); return true; diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index be17d62e3..e796cacd1 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -41,7 +41,7 @@ public ErrorArgs(Exception error) [DefaultValue(true)] public bool Enable { get; set; } = true; - private ConcurrentQueue _objQueue = new(); + private readonly ConcurrentQueue _objQueue = new(); private readonly ConcurrentQueue _derivedQueue = new(); private readonly ConcurrentQueue _bufferQueue = new(); private int _throttled; diff --git a/src/runtime/InternString.cs b/src/runtime/InternString.cs index 0780a0bb8..b6d9a0e4a 100644 --- a/src/runtime/InternString.cs +++ b/src/runtime/InternString.cs @@ -61,8 +61,7 @@ public static void Shutdown() public static string? GetManagedString(BorrowedReference op) { - string s; - if (TryGetInterned(op, out s)) + if (TryGetInterned(op, out string s)) { return s; } diff --git a/src/runtime/Interop.cs b/src/runtime/Interop.cs index bcf99bede..4aa4aa09b 100644 --- a/src/runtime/Interop.cs +++ b/src/runtime/Interop.cs @@ -139,7 +139,7 @@ internal static Type GetPrototype(MethodInfo method) } - internal static Dictionary allocatedThunks = new Dictionary(); + internal static Dictionary allocatedThunks = new(); internal static ThunkInfo GetThunk(MethodInfo method) { diff --git a/src/runtime/InteropConfiguration.cs b/src/runtime/InteropConfiguration.cs index 30c9a1c2c..781d0d01f 100644 --- a/src/runtime/InteropConfiguration.cs +++ b/src/runtime/InteropConfiguration.cs @@ -9,7 +9,7 @@ namespace Python.Runtime public sealed class InteropConfiguration: IDisposable { internal readonly PythonBaseTypeProviderGroup pythonBaseTypeProviders - = new PythonBaseTypeProviderGroup(); + = new(); /// Enables replacing base types of CLR types as seen from Python public IList PythonBaseTypeProviders => this.pythonBaseTypeProviders; diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index be4e8d0e5..ebbf4489e 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -242,52 +242,24 @@ internal static int ArgPrecedence(Type t) TypeCode tc = Type.GetTypeCode(t); // TODO: Clean up - switch (tc) + return tc switch { - case TypeCode.Object: - return 1; - - case TypeCode.UInt64: - return 10; - - case TypeCode.UInt32: - return 11; - - case TypeCode.UInt16: - return 12; - - case TypeCode.Int64: - return 13; - - case TypeCode.Int32: - return 14; - - case TypeCode.Int16: - return 15; - - case TypeCode.Char: - return 16; - - case TypeCode.SByte: - return 17; - - case TypeCode.Byte: - return 18; - - case TypeCode.Single: - return 20; - - case TypeCode.Double: - return 21; - - case TypeCode.String: - return 30; - - case TypeCode.Boolean: - return 40; - } - - return 2000; + TypeCode.Object => 1, + TypeCode.UInt64 => 10, + TypeCode.UInt32 => 11, + TypeCode.UInt16 => 12, + TypeCode.Int64 => 13, + TypeCode.Int32 => 14, + TypeCode.Int16 => 15, + TypeCode.Char => 16, + TypeCode.SByte => 17, + TypeCode.Byte => 18, + TypeCode.Single => 20, + TypeCode.Double => 21, + TypeCode.String => 30, + TypeCode.Boolean => 40, + _ => 2000, + }; } /// @@ -410,10 +382,6 @@ public MismatchedMethod(Exception exception, MethodBase mb) isGeneric = true; } ParameterInfo[] pi = mi.GetParameters(); - ArrayList? defaultArgList; - bool paramsArray; - int kwargsMatched; - int defaultsNeeded; bool isOperator = OperatorMethod.IsOperatorMethod(mi); // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. @@ -421,7 +389,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) continue; // Comparison operators in Python have no reverse mode. - if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded) && !isOperator) + if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator) { continue; } @@ -436,8 +404,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); } - int outs; - var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, outs: out outs); + var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, outs: out int outs); if (margs == null) { var mismatchCause = PythonException.FetchCurrent(); @@ -495,7 +462,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) { // Best effort for determining method to match on gives multiple possible // matches and we need at least one default argument - bail from this point - StringBuilder stringBuilder = new StringBuilder("Not enough arguments provided to disambiguate the method. Found:"); + var stringBuilder = new StringBuilder("Not enough arguments provided to disambiguate the method. Found:"); foreach (var matchedMethod in argMatchedMethods) { stringBuilder.AppendLine(); @@ -523,18 +490,20 @@ public MismatchedMethod(Exception exception, MethodBase mb) //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst); // InvalidCastException: Unable to cast object of type // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject' - var co = ManagedType.GetManagedObject(inst) as CLRObject; // Sanity check: this ensures a graceful exit if someone does // something intentionally wrong like call a non-static method // on the class rather than on an instance of the class. // XXX maybe better to do this before all the other rigmarole. - if (co == null) + if (ManagedType.GetManagedObject(inst) is CLRObject co) + { + target = co.inst; + } + else { Exceptions.SetError(Exceptions.TypeError, "Invoked a non-static method with an invalid instance"); return null; } - target = co.inst; } return new Binding(mi, target, margs, outs); @@ -623,7 +592,7 @@ static BorrowedReference HandleParamsArray(BorrowedReference args, int arrayStar for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++) { var parameter = pi[paramIndex]; - bool hasNamedParam = parameter.Name != null ? kwargDict.ContainsKey(parameter.Name) : false; + bool hasNamedParam = parameter.Name != null && kwargDict.ContainsKey(parameter.Name); if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart))) { @@ -658,8 +627,7 @@ static BorrowedReference HandleParamsArray(BorrowedReference args, int arrayStar } } - bool isOut; - if (!TryConvertArgument(op, parameter.ParameterType, out margs[paramIndex], out isOut)) + if (!TryConvertArgument(op, parameter.ParameterType, out margs[paramIndex], out bool isOut)) { tempObject.Dispose(); return null; @@ -789,7 +757,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa { defaultArgList = null; var match = false; - paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false; + paramsArray = parameters.Length > 0 && Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)); kwargsMatched = 0; defaultsNeeded = 0; if (positionalArgumentCount == parameters.Length && kwargDict.Count == 0) diff --git a/src/runtime/Native/BorrowedReference.cs b/src/runtime/Native/BorrowedReference.cs index 98c151bab..fd1059a5f 100644 --- a/src/runtime/Native/BorrowedReference.cs +++ b/src/runtime/Native/BorrowedReference.cs @@ -19,7 +19,7 @@ public IntPtr DangerousGetAddress() /// Gets a raw pointer to the Python object public IntPtr DangerousGetAddressOrNull() => this.pointer; - public static BorrowedReference Null => new BorrowedReference(); + public static BorrowedReference Null => new(); /// /// Creates new instance of from raw pointer. Unsafe. diff --git a/src/runtime/Native/CustomMarshaler.cs b/src/runtime/Native/CustomMarshaler.cs index f544756d8..62c027150 100644 --- a/src/runtime/Native/CustomMarshaler.cs +++ b/src/runtime/Native/CustomMarshaler.cs @@ -47,9 +47,7 @@ internal class UcsMarshaler : MarshalerBase public override IntPtr MarshalManagedToNative(object managedObj) { - var s = managedObj as string; - - if (s == null) + if (managedObj is not string s) { return IntPtr.Zero; } @@ -152,9 +150,7 @@ internal class StrArrayMarshaler : MarshalerBase public override IntPtr MarshalManagedToNative(object managedObj) { - var argv = managedObj as string[]; - - if (argv == null) + if (managedObj is not string[] argv) { return IntPtr.Zero; } diff --git a/src/runtime/Native/NewReference.cs b/src/runtime/Native/NewReference.cs index 91ebbdb01..25145fc4f 100644 --- a/src/runtime/Native/NewReference.cs +++ b/src/runtime/Native/NewReference.cs @@ -124,7 +124,7 @@ public void Dispose() /// [Pure] public static NewReference DangerousFromPointer(IntPtr pointer) - => new NewReference {pointer = pointer}; + => new() { pointer = pointer}; [Pure] internal static IntPtr DangerousGetAddressOrNull(in NewReference reference) => reference.pointer; diff --git a/src/runtime/Native/PyIdentifier_.cs b/src/runtime/Native/PyIdentifier_.cs index 4884a81ad..1ea2b704c 100644 --- a/src/runtime/Native/PyIdentifier_.cs +++ b/src/runtime/Native/PyIdentifier_.cs @@ -1,69 +1,69 @@ -using System; - -namespace Python.Runtime -{ - static class PyIdentifier +using System; + +namespace Python.Runtime +{ + static class PyIdentifier { -#pragma warning disable CS0649 // indentifier is never assigned to (assigned with reflection) - static IntPtr f__name__; - public static BorrowedReference __name__ => new(f__name__); - static IntPtr f__dict__; - public static BorrowedReference __dict__ => new(f__dict__); - static IntPtr f__doc__; - public static BorrowedReference __doc__ => new(f__doc__); - static IntPtr f__class__; - public static BorrowedReference __class__ => new(f__class__); - static IntPtr f__clear_reentry_guard__; - public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__); - static IntPtr f__module__; - public static BorrowedReference __module__ => new(f__module__); - static IntPtr f__file__; - public static BorrowedReference __file__ => new(f__file__); - static IntPtr f__slots__; - public static BorrowedReference __slots__ => new(f__slots__); - static IntPtr f__self__; - public static BorrowedReference __self__ => new(f__self__); - static IntPtr f__annotations__; - public static BorrowedReference __annotations__ => new(f__annotations__); - static IntPtr f__init__; - public static BorrowedReference __init__ => new(f__init__); - static IntPtr f__repr__; - public static BorrowedReference __repr__ => new(f__repr__); - static IntPtr f__import__; - public static BorrowedReference __import__ => new(f__import__); - static IntPtr f__builtins__; - public static BorrowedReference __builtins__ => new(f__builtins__); - static IntPtr fbuiltins; - public static BorrowedReference builtins => new(fbuiltins); - static IntPtr f__overloads__; - public static BorrowedReference __overloads__ => new(f__overloads__); - static IntPtr fOverloads; +#pragma warning disable CS0649 // indentifier is never assigned to (assigned with reflection) + static IntPtr f__name__; + public static BorrowedReference __name__ => new(f__name__); + static IntPtr f__dict__; + public static BorrowedReference __dict__ => new(f__dict__); + static IntPtr f__doc__; + public static BorrowedReference __doc__ => new(f__doc__); + static IntPtr f__class__; + public static BorrowedReference __class__ => new(f__class__); + static IntPtr f__clear_reentry_guard__; + public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__); + static IntPtr f__module__; + public static BorrowedReference __module__ => new(f__module__); + static IntPtr f__file__; + public static BorrowedReference __file__ => new(f__file__); + static IntPtr f__slots__; + public static BorrowedReference __slots__ => new(f__slots__); + static IntPtr f__self__; + public static BorrowedReference __self__ => new(f__self__); + static IntPtr f__annotations__; + public static BorrowedReference __annotations__ => new(f__annotations__); + static IntPtr f__init__; + public static BorrowedReference __init__ => new(f__init__); + static IntPtr f__repr__; + public static BorrowedReference __repr__ => new(f__repr__); + static IntPtr f__import__; + public static BorrowedReference __import__ => new(f__import__); + static IntPtr f__builtins__; + public static BorrowedReference __builtins__ => new(f__builtins__); + static IntPtr fbuiltins; + public static BorrowedReference builtins => new(fbuiltins); + static IntPtr f__overloads__; + public static BorrowedReference __overloads__ => new(f__overloads__); + static IntPtr fOverloads; public static BorrowedReference Overloads => new(fOverloads); #pragma warning restore CS0649 // indentifier is never assigned to (assigned with reflection) - } - - - static partial class InternString - { - private static readonly string[] _builtinNames = new string[] - { - "__name__", - "__dict__", - "__doc__", - "__class__", - "__clear_reentry_guard__", - "__module__", - "__file__", - "__slots__", - "__self__", - "__annotations__", - "__init__", - "__repr__", - "__import__", - "__builtins__", - "builtins", - "__overloads__", - "Overloads", - }; - } -} + } + + + static partial class InternString + { + private static readonly string[] _builtinNames = new string[] + { + "__name__", + "__dict__", + "__doc__", + "__class__", + "__clear_reentry_guard__", + "__module__", + "__file__", + "__slots__", + "__self__", + "__annotations__", + "__init__", + "__repr__", + "__import__", + "__builtins__", + "builtins", + "__overloads__", + "Overloads", + }; + } +} diff --git a/src/runtime/Native/PyIdentifier_.tt b/src/runtime/Native/PyIdentifier_.tt index 03a26cb50..9350cde43 100644 --- a/src/runtime/Native/PyIdentifier_.tt +++ b/src/runtime/Native/PyIdentifier_.tt @@ -1,62 +1,62 @@ -<#@ template debug="true" hostSpecific="true" #> -<#@ output extension=".cs" #> -<# - string[] internNames = new string[] - { - "__name__", - "__dict__", - "__doc__", - "__class__", - "__clear_reentry_guard__", - "__module__", - "__file__", - "__slots__", - "__self__", - "__annotations__", - - "__init__", - "__repr__", - "__import__", - "__builtins__", - - "builtins", - - "__overloads__", - "Overloads", - }; -#> -using System; - -namespace Python.Runtime -{ - static class PyIdentifier +<#@ template debug="true" hostSpecific="true" #> +<#@ output extension=".cs" #> +<# + string[] internNames = new string[] { -#pragma warning disable CS0649 // indentifier is never assigned to (assigned with reflection) -<# - foreach (var name in internNames) - { -#> - static IntPtr f<#= name #>; - public static BorrowedReference <#= name #> => new(f<#= name #>); -<# - } + "__name__", + "__dict__", + "__doc__", + "__class__", + "__clear_reentry_guard__", + "__module__", + "__file__", + "__slots__", + "__self__", + "__annotations__", + + "__init__", + "__repr__", + "__import__", + "__builtins__", + + "builtins", + + "__overloads__", + "Overloads", + }; #> -#pragma warning restore CS0649 // indentifier is never assigned to (assigned with reflection) - } - - - static partial class InternString - { - private static readonly string[] _builtinNames = new string[] - { -<# - foreach (var name in internNames) - { -#> - "<#= name #>", -<# - } -#> - }; - } -} +using System; + +namespace Python.Runtime +{ + static class PyIdentifier + { +#pragma warning disable CS0649 // indentifier is never assigned to (assigned with reflection) +<# + foreach (var name in internNames) + { +#> + static IntPtr f<#= name #>; + public static BorrowedReference <#= name #> => new(f<#= name #>); +<# + } +#> +#pragma warning restore CS0649 // indentifier is never assigned to (assigned with reflection) + } + + + static partial class InternString + { + private static readonly string[] _builtinNames = new string[] + { +<# + foreach (var name in internNames) + { +#> + "<#= name #>", +<# + } +#> + }; + } +} diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index a1bae8253..59c5c3ee0 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -121,7 +121,7 @@ public static int GetSlotOffset(string slotName) public static string? GetSlotName(int offset) => SlotOffsets.FirstOrDefault(kv => kv.Value == offset).Key; - static readonly HashSet slotNames = new HashSet(); + static readonly HashSet slotNames = new(); internal static bool IsSupportedSlotName(string name) => slotNames.Contains(name); [Conditional("DEBUG")] diff --git a/src/runtime/Py.cs b/src/runtime/Py.cs index 4f3fbf6d4..ec991f971 100644 --- a/src/runtime/Py.cs +++ b/src/runtime/Py.cs @@ -77,8 +77,7 @@ public static KeywordArguments kw(params object?[] kv) } for (var i = 0; i < kv.Length; i += 2) { - var key = kv[i] as string; - if (key is null) + if (kv[i] is not string key) throw new ArgumentException("Keys must be non-null strings"); BorrowedReference value; diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 1e82446cb..24e1bedeb 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -233,7 +233,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, // add the imported module to the clr module, and copy the API functions // and decorators into the main clr module. Runtime.PyDict_SetItemString(clr_dict, "_extras", module); - using (var keys = locals.Keys()) + using var keys = locals.Keys(); foreach (PyObject key in keys) { if (!key.ToString()!.StartsWith("_") || key.ToString()!.Equals("__version__")) @@ -374,7 +374,7 @@ public static void Shutdown() /// public delegate void ShutdownHandler(); - static List ShutdownHandlers = new List(); + static readonly List ShutdownHandlers = new(); /// /// Add a function to be called when the engine is shut down. diff --git a/src/runtime/PythonException.cs b/src/runtime/PythonException.cs index 813d0e586..8d3330c7b 100644 --- a/src/runtime/PythonException.cs +++ b/src/runtime/PythonException.cs @@ -397,8 +397,13 @@ public string Format() } public PythonException Clone() - => new PythonException(type: Type, value: Value, traceback: Traceback, - Message, InnerException); + => new( + type: Type, + value: Value, + traceback: Traceback, + Message, + InnerException + ); #region Serializable [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] diff --git a/src/runtime/PythonTypes/PyDict.cs b/src/runtime/PythonTypes/PyDict.cs index 80b8c8c9f..272f7828f 100644 --- a/src/runtime/PythonTypes/PyDict.cs +++ b/src/runtime/PythonTypes/PyDict.cs @@ -72,10 +72,8 @@ public bool HasKey(PyObject key) /// public bool HasKey(string key) { - using (var str = new PyString(key)) - { - return HasKey(str); - } + using var str = new PyString(key); + return HasKey(str); } diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index 10104c10f..c09ec93ba 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -53,12 +53,10 @@ private static StolenReference FromString(string value) { if (value is null) throw new ArgumentNullException(nameof(value)); - using (var s = new PyString(value)) - { - NewReference val = Runtime.PyFloat_FromString(s.Reference); - PythonException.ThrowIfIsNull(val); - return val.Steal(); - } + using var s = new PyString(value); + NewReference val = Runtime.PyFloat_FromString(s.Reference); + PythonException.ThrowIfIsNull(val); + return val.Steal(); } /// diff --git a/src/runtime/PythonTypes/PyModule.cs b/src/runtime/PythonTypes/PyModule.cs index ada24c6cd..4549678ed 100644 --- a/src/runtime/PythonTypes/PyModule.cs +++ b/src/runtime/PythonTypes/PyModule.cs @@ -322,13 +322,11 @@ public PyModule Set(string name, object? value) private void SetPyValue(string name, BorrowedReference value) { Check(); - using (var pyKey = new PyString(name)) + using var pyKey = new PyString(name); + int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); + if (r < 0) { - int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); - if (r < 0) - { - throw PythonException.ThrowLastAsClrException(); - } + throw PythonException.ThrowLastAsClrException(); } } @@ -362,10 +360,8 @@ public bool Contains(string name) if (name is null) throw new ArgumentNullException(nameof(name)); Check(); - using (var pyKey = new PyString(name)) - { - return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; - } + using var pyKey = new PyString(name); + return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; } /// @@ -398,19 +394,17 @@ public bool TryGet(string name, out PyObject? value) if (name is null) throw new ArgumentNullException(nameof(name)); Check(); - using (var pyKey = new PyString(name)) + using var pyKey = new PyString(name); + if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) { - if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) - { - using var op = Runtime.PyObject_GetItem(variables, pyKey.obj); - value = new PyObject(op.StealOrThrow()); - return true; - } - else - { - value = null; - return false; - } + using var op = Runtime.PyObject_GetItem(variables, pyKey.obj); + value = new PyObject(op.StealOrThrow()); + return true; + } + else + { + value = null; + return false; } } @@ -445,7 +439,7 @@ public bool TryGet(string name, out T? value) var result = TryGet(name, out var pyObj); if (!result) { - value = default(T); + value = default; return false; } value = pyObj!.As(); diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index e0a17bed5..cfd3e7158 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -539,10 +539,8 @@ public virtual PyObject GetItem(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); - using (var pyKey = new PyString(key)) - { - return GetItem(pyKey); - } + using var pyKey = new PyString(key); + return GetItem(pyKey); } @@ -556,10 +554,8 @@ public virtual PyObject GetItem(string key) /// public virtual PyObject GetItem(int index) { - using (var key = new PyInt(index)) - { - return GetItem(key); - } + using var key = new PyInt(index); + return GetItem(key); } @@ -597,10 +593,8 @@ public virtual void SetItem(string key, PyObject value) if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); - using (var pyKey = new PyString(key)) - { - SetItem(pyKey, value); - } + using var pyKey = new PyString(key); + SetItem(pyKey, value); } @@ -616,10 +610,8 @@ public virtual void SetItem(int index, PyObject value) { if (value == null) throw new ArgumentNullException(nameof(value)); - using (var pyindex = new PyInt(index)) - { - SetItem(pyindex, value); - } + using var pyindex = new PyInt(index); + SetItem(pyindex, value); } @@ -655,10 +647,8 @@ public virtual void DelItem(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); - using (var pyKey = new PyString(key)) - { - DelItem(pyKey); - } + using var pyKey = new PyString(key); + DelItem(pyKey); } @@ -672,10 +662,8 @@ public virtual void DelItem(string key) /// public virtual void DelItem(int index) { - using (var pyindex = new PyInt(index)) - { - DelItem(pyindex); - } + using var pyindex = new PyInt(index); + DelItem(pyindex); } @@ -1320,7 +1308,7 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg { using var _ = Py.GIL(); NewReference res; - if (!(arg is PyObject)) + if (arg is not PyObject) { arg = arg.ToPython(); } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index d92f45afb..6ad1d459f 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -554,17 +554,17 @@ internal static void CheckExceptionOccurred() } ManagedType? mt = ManagedType.GetManagedObject(op); - if (mt is ClassBase) + if (mt is ClassBase b) { - MaybeType _type = ((ClassBase)mt).type; + var _type = b.type; t = _type.Valid ? _type.Value : null; } - else if (mt is CLRObject) + else if (mt is CLRObject ob) { - object inst = ((CLRObject)mt).inst; - if (inst is Type) + var inst = ob.inst; + if (inst is Type ty) { - t = inst as Type; + t = ty; } } else diff --git a/src/runtime/StateSerialization/MaybeMemberInfo.cs b/src/runtime/StateSerialization/MaybeMemberInfo.cs index aa369a5ed..b734bb070 100644 --- a/src/runtime/StateSerialization/MaybeMemberInfo.cs +++ b/src/runtime/StateSerialization/MaybeMemberInfo.cs @@ -12,10 +12,10 @@ internal struct MaybeMemberInfo : ISerializable where T : MemberInfo // The ReflectedType of the object const string SerializationType = "t"; const string SerializationMemberName = "n"; - MemberInfo? info; + readonly MemberInfo? info; [NonSerialized] - Exception? deserializationException; + readonly Exception? deserializationException; public string DeletedMessage { diff --git a/src/runtime/StateSerialization/MaybeMethodBase.cs b/src/runtime/StateSerialization/MaybeMethodBase.cs index 9fb8ae047..d196d5e88 100644 --- a/src/runtime/StateSerialization/MaybeMethodBase.cs +++ b/src/runtime/StateSerialization/MaybeMethodBase.cs @@ -24,11 +24,11 @@ internal struct MaybeMethodBase : ISerializable where T: MethodBase public static implicit operator MaybeMethodBase (T? ob) => new (ob); - string? name; - MethodBase? info; + readonly string? name; + readonly MethodBase? info; [NonSerialized] - Exception? deserializationException; + readonly Exception? deserializationException; public string DeletedMessage { diff --git a/src/runtime/StateSerialization/MaybeType.cs b/src/runtime/StateSerialization/MaybeType.cs index abb3a8fb6..f3c96e369 100644 --- a/src/runtime/StateSerialization/MaybeType.cs +++ b/src/runtime/StateSerialization/MaybeType.cs @@ -9,12 +9,12 @@ namespace Python.Runtime [Serializable] internal struct MaybeType : ISerializable { - public static implicit operator MaybeType (Type ob) => new MaybeType(ob); + public static implicit operator MaybeType (Type ob) => new(ob); // The AssemblyQualifiedName of the serialized Type const string SerializationName = "n"; - string name; - Type type; + readonly string name; + readonly Type type; public string DeletedMessage { diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 84618df64..d9ca184f6 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -26,9 +26,9 @@ internal class TypeManager private const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static; - private static Dictionary cache = new(); + private static readonly Dictionary cache = new(); - static readonly Dictionary _slotsHolders = new Dictionary(PythonReferenceComparer.Instance); + static readonly Dictionary _slotsHolders = new(PythonReferenceComparer.Instance); // Slots which must be set private static readonly string[] _requiredSlots = new string[] @@ -84,7 +84,7 @@ internal static void RestoreRuntimeData(TypeManagerState storage) var typeCache = storage.Cache; foreach (var entry in typeCache) { - Type type = entry.Key.Value;; + var type = entry.Key.Value; cache![type] = entry.Value; SlotsHolder holder = CreateSlotsHolder(entry.Value); InitializeSlots(entry.Value, type, holder); @@ -385,31 +385,30 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe return Exceptions.RaiseTypeError("Couldn't convert __assembly__ value to string"); } - using (var namespaceKey = new PyString("__namespace__")) + using var namespaceKey = new PyString("__namespace__"); + var pyNamespace = Runtime.PyDict_GetItemWithError(dictRef, namespaceKey.Reference); + if (pyNamespace.IsNull) { - var pyNamespace = Runtime.PyDict_GetItemWithError(dictRef, namespaceKey.Reference); - if (pyNamespace.IsNull) - { - if (Exceptions.ErrorOccurred()) return default; - } - else if (!Converter.ToManagedValue(pyNamespace, typeof(string), out namespaceStr, true)) - { - return Exceptions.RaiseTypeError("Couldn't convert __namespace__ value to string"); - } + if (Exceptions.ErrorOccurred()) return default; + } + else if (!Converter.ToManagedValue(pyNamespace, typeof(string), out namespaceStr, true)) + { + return Exceptions.RaiseTypeError("Couldn't convert __namespace__ value to string"); } } // create the new managed type subclassing the base managed type - var baseClass = ManagedType.GetManagedObject(py_base_type) as ClassBase; - if (null == baseClass) + if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass) + { + return ReflectedClrType.CreateSubclass(baseClass, name, + ns: (string?)namespaceStr, + assembly: (string?)assembly, + dict: dictRef); + } + else { return Exceptions.RaiseTypeError("invalid base class, expected CLR class type"); } - - return ReflectedClrType.CreateSubclass(baseClass, name, - ns: (string?)namespaceStr, - assembly: (string?)assembly, - dict: dictRef); } internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc) @@ -526,7 +525,7 @@ internal static PyType CreateMetaType(Type impl, out SlotsHolder slotsHolder) internal static SlotsHolder SetupMetaSlots(Type impl, PyType type) { // Override type slots with those of the managed implementation. - SlotsHolder slotsHolder = new SlotsHolder(type); + var slotsHolder = new SlotsHolder(type); InitializeSlots(type, impl, slotsHolder); // We need space for 3 PyMethodDef structs. @@ -683,7 +682,7 @@ private static void SetRequiredSlots(PyType type, HashSet seen) static void InitializeSlot(BorrowedReference type, ThunkInfo thunk, string name, SlotsHolder? slotsHolder) { - if (!Enum.TryParse(name, out var id)) + if (!Enum.TryParse(name, out _)) { throw new NotSupportedException("Bad slot name " + name); } @@ -741,10 +740,10 @@ class SlotsHolder { public delegate void Resetor(PyType type, int offset); - private Dictionary _slots = new Dictionary(); - private List _keepalive = new List(); - private Dictionary _customResetors = new Dictionary(); - private List _deallocators = new List(); + private readonly Dictionary _slots = new(); + private readonly List _keepalive = new(); + private readonly Dictionary _customResetors = new(); + private readonly List _deallocators = new(); private bool _alreadyReset = false; private readonly PyType Type; diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index 3ca09ddce..bda717e56 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -63,14 +63,16 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, return NewInstance(arrType.GetElementType(), tp, dimensions); } } - object? result; // this implements casting to Array[T] - if (!Converter.ToManaged(op, arrType, out result, true)) + if (Converter.ToManaged(op, arrType, out object? result, true)) + { + return CLRObject.GetReference(result!, tp); + } + else { return default; } - return CLRObject.GetReference(result!, tp); } static NewReference CreateMultidimensional(Type elementType, long[] dimensions, BorrowedReference shapeTuple, BorrowedReference pyType) @@ -250,7 +252,6 @@ public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, Type itemType = obj.inst.GetType().GetElementType(); int rank = items.Rank; long index; - object? value; if (items.IsReadOnly) { @@ -258,7 +259,7 @@ public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, return -1; } - if (!Converter.ToManaged(v, itemType, out value, true)) + if (!Converter.ToManaged(v, itemType, out object? value, true)) { return -1; } @@ -353,9 +354,8 @@ public static int sq_contains(BorrowedReference ob, BorrowedReference v) var obj = (CLRObject)GetManagedObject(ob)!; Type itemType = obj.inst.GetType().GetElementType(); var items = (IList)obj.inst; - object? value; - if (!Converter.ToManaged(v, itemType, out value, false)) + if (!Converter.ToManaged(v, itemType, out object? value, false)) { return 0; } @@ -397,7 +397,8 @@ static int GetBuffer(BorrowedReference obj, out Py_buffer buffer, PyBUF flags) try { gcHandle = GCHandle.Alloc(self, GCHandleType.Pinned); - } catch (ArgumentException ex) + } + catch (ArgumentException ex) { Exceptions.SetError(Exceptions.BufferError, ex.Message); return -1; @@ -410,7 +411,7 @@ static int GetBuffer(BorrowedReference obj, out Py_buffer buffer, PyBUF flags) { buf = gcHandle.AddrOfPinnedObject(), obj = new NewReference(obj).DangerousMoveToPointer(), - len = (IntPtr)(self.LongLength*itemSize), + len = (IntPtr)(self.LongLength * itemSize), itemsize = (IntPtr)itemSize, _readonly = false, ndim = self.Rank, @@ -476,7 +477,7 @@ static unsafe IntPtr ToUnmanaged(T[] array) where T : unmanaged return result; } - static readonly Dictionary ItemFormats = new Dictionary + static readonly Dictionary ItemFormats = new() { [typeof(byte)] = "B", [typeof(sbyte)] = "b", diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 6066e5fec..1e3c325cc 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -41,7 +41,7 @@ internal virtual bool CanSubclass() return !type.Value.IsEnum; } - public readonly static Dictionary CilToPyOpMap = new Dictionary + public readonly static Dictionary CilToPyOpMap = new() { ["op_Equality"] = Runtime.Py_EQ, ["op_Inequality"] = Runtime.Py_NE, @@ -153,8 +153,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc { return Exceptions.RaiseTypeError("Cannot get managed object"); } - var co1Comp = co1.inst as IComparable; - if (co1Comp == null) + if (co1.inst is not IComparable co1Comp) { Type co1Type = co1.GetType(); return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); @@ -215,15 +214,13 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc /// static NewReference tp_iter_impl(BorrowedReference ob) { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) + if (GetManagedObject(ob) is not CLRObject co) { return Exceptions.RaiseTypeError("invalid object"); } - var e = co.inst as IEnumerable; IEnumerator? o; - if (e != null) + if (co.inst is IEnumerable e) { o = e.GetEnumerator(); } @@ -239,7 +236,7 @@ static NewReference tp_iter_impl(BorrowedReference ob) var elemType = typeof(object); var iterType = co.inst.GetType(); - foreach(var ifc in iterType.GetInterfaces()) + foreach (var ifc in iterType.GetInterfaces()) { if (ifc.IsGenericType) { @@ -261,13 +258,15 @@ static NewReference tp_iter_impl(BorrowedReference ob) /// public static nint tp_hash(BorrowedReference ob) { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) + if (GetManagedObject(ob) is CLRObject co) + { + return co.inst.GetHashCode(); + } + else { Exceptions.RaiseTypeError("unhashable type"); return 0; } - return co.inst.GetHashCode(); } @@ -298,8 +297,7 @@ public static NewReference tp_str(BorrowedReference ob) public static NewReference tp_repr(BorrowedReference ob) { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) + if (GetManagedObject(ob) is not CLRObject co) { return Exceptions.RaiseTypeError("invalid object"); } @@ -307,11 +305,17 @@ public static NewReference tp_repr(BorrowedReference ob) { //if __repr__ is defined, use it var instType = co.inst.GetType(); - System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); + var methodInfo = instType.GetMethod("__repr__"); if (methodInfo != null && methodInfo.IsPublic) { - var reprString = methodInfo.Invoke(co.inst, null) as string; - return reprString is null ? new NewReference(Runtime.PyNone) : Runtime.PyString_FromString(reprString); + if (methodInfo.Invoke(co.inst, null) is string reprString) + { + return Runtime.PyString_FromString(reprString); + } + else + { + return new NewReference(Runtime.PyNone); + } } //otherwise use the standard object.__repr__(inst) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 6c2c81b13..dd1f4595f 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -107,7 +107,8 @@ internal static NewReference ToPython(IPythonDerivedType obj) try { self = GetPyObj(obj).CheckRun(); - } catch (RuntimeShutdownException e) + } + catch (RuntimeShutdownException e) { Exceptions.SetError(e); return default; @@ -197,22 +198,19 @@ internal static Type CreateDerivedType(string name, if (py_dict != null && Runtime.PyDict_Check(py_dict)) { using var dict = new PyDict(py_dict); - using (PyIterable keys = dict.Keys()) + using var keys = dict.Keys(); + foreach (PyObject pyKey in keys) { - foreach (PyObject pyKey in keys) + using var value = dict[pyKey]; + if (value.HasAttr("_clr_property_type_")) { - using (PyObject value = dict[pyKey]) - { - if (value.HasAttr("_clr_property_type_")) - { - string propertyName = pyKey.ToString()!; - pyProperties.Add(propertyName); + string propertyName = pyKey.ToString()!; + pyProperties.Add(propertyName); - // Add the property to the type - AddPythonProperty(propertyName, value, typeBuilder); - } - } + // Add the property to the type + AddPythonProperty(propertyName, value, typeBuilder); } + pyKey.Dispose(); } } @@ -245,27 +243,24 @@ internal static Type CreateDerivedType(string name, if (py_dict != null && Runtime.PyDict_Check(py_dict)) { using var dict = new PyDict(py_dict); - using (PyIterable keys = dict.Keys()) + using var keys = dict.Keys(); + foreach (PyObject pyKey in keys) { - foreach (PyObject pyKey in keys) + using var value = dict[pyKey]; + if (value.HasAttr("_clr_return_type_") && value.HasAttr("_clr_arg_types_")) { - using (PyObject value = dict[pyKey]) - { - if (value.HasAttr("_clr_return_type_") && value.HasAttr("_clr_arg_types_")) - { - string methodName = pyKey.ToString()!; + string methodName = pyKey.ToString()!; - // if this method has already been redirected to the python method skip it - if (virtualMethods.Contains(methodName)) - { - continue; - } - - // Add the method to the type - AddPythonMethod(methodName, value, typeBuilder); - } + // if this method has already been redirected to the python method skip it + if (virtualMethods.Contains(methodName)) + { + continue; } + + // Add the method to the type + AddPythonMethod(methodName, value, typeBuilder); } + pyKey.Dispose(); } } @@ -868,10 +863,8 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s try { using var pyself = new PyObject(self.CheckRun()); - using (PyObject pyvalue = pyself.GetAttr(propertyName)) - { - return pyvalue.As(); - } + using var pyvalue = pyself.GetAttr(propertyName); + return pyvalue.As(); } finally { diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 5ba83c25e..04613afa5 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -53,12 +53,10 @@ internal NewReference GetDocString() /// static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, BorrowedReference kw) { - var self = GetManagedObject(tp) as ClassObject; - // Sanity check: this ensures a graceful error if someone does // something intentially wrong like use the managed metatype for // a class that is not really derived from a managed class. - if (self == null) + if (GetManagedObject(tp) is not ClassObject self) { return Exceptions.RaiseTypeError("invalid object"); } @@ -224,8 +222,15 @@ public override NewReference type_subscript(BorrowedReference idx) { return Exceptions.RaiseTypeError("type expected"); } - var c = GetManagedObject(idx) as ClassBase; - Type? t = c != null ? c.type.Value : Converter.GetTypeByAlias(idx); + Type? t; + if (GetManagedObject(idx) is ClassBase c) + { + t = c.type.Value; + } + else + { + t = Converter.GetTypeByAlias(idx); + } if (t == null) { return Exceptions.RaiseTypeError("type expected"); diff --git a/src/runtime/Types/DelegateObject.cs b/src/runtime/Types/DelegateObject.cs index 43a75aba7..cbc32696e 100644 --- a/src/runtime/Types/DelegateObject.cs +++ b/src/runtime/Types/DelegateObject.cs @@ -11,7 +11,7 @@ namespace Python.Runtime [Serializable] internal class DelegateObject : ClassBase { - private MethodBinder binder; + private readonly MethodBinder binder; internal DelegateObject(Type tp) : base(tp) { @@ -25,8 +25,7 @@ internal DelegateObject(Type tp) : base(tp) /// private static Delegate? GetTrueDelegate(BorrowedReference op) { - var o = GetManagedObject(op) as CLRObject; - if (o != null) + if (GetManagedObject(op) is CLRObject o) { var d = o.inst as Delegate; return d; @@ -83,20 +82,12 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, // TODO: add fast type check! BorrowedReference pytype = Runtime.PyObject_TYPE(ob); var self = (DelegateObject)GetManagedObject(pytype)!; - var o = GetManagedObject(ob) as CLRObject; - if (o == null) + if (GetManagedObject(ob) is CLRObject o && o.inst is Delegate _) { - return Exceptions.RaiseTypeError("invalid argument"); + return self.binder.Invoke(ob, args, kw); } - - var d = o.inst as Delegate; - - if (d == null) - { - return Exceptions.RaiseTypeError("invalid argument"); - } - return self.binder.Invoke(ob, args, kw); + return Exceptions.RaiseTypeError("invalid argument"); } diff --git a/src/runtime/Types/EventBinding.cs b/src/runtime/Types/EventBinding.cs index 69ca8f88e..9eb2382ec 100644 --- a/src/runtime/Types/EventBinding.cs +++ b/src/runtime/Types/EventBinding.cs @@ -12,7 +12,7 @@ internal class EventBinding : ExtensionType { private readonly string name; private readonly EventHandlerCollection e; - private PyObject? target; + private readonly PyObject? target; public EventBinding(string name, EventHandlerCollection e, PyObject? target) { diff --git a/src/runtime/Types/EventObject.cs b/src/runtime/Types/EventObject.cs index 90346f2d2..a682bfebd 100644 --- a/src/runtime/Types/EventObject.cs +++ b/src/runtime/Types/EventObject.cs @@ -57,9 +57,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference /// public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val) { - var e = GetManagedObject(val) as EventBinding; - - if (e != null) + if (GetManagedObject(val) is EventBinding _) { return 0; } diff --git a/src/runtime/Types/ExtensionType.cs b/src/runtime/Types/ExtensionType.cs index 439bd3314..305fdc15d 100644 --- a/src/runtime/Types/ExtensionType.cs +++ b/src/runtime/Types/ExtensionType.cs @@ -40,7 +40,7 @@ public virtual NewReference Alloc() return py; } - public PyObject AllocObject() => new PyObject(Alloc().Steal()); + public PyObject AllocObject() => new(Alloc().Steal()); // "borrowed" references internal static readonly HashSet loadedExtensions = new(); diff --git a/src/runtime/Types/InterfaceObject.cs b/src/runtime/Types/InterfaceObject.cs index b7e865b62..2d82392bf 100644 --- a/src/runtime/Types/InterfaceObject.cs +++ b/src/runtime/Types/InterfaceObject.cs @@ -27,7 +27,7 @@ internal InterfaceObject(Type tp) : base(tp) return comClass?.CoClass.GetConstructor(Type.EmptyTypes); } - private static Type cc_attr; + private static readonly Type cc_attr; static InterfaceObject() { @@ -51,15 +51,16 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, if (nargs == 1) { BorrowedReference inst = Runtime.PyTuple_GetItem(args, 0); - var co = GetManagedObject(inst) as CLRObject; - if (co == null || !type.IsInstanceOfType(co.inst)) + if (GetManagedObject(inst) is CLRObject co && type.IsInstanceOfType(co.inst)) + { + obj = co.inst; + } + else { Exceptions.SetError(Exceptions.TypeError, $"object does not implement {type.Name}"); return default; } - - obj = co.inst; } else if (nargs == 0 && self.ctor != null) diff --git a/src/runtime/Types/Iterator.cs b/src/runtime/Types/Iterator.cs index 829ff8a7a..49145d2c3 100644 --- a/src/runtime/Types/Iterator.cs +++ b/src/runtime/Types/Iterator.cs @@ -9,8 +9,8 @@ namespace Python.Runtime /// internal class Iterator : ExtensionType { - private IEnumerator iter; - private Type elemType; + private readonly IEnumerator iter; + private readonly Type elemType; public Iterator(IEnumerator e, Type elemType) { diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 1543711f6..5b59f5139 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -100,8 +100,7 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, // Ensure that the reflected type is appropriate for subclassing, // disallowing subclassing of delegates, enums and array types. - var cb = GetManagedObject(base_type) as ClassBase; - if (cb != null) + if (GetManagedObject(base_type) is ClassBase cb) { try { @@ -128,12 +127,10 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, // into python. if (null != dict) { - using (var clsDict = new PyDict(dict)) + using var clsDict = new PyDict(dict); + if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) { - if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) - { - return TypeManager.CreateSubType(name, base_type, clsDict); - } + return TypeManager.CreateSubType(name, base_type, clsDict); } } @@ -259,8 +256,7 @@ public static int tp_setattro(BorrowedReference tp, BorrowedReference name, Borr /// public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference idx) { - var cb = GetManagedObject(tp) as ClassBase; - if (cb != null) + if (GetManagedObject(tp) is ClassBase cb) { return cb.type_subscript(idx); } @@ -310,44 +306,30 @@ public static void tp_dealloc(NewReference lastRef) private static NewReference DoInstanceCheck(BorrowedReference tp, BorrowedReference args, bool checkType) { - var cb = GetManagedObject(tp) as ClassBase; - - if (cb == null || !cb.type.Valid) + if (GetManagedObject(tp) is not ClassBase cb || !cb.type.Valid) { return new NewReference(Runtime.PyFalse); } - using (var argsObj = new PyList(args)) + using var argsObj = new PyList(args); + if (argsObj.Length() != 1) { - if (argsObj.Length() != 1) - { - return Exceptions.RaiseTypeError("Invalid parameter count"); - } - - PyObject arg = argsObj[0]; - PyObject otherType; - if (checkType) - { - otherType = arg; - } - else - { - otherType = arg.GetPythonType(); - } + return Exceptions.RaiseTypeError("Invalid parameter count"); + } - if (Runtime.PyObject_TYPE(otherType) != PyCLRMetaType) - { - return new NewReference(Runtime.PyFalse); - } + PyObject arg = argsObj[0]; + var otherType = checkType ? arg : arg.GetPythonType(); - var otherCb = GetManagedObject(otherType) as ClassBase; - if (otherCb == null || !otherCb.type.Valid) - { - return new NewReference(Runtime.PyFalse); - } + if (Runtime.PyObject_TYPE(otherType) != PyCLRMetaType) + { + return new NewReference(Runtime.PyFalse); + } + if (GetManagedObject(otherType) is ClassBase otherCb && otherCb.type.Valid) + { return Converter.ToPython(cb.type.Value.IsAssignableFrom(otherCb.type.Value)); } + return new NewReference(Runtime.PyFalse); } public static NewReference __instancecheck__(BorrowedReference tp, BorrowedReference args) diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index 6d21af01e..334d705a6 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -172,7 +172,6 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, var info = self.info.Value; if (info.IsGenericMethod) { - var len = Runtime.PyTuple_Size(args); //FIXME: Never used Type[]? sigTp = Runtime.PythonArgsToTypeArray(args, true); if (sigTp != null) { @@ -220,15 +219,13 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, var inst = GetManagedObject(target) as CLRObject; if (inst?.inst is IPythonDerivedType) { - var baseType = GetManagedObject(self.targetType!) as ClassBase; - if (baseType != null && baseType.type.Valid) + if (GetManagedObject(self.targetType!) is ClassBase baseType && baseType.type.Valid) { - string baseMethodName = "_" + baseType.type.Value.Name + "__" + self.m.name; + var baseMethodName = $"_{baseType.type.Value.Name}__{self.m.name}"; using var baseMethod = Runtime.PyObject_GetAttrString(target, baseMethodName); if (!baseMethod.IsNull()) { - var baseSelf = GetManagedObject(baseMethod.Borrow()) as MethodBinding; - if (baseSelf != null) + if (GetManagedObject(baseMethod.Borrow()) is MethodBinding baseSelf) { self = baseSelf; } diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index b0fda49d3..0bdd11ac2 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -186,8 +186,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference // this descriptor was defined on then it will be because the base class method // is being called via super(Derived, self).method(...). // In which case create a MethodBinding bound to the base class. - var obj = GetManagedObject(ob) as CLRObject; - if (obj != null + if (GetManagedObject(ob) is CLRObject obj && obj.inst.GetType() != self.type.Value && obj.inst is IPythonDerivedType && self.type.Value.IsInstanceOfType(obj.inst)) diff --git a/src/runtime/Types/ModuleObject.cs b/src/runtime/Types/ModuleObject.cs index 1cc9f04b2..a9e8c9937 100644 --- a/src/runtime/Types/ModuleObject.cs +++ b/src/runtime/Types/ModuleObject.cs @@ -499,8 +499,7 @@ public static Assembly AddReference(string name) { AssemblyManager.UpdatePath(); var origNs = AssemblyManager.GetNamespaces(); - Assembly? assembly = null; - assembly = AssemblyManager.FindLoadedAssembly(name); + Assembly? assembly = AssemblyManager.FindLoadedAssembly(name); if (assembly == null) { assembly = AssemblyManager.LoadAssemblyPath(name); diff --git a/src/runtime/Types/MpLengthSlot.cs b/src/runtime/Types/MpLengthSlot.cs index 9e4865fe0..4cf15f221 100644 --- a/src/runtime/Types/MpLengthSlot.cs +++ b/src/runtime/Types/MpLengthSlot.cs @@ -32,8 +32,7 @@ public static bool CanAssign(Type clrType) /// internal static nint impl(BorrowedReference ob) { - var co = ManagedType.GetManagedObject(ob) as CLRObject; - if (co == null) + if (ManagedType.GetManagedObject(ob) is not CLRObject co) { Exceptions.RaiseTypeError("invalid object"); return -1; diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index abe6ded1a..0c6127f65 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -148,7 +148,7 @@ public static string GetPyMethodName(string clrName) private static string GenerateDummyCode() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.AppendLine("class OperatorMethod(object):"); foreach (var item in OpMethodMap.Values) { @@ -160,15 +160,13 @@ private static string GenerateDummyCode() private static PyObject GetOperatorType() { - using (PyDict locals = new PyDict()) - { - // A hack way for getting typeobject.c::slotdefs - string code = GenerateDummyCode(); - // The resulting OperatorMethod class is stored in a PyDict. - PythonEngine.Exec(code, null, locals); - // Return the class itself, which is a type. - return locals.GetItem("OperatorMethod"); - } + using var locals = new PyDict(); + // A hack way for getting typeobject.c::slotdefs + string code = GenerateDummyCode(); + // The resulting OperatorMethod class is stored in a PyDict. + PythonEngine.Exec(code, null, locals); + // Return the class itself, which is a type. + return locals.GetItem("OperatorMethod"); } public static string ReversePyMethodName(string pyName) diff --git a/src/runtime/Types/OverloadMapper.cs b/src/runtime/Types/OverloadMapper.cs index 20939f4c6..8f6e30478 100644 --- a/src/runtime/Types/OverloadMapper.cs +++ b/src/runtime/Types/OverloadMapper.cs @@ -9,8 +9,8 @@ namespace Python.Runtime /// internal class OverloadMapper : ExtensionType { - private MethodObject m; - private PyObject? target; + private readonly MethodObject m; + private readonly PyObject? target; public OverloadMapper(MethodObject m, PyObject? target) { diff --git a/src/runtime/Util/CodeGenerator.cs b/src/runtime/Util/CodeGenerator.cs index d0079fabb..35a637113 100644 --- a/src/runtime/Util/CodeGenerator.cs +++ b/src/runtime/Util/CodeGenerator.cs @@ -14,8 +14,8 @@ namespace Python.Runtime /// internal class CodeGenerator { - private AssemblyBuilder aBuilder; - private ModuleBuilder mBuilder; + private readonly AssemblyBuilder aBuilder; + private readonly ModuleBuilder mBuilder; internal CodeGenerator() { diff --git a/src/runtime/Util/DebugUtil.cs b/src/runtime/Util/DebugUtil.cs index eb9facb3c..0eecc87b0 100644 --- a/src/runtime/Util/DebugUtil.cs +++ b/src/runtime/Util/DebugUtil.cs @@ -57,7 +57,6 @@ internal static void DumpType(BorrowedReference type) var slots = TypeOffset.GetOffsets(); - int size = IntPtr.Size; foreach (var entry in slots) { @@ -99,7 +98,7 @@ internal static void DumpInst(BorrowedReference ob) } [Conditional("DEBUG")] - internal static void debug(string msg) + internal static void Debug(string msg) { var st = new StackTrace(1, true); StackFrame sf = st.GetFrame(0); @@ -138,13 +137,13 @@ public static void PrintHexBytes(byte[] bytes) public static void AssertHasReferences(BorrowedReference obj) { nint refcount = Runtime.Refcount(obj); - Debug.Assert(refcount > 0, "Object refcount is 0 or less"); + System.Diagnostics.Debug.Assert(refcount > 0, "Object refcount is 0 or less"); } [Conditional("DEBUG")] public static void EnsureGIL() { - Debug.Assert(HaveInterpreterLock(), "GIL must be acquired"); + System.Diagnostics.Debug.Assert(HaveInterpreterLock(), "GIL must be acquired"); } public static bool HaveInterpreterLock() => Runtime.PyGILState_Check() == 1; diff --git a/src/runtime/Util/GenericUtil.cs b/src/runtime/Util/GenericUtil.cs index 74db54af1..907a3a616 100644 --- a/src/runtime/Util/GenericUtil.cs +++ b/src/runtime/Util/GenericUtil.cs @@ -32,15 +32,13 @@ internal static void Register(Type t) return; } - Dictionary> nsmap; - if (!mapping.TryGetValue(t.Namespace, out nsmap)) + if (!mapping.TryGetValue(t.Namespace, out var nsmap)) { nsmap = new Dictionary>(); mapping[t.Namespace] = nsmap; } string basename = GetBasename(t.Name); - List gnames; - if (!nsmap.TryGetValue(basename, out gnames)) + if (!nsmap.TryGetValue(basename, out var gnames)) { gnames = new List(); nsmap[basename] = gnames; @@ -53,17 +51,11 @@ internal static void Register(Type t) /// public static List? GetGenericBaseNames(string ns) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) + if (mapping.TryGetValue(ns, out var nsmap)) { - return null; + return nsmap.Keys.ToList(); } - var names = new List(); - foreach (string key in nsmap.Keys) - { - names.Add(key); - } - return names; + return null; } /// @@ -79,28 +71,21 @@ internal static void Register(Type t) /// public static Type? GenericByName(string ns, string basename, int paramCount) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) - { - return null; - } - - List names; - if (!nsmap.TryGetValue(GetBasename(basename), out names)) - { - return null; - } - - foreach (string name in names) + if (mapping.TryGetValue(ns, out var nsmap)) { - string qname = ns + "." + name; - Type o = AssemblyManager.LookupTypes(qname).FirstOrDefault(); - if (o != null && o.GetGenericArguments().Length == paramCount) + if (nsmap.TryGetValue(GetBasename(basename), out var names)) { - return o; + foreach (string name in names) + { + string qname = $"{ns}.{name}"; + Type o = AssemblyManager.LookupTypes(qname).FirstOrDefault(); + if (o != null && o.GetGenericArguments().Length == paramCount) + { + return o; + } + } } } - return null; } @@ -109,16 +94,13 @@ internal static void Register(Type t) /// public static string? GenericNameForBaseName(string ns, string name) { - Dictionary> nsmap; - if (!mapping.TryGetValue(ns, out nsmap)) + if (mapping.TryGetValue(ns, out var nsmap)) { - return null; - } - List gnames; - nsmap.TryGetValue(name, out gnames); - if (gnames?.Count > 0) - { - return gnames[0]; + nsmap.TryGetValue(name, out var gnames); + if (gnames?.Count > 0) + { + return gnames[0]; + } } return null; } diff --git a/src/runtime/Util/OpsHelper.cs b/src/runtime/Util/OpsHelper.cs index ab623f3de..12d0c7aa6 100644 --- a/src/runtime/Util/OpsHelper.cs +++ b/src/runtime/Util/OpsHelper.cs @@ -46,10 +46,12 @@ internal static class FlagEnumOps where T : Enum static readonly Func invert = UnaryOp(Expression.OnesComplement); +#pragma warning disable IDE1006 public static T op_BitwiseAnd(T a, T b) => and(a, b); public static T op_BitwiseOr(T a, T b) => or(a, b); public static T op_ExclusiveOr(T a, T b) => xor(a, b); public static T op_OnesComplement(T value) => invert(value); +#pragma warning restore IDE1006 static Expression FromNumber(Expression number) => Expression.Convert(number, typeof(T)); diff --git a/src/runtime/Util/ReflectionPolyfills.cs b/src/runtime/Util/ReflectionPolyfills.cs index 36bd39cef..b33698509 100644 --- a/src/runtime/Util/ReflectionPolyfills.cs +++ b/src/runtime/Util/ReflectionPolyfills.cs @@ -7,7 +7,7 @@ namespace Python.Runtime { internal static class ReflectionPolyfills { - public static AssemblyBuilder DefineDynamicAssembly(this AppDomain appDomain, AssemblyName assemblyName, AssemblyBuilderAccess assemblyBuilderAccess) + public static AssemblyBuilder DefineDynamicAssembly(this AppDomain _, AssemblyName assemblyName, AssemblyBuilderAccess assemblyBuilderAccess) { return AssemblyBuilder.DefineDynamicAssembly(assemblyName, assemblyBuilderAccess); } From d8990839a8eaf87ed885cba75427736cffc38353 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 30 Mar 2022 15:38:30 +0200 Subject: [PATCH 015/183] Fix Py.SetArgv for systems that don't support GetCommandLineArgs --- src/runtime/Py.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/runtime/Py.cs b/src/runtime/Py.cs index ec991f971..1fcce5110 100644 --- a/src/runtime/Py.cs +++ b/src/runtime/Py.cs @@ -122,11 +122,7 @@ public static void SetArgv() args = Enumerable.Empty(); } - SetArgv( - new[] { "" }.Concat( - Environment.GetCommandLineArgs().Skip(1) - ) - ); + SetArgv(new[] { "" }.Concat(args.Skip(1))); } public static void SetArgv(params string[] argv) From aa5b54bf7f27970275b89764140dccf0b44d38e2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 30 Mar 2022 15:39:15 +0200 Subject: [PATCH 016/183] Modernise syntax in ClassDerived --- src/runtime/Types/ClassDerived.cs | 236 +++++++++++++++--------------- 1 file changed, 115 insertions(+), 121 deletions(-) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index dd1f4595f..02288faee 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -469,100 +469,100 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde methodName = pyMethodName.As() ?? throw new ArgumentNullException(methodNameAttribute); } - using (PyObject pyReturnType = func.GetAttr("_clr_return_type_")) - using (var pyArgTypes = PyIter.GetIter(func.GetAttr("_clr_arg_types_"))) + using var pyReturnType = func.GetAttr("_clr_return_type_"); + using var pyArgTypes = func.GetAttr("_clr_arg_types_"); + using var pyArgTypesIter = PyIter.GetIter(pyArgTypes); + var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type; + if (returnType == null) { - var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type; - if (returnType == null) - { - returnType = typeof(void); - } + returnType = typeof(void); + } - var argTypes = new List(); - foreach (PyObject pyArgType in pyArgTypes) + var argTypes = new List(); + foreach (PyObject pyArgType in pyArgTypesIter) + { + var argType = pyArgType.AsManagedObject(typeof(Type)) as Type; + if (argType == null) { - var argType = pyArgType.AsManagedObject(typeof(Type)) as Type; - if (argType == null) - { - throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); - } - argTypes.Add(argType); + throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); } + argTypes.Add(argType); + pyArgType.Dispose(); + } - // add the method to call back into python - MethodAttributes methodAttribs = MethodAttributes.Public | - MethodAttributes.Virtual | - MethodAttributes.ReuseSlot | - MethodAttributes.HideBySig; + // add the method to call back into python + MethodAttributes methodAttribs = MethodAttributes.Public | + MethodAttributes.Virtual | + MethodAttributes.ReuseSlot | + MethodAttributes.HideBySig; - MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, - methodAttribs, - returnType, - argTypes.ToArray()); + MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, + methodAttribs, + returnType, + argTypes.ToArray()); - ILGenerator il = methodBuilder.GetILGenerator(); + ILGenerator il = methodBuilder.GetILGenerator(); - il.DeclareLocal(typeof(object[])); - il.DeclareLocal(typeof(RuntimeMethodHandle)); + il.DeclareLocal(typeof(object[])); + il.DeclareLocal(typeof(RuntimeMethodHandle)); - // this - il.Emit(OpCodes.Ldarg_0); + // this + il.Emit(OpCodes.Ldarg_0); - // Python method to call - il.Emit(OpCodes.Ldstr, methodName); + // Python method to call + il.Emit(OpCodes.Ldstr, methodName); - // original method name - il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method + // original method name + il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method - // create args array - il.Emit(OpCodes.Ldc_I4, argTypes.Count); - il.Emit(OpCodes.Newarr, typeof(object)); - il.Emit(OpCodes.Stloc_0); + // create args array + il.Emit(OpCodes.Ldc_I4, argTypes.Count); + il.Emit(OpCodes.Newarr, typeof(object)); + il.Emit(OpCodes.Stloc_0); - // fill args array - for (var i = 0; i < argTypes.Count; ++i) + // fill args array + for (var i = 0; i < argTypes.Count; ++i) + { + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldarg, i + 1); + var type = argTypes[i]; + if (type.IsByRef) { - il.Emit(OpCodes.Ldloc_0); - il.Emit(OpCodes.Ldc_I4, i); - il.Emit(OpCodes.Ldarg, i + 1); - var type = argTypes[i]; - if (type.IsByRef) - { - type = type.GetElementType(); - il.Emit(OpCodes.Ldobj, type); - } - if (type.IsValueType) - { - il.Emit(OpCodes.Box, type); - } - il.Emit(OpCodes.Stelem, typeof(object)); + type = type.GetElementType(); + il.Emit(OpCodes.Ldobj, type); + } + if (type.IsValueType) + { + il.Emit(OpCodes.Box, type); } + il.Emit(OpCodes.Stelem, typeof(object)); + } - // args array - il.Emit(OpCodes.Ldloc_0); + // args array + il.Emit(OpCodes.Ldloc_0); - // method handle for the base method is null - il.Emit(OpCodes.Ldloca_S, 1); - il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle)); - il.Emit(OpCodes.Ldloc_1); + // method handle for the base method is null + il.Emit(OpCodes.Ldloca_S, 1); + il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle)); + il.Emit(OpCodes.Ldloc_1); #pragma warning disable CS0618 // PythonDerivedType is for internal use only - // invoke the method - if (returnType == typeof(void)) - { - il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid))); - } - else - { - il.Emit(OpCodes.Call, - typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(returnType)); - } + // invoke the method + if (returnType == typeof(void)) + { + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid))); + } + else + { + il.Emit(OpCodes.Call, + typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(returnType)); + } - CodeGenerator.GenerateMarshalByRefsBack(il, argTypes); + CodeGenerator.GenerateMarshalByRefsBack(il, argTypes); #pragma warning restore CS0618 // PythonDerivedType is for internal use only - il.Emit(OpCodes.Ret); - } + il.Emit(OpCodes.Ret); } /// @@ -581,68 +581,62 @@ private static void AddPythonProperty(string propertyName, PyObject func, TypeBu MethodAttributes.HideBySig | MethodAttributes.SpecialName; - using (PyObject pyPropertyType = func.GetAttr("_clr_property_type_")) + using var pyPropertyType = func.GetAttr("_clr_property_type_"); + var propertyType = pyPropertyType.AsManagedObject(typeof(Type)) as Type; + if (propertyType == null) { - var propertyType = pyPropertyType.AsManagedObject(typeof(Type)) as Type; - if (propertyType == null) - { - throw new ArgumentException("_clr_property_type must be a CLR type"); - } + throw new ArgumentException("_clr_property_type must be a CLR type"); + } - PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, - PropertyAttributes.None, - propertyType, - null); + PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, + PropertyAttributes.None, + propertyType, + null); - if (func.HasAttr("fget")) + if (func.HasAttr("fget")) + { + using var pyfget = func.GetAttr("fget"); + if (pyfget.IsTrue()) { - using (PyObject pyfget = func.GetAttr("fget")) - { - if (pyfget.IsTrue()) - { - MethodBuilder methodBuilder = typeBuilder.DefineMethod("get_" + propertyName, - methodAttribs, - propertyType, - null); - - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldstr, propertyName); + MethodBuilder methodBuilder = typeBuilder.DefineMethod("get_" + propertyName, + methodAttribs, + propertyType, + null); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, propertyName); #pragma warning disable CS0618 // PythonDerivedType is for internal use only - il.Emit(OpCodes.Call, - typeof(PythonDerivedType).GetMethod("InvokeGetProperty").MakeGenericMethod(propertyType)); + il.Emit(OpCodes.Call, + typeof(PythonDerivedType).GetMethod("InvokeGetProperty").MakeGenericMethod(propertyType)); #pragma warning restore CS0618 // PythonDerivedType is for internal use only - il.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ret); - propertyBuilder.SetGetMethod(methodBuilder); - } - } + propertyBuilder.SetGetMethod(methodBuilder); } + } - if (func.HasAttr("fset")) + if (func.HasAttr("fset")) + { + using var pyset = func.GetAttr("fset"); + if (pyset.IsTrue()) { - using (PyObject pyset = func.GetAttr("fset")) - { - if (pyset.IsTrue()) - { - MethodBuilder methodBuilder = typeBuilder.DefineMethod("set_" + propertyName, - methodAttribs, - null, - new[] { propertyType }); - - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldstr, propertyName); - il.Emit(OpCodes.Ldarg_1); + MethodBuilder methodBuilder = typeBuilder.DefineMethod("set_" + propertyName, + methodAttribs, + null, + new[] { propertyType }); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, propertyName); + il.Emit(OpCodes.Ldarg_1); #pragma warning disable CS0618 // PythonDerivedType is for internal use only - il.Emit(OpCodes.Call, - typeof(PythonDerivedType).GetMethod("InvokeSetProperty").MakeGenericMethod(propertyType)); + il.Emit(OpCodes.Call, + typeof(PythonDerivedType).GetMethod("InvokeSetProperty").MakeGenericMethod(propertyType)); #pragma warning restore CS0618 // PythonDerivedType is for internal use only - il.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ret); - propertyBuilder.SetSetMethod(methodBuilder); - } - } + propertyBuilder.SetSetMethod(methodBuilder); } } } From df3b569eb1f8da0524d1322139aab520918f2a1b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 23 Nov 2021 19:47:01 +0100 Subject: [PATCH 017/183] Fix enum codec A boxed enum value can't be casted directly to an integer, but using `System.Convert` functions instead works fine. --- src/runtime/Codecs/EnumPyIntCodec.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/runtime/Codecs/EnumPyIntCodec.cs b/src/runtime/Codecs/EnumPyIntCodec.cs index 7d33b34ce..34030ffb0 100644 --- a/src/runtime/Codecs/EnumPyIntCodec.cs +++ b/src/runtime/Codecs/EnumPyIntCodec.cs @@ -53,14 +53,7 @@ public bool TryDecode(PyObject pyObj, out T? value) var enumType = value.GetType(); if (!enumType.IsEnum) return null; - try - { - return new PyInt((long)value); - } - catch (InvalidCastException) - { - return new PyInt((ulong)value); - } + return new PyInt(Convert.ToInt64(value)); } private EnumPyIntCodec() { } From 4f070f24c3e7500b73d88d62a827f75be8f676ac Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 4 May 2022 08:33:32 +0200 Subject: [PATCH 018/183] Add unit test for enum encoder --- tests/test_codec.py | 68 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/tests/test_codec.py b/tests/test_codec.py index 9744d3856..6c74bd8eb 100644 --- a/tests/test_codec.py +++ b/tests/test_codec.py @@ -1,39 +1,53 @@ # -*- coding: utf-8 -*- """Test conversions using codecs from client python code""" -import clr -import System + import pytest import Python.Runtime +import Python.Test as Test from Python.Test import ListConversionTester, ListMember, CodecResetter -class int_iterable(): + +@pytest.fixture(autouse=True) +def reset(): + yield + CodecResetter.Reset() + + +class int_iterable: def __init__(self): self.counter = 0 + def __iter__(self): return self + def __next__(self): if self.counter == 3: raise StopIteration self.counter = self.counter + 1 return self.counter -class obj_iterable(): + +class obj_iterable: def __init__(self): self.counter = 0 + def __iter__(self): return self + def __next__(self): if self.counter == 3: raise StopIteration self.counter = self.counter + 1 return ListMember(self.counter, "Number " + str(self.counter)) + def test_iterable(): - """Test that a python iterable can be passed into a function that takes an IEnumerable""" + """Test that a python iterable can be passed into a function that takes an + IEnumerable""" - #Python.Runtime.Codecs.ListDecoder.Register() - #Python.Runtime.Codecs.SequenceDecoder.Register() + # Python.Runtime.Codecs.ListDecoder.Register() + # Python.Runtime.Codecs.SequenceDecoder.Register() Python.Runtime.Codecs.IterableDecoder.Register() ob = ListConversionTester() @@ -43,28 +57,58 @@ def test_iterable(): iterable2 = obj_iterable() assert 3 == ob.GetLength2(iterable2) - CodecResetter.Reset() def test_sequence(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() - tup = (1,2,3) + tup = (1, 2, 3) assert 3 == ob.GetLength(tup) tup2 = (ListMember(1, "one"), ListMember(2, "two"), ListMember(3, "three")) assert 3 == ob.GetLength(tup2) - CodecResetter.Reset() def test_list(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() - l = [1,2,3] + l = [1, 2, 3] assert 3 == ob.GetLength(l) l2 = [ListMember(1, "one"), ListMember(2, "two"), ListMember(3, "three")] assert 3 == ob.GetLength(l2) - CodecResetter.Reset() + +def test_enum(): + Python.Runtime.PyObjectConversions.RegisterEncoder( + Python.Runtime.Codecs.EnumPyIntCodec.Instance + ) + + assert Test.ByteEnum.Zero == 0 + assert Test.ByteEnum.One == 1 + assert Test.ByteEnum.Two == 2 + assert Test.SByteEnum.Zero == 0 + assert Test.SByteEnum.One == 1 + assert Test.SByteEnum.Two == 2 + assert Test.ShortEnum.Zero == 0 + assert Test.ShortEnum.One == 1 + assert Test.ShortEnum.Two == 2 + assert Test.UShortEnum.Zero == 0 + assert Test.UShortEnum.One == 1 + assert Test.UShortEnum.Two == 2 + assert Test.IntEnum.Zero == 0 + assert Test.IntEnum.One == 1 + assert Test.IntEnum.Two == 2 + assert Test.UIntEnum.Zero == 0 + assert Test.UIntEnum.One == 1 + assert Test.UIntEnum.Two == 2 + assert Test.LongEnum.Zero == 0 + assert Test.LongEnum.One == 1 + assert Test.LongEnum.Two == 2 + assert Test.ULongEnum.Zero == 0 + assert Test.ULongEnum.One == 1 + assert Test.ULongEnum.Two == 2 + assert Test.LongEnum.Max == 9223372036854775807 + assert Test.LongEnum.Min == -9223372036854775808 + assert int(Test.ULongEnum.Max) == 18446744073709551615 From 7d6e27a23ec88138ada829f50e473cea394df189 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 4 May 2022 08:40:50 +0200 Subject: [PATCH 019/183] Allow conversion of UInt64 based enums --- src/runtime/Codecs/EnumPyIntCodec.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/runtime/Codecs/EnumPyIntCodec.cs b/src/runtime/Codecs/EnumPyIntCodec.cs index 34030ffb0..42f5eb1b2 100644 --- a/src/runtime/Codecs/EnumPyIntCodec.cs +++ b/src/runtime/Codecs/EnumPyIntCodec.cs @@ -53,7 +53,14 @@ public bool TryDecode(PyObject pyObj, out T? value) var enumType = value.GetType(); if (!enumType.IsEnum) return null; - return new PyInt(Convert.ToInt64(value)); + try + { + return new PyInt(Convert.ToInt64(value)); + } + catch (OverflowException) + { + return new PyInt(Convert.ToUInt64(value)); + } } private EnumPyIntCodec() { } From a80c685d197d47e959d65f82941068286c481276 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 4 May 2022 11:11:13 -0700 Subject: [PATCH 020/183] disallow runtime shutdown when the Python error indicator is set, as it may lead to unpredictable behavior (#1780) --- CHANGELOG.md | 1 + src/runtime/PythonEngine.cs | 6 ++++++ src/runtime/PythonException.cs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 766258c5d..ea0f1f7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and other `PyObject` derived types when called from Python. details about the cause of the failure - `clr.AddReference` no longer adds ".dll" implicitly - `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method +- Python runtime can no longer be shut down if the Python error indicator is set, as it would have unpredictable behavior - BREAKING: Return values from .NET methods that return an interface are now automatically wrapped in that interface. This is a breaking change for users that rely on being able to access members that are part of the implementation class, but not the diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 24e1bedeb..c4c9fad61 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -351,6 +351,12 @@ public static void Shutdown() { return; } + if (Exceptions.ErrorOccurred()) + { + throw new InvalidOperationException( + "Python error indicator is set", + innerException: PythonException.PeekCurrentOrNull(out _)); + } // If the shutdown handlers trigger a domain unload, // don't call shutdown again. AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; diff --git a/src/runtime/PythonException.cs b/src/runtime/PythonException.cs index 8d3330c7b..e4d38c362 100644 --- a/src/runtime/PythonException.cs +++ b/src/runtime/PythonException.cs @@ -75,6 +75,23 @@ internal static PythonException FetchCurrentRaw() => FetchCurrentOrNullRaw() ?? throw new InvalidOperationException("No exception is set"); + internal static Exception? PeekCurrentOrNull(out ExceptionDispatchInfo? dispatchInfo) + { + using var _ = new Py.GILState(); + + Runtime.PyErr_Fetch(out var type, out var value, out var traceback); + Runtime.PyErr_Restore( + new NewReference(type, canBeNull: true).StealNullable(), + new NewReference(value, canBeNull: true).StealNullable(), + new NewReference(traceback, canBeNull: true).StealNullable()); + + var err = FetchCurrentOrNull(out dispatchInfo); + + Runtime.PyErr_Restore(type.StealNullable(), value.StealNullable(), traceback.StealNullable()); + + return err; + } + internal static Exception? FetchCurrentOrNull(out ExceptionDispatchInfo? dispatchInfo) { dispatchInfo = null; From 2910800eb6f04fbe1fc70645b8c1820e6e192369 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 6 May 2022 23:59:33 -0700 Subject: [PATCH 021/183] type name generator ignored names of nested classes --- src/runtime/TypeManager.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index d9ca184f6..217b4820e 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -217,6 +217,23 @@ static void GetPythonTypeName(Type clrType, System.Text.StringBuilder target) } target.Append(']'); + + int nestedStart = fullName.IndexOf('+'); + while (nestedStart >= 0) + { + target.Append('.'); + int nextNested = fullName.IndexOf('+', nestedStart + 1); + if (nextNested < 0) + { + target.Append(fullName.Substring(nestedStart + 1)); + } + else + { + target.Append(fullName.Substring(nestedStart + 1, length: nextNested - nestedStart - 1)); + } + nestedStart = nextNested; + } + return; } } From cf8823fa941842cfd19ad808f04bc6aa6d0b7970 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 7 May 2022 10:03:50 -0700 Subject: [PATCH 022/183] added regression test for "in Dictionary.Keys" check --- tests/test_collection_mixins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_collection_mixins.py b/tests/test_collection_mixins.py index 2f74e93ab..0ac040038 100644 --- a/tests/test_collection_mixins.py +++ b/tests/test_collection_mixins.py @@ -14,3 +14,10 @@ def test_dict_items(): k,v = items[0] assert k == 42 assert v == "a" + +# regression test for https://github.com/pythonnet/pythonnet/issues/1785 +def test_dict_in_keys(): + d = C.Dictionary[str, int]() + d["a"] = 42 + assert "a" in d.Keys + assert "b" not in d.Keys From f48d7a96f76cf1e44411c3d19ad036e96b9adaf0 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 7 May 2022 10:05:36 -0700 Subject: [PATCH 023/183] ClassGeneric.GetClass now returns BorrowedReference to indicate that the value should not be disposed --- src/runtime/ClassManager.cs | 2 +- src/runtime/Types/ClassObject.cs | 2 +- src/runtime/Types/ClrObject.cs | 4 ++-- src/runtime/Types/MethodObject.cs | 2 +- src/runtime/Types/ReflectedClrType.cs | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 6379f51de..6c5558f3a 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -133,7 +133,7 @@ internal static void RestoreRuntimeData(ClassManagerState storage) /// Return the ClassBase-derived instance that implements a particular /// reflected managed type, creating it if it doesn't yet exist. /// - internal static ReflectedClrType GetClass(Type type) => ReflectedClrType.GetOrCreate(type); + internal static BorrowedReference GetClass(Type type) => ReflectedClrType.GetOrCreate(type); internal static ClassBase GetClassImpl(Type type) { diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 04613afa5..474e9dd7b 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -236,7 +236,7 @@ public override NewReference type_subscript(BorrowedReference idx) return Exceptions.RaiseTypeError("type expected"); } Type a = t.MakeArrayType(); - PyType o = ClassManager.GetClass(a); + BorrowedReference o = ClassManager.GetClass(a); return new NewReference(o); } diff --git a/src/runtime/Types/ClrObject.cs b/src/runtime/Types/ClrObject.cs index db6e99121..4cf9062cb 100644 --- a/src/runtime/Types/ClrObject.cs +++ b/src/runtime/Types/ClrObject.cs @@ -43,13 +43,13 @@ internal static NewReference GetReference(object ob, BorrowedReference pyType) internal static NewReference GetReference(object ob, Type type) { - PyType cc = ClassManager.GetClass(type); + BorrowedReference cc = ClassManager.GetClass(type); return Create(ob, cc); } internal static NewReference GetReference(object ob) { - PyType cc = ClassManager.GetClass(ob.GetType()); + BorrowedReference cc = ClassManager.GetClass(ob.GetType()); return Create(ob, cc); } diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 0bdd11ac2..55ad06e2d 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -191,7 +191,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference && obj.inst is IPythonDerivedType && self.type.Value.IsInstanceOfType(obj.inst)) { - var basecls = ClassManager.GetClass(self.type.Value); + var basecls = ReflectedClrType.GetOrCreate(self.type.Value); return new MethodBinding(self, new PyObject(ob), basecls).Alloc(); } diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 15ea5c2b2..2e8f95924 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -12,6 +12,7 @@ internal sealed class ReflectedClrType : PyType { private ReflectedClrType(StolenReference reference) : base(reference, prevalidated: true) { } internal ReflectedClrType(ReflectedClrType original) : base(original, prevalidated: true) { } + internal ReflectedClrType(BorrowedReference original) : base(original) { } ReflectedClrType(SerializationInfo info, StreamingContext context) : base(info, context) { } internal ClassBase Impl => (ClassBase)ManagedType.GetManagedObject(this)!; From 537ddf4b7fc938bca461b1ffb532a136af7b7d67 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 7 May 2022 10:15:16 -0700 Subject: [PATCH 024/183] allow casting objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity --- CHANGELOG.md | 1 + src/runtime/Types/GenericType.cs | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0f1f7bb..afd2f58c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - `__name__` and `__signature__` to reflected .NET methods - .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). +- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity. - .NET arrays implement Python buffer protocol - Python integer interoperability with `System.Numerics.BigInteger` - Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, diff --git a/src/runtime/Types/GenericType.cs b/src/runtime/Types/GenericType.cs index 6b537931e..380ca8875 100644 --- a/src/runtime/Types/GenericType.cs +++ b/src/runtime/Types/GenericType.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Python.Runtime { @@ -20,10 +21,58 @@ internal GenericType(Type tp) : base(tp) /// public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, BorrowedReference kw) { + var self = (GenericType)GetManagedObject(tp)!; + if (!self.type.Valid) + { + return Exceptions.RaiseTypeError(self.type.DeletedMessage); + } + var type = self.type.Value; + + if (type.IsInterface && !type.IsConstructedGenericType) + { + var nargs = Runtime.PyTuple_Size(args); + if (nargs == 1) + { + var instance = Runtime.PyTuple_GetItem(args, 0); + return AsGenericInterface(instance, type); + } + } + Exceptions.SetError(Exceptions.TypeError, "cannot instantiate an open generic type"); + return default; } + static NewReference AsGenericInterface(BorrowedReference instance, Type targetType) + { + if (GetManagedObject(instance) is not CLRObject obj) + { + return Exceptions.RaiseTypeError("only .NET objects can be cast to .NET interfaces"); + } + + Type[] supportedInterfaces = obj.inst.GetType().GetInterfaces(); + Type[] constructedInterfaces = supportedInterfaces + .Where(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == targetType) + .ToArray(); + + if (constructedInterfaces.Length == 1) + { + BorrowedReference pythonic = ClassManager.GetClass(constructedInterfaces[0]); + using var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, instance); + return Runtime.PyObject_CallObject(pythonic, args.Borrow()); + } + + if (constructedInterfaces.Length > 1) + { + string interfaces = string.Join(", ", constructedInterfaces.Select(TypeManager.GetPythonTypeName)); + return Exceptions.RaiseTypeError("Ambiguous cast to .NET interface. " + + $"Object implements: {interfaces}"); + } + + return Exceptions.RaiseTypeError("object does not implement " + + TypeManager.GetPythonTypeName(targetType)); + } /// /// Implements __call__ for reflected generic types. From d8543321a32535b90789770219f0d7c1ff0265de Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 7 May 2022 10:16:43 -0700 Subject: [PATCH 025/183] a few collection mixins now properly handle private interface implementations fixes https://github.com/pythonnet/pythonnet/issues/1785 --- src/runtime/Mixins/collections.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/runtime/Mixins/collections.py b/src/runtime/Mixins/collections.py index a82032472..3203ad96f 100644 --- a/src/runtime/Mixins/collections.py +++ b/src/runtime/Mixins/collections.py @@ -7,7 +7,11 @@ class IteratorMixin(col.Iterator): def close(self): - self.Dispose() + if hasattr(self, 'Dispose'): + self.Dispose() + else: + from System import IDisposable + IDisposable(self).Dispose() class IterableMixin(col.Iterable): pass @@ -16,7 +20,12 @@ class SizedMixin(col.Sized): def __len__(self): return self.Count class ContainerMixin(col.Container): - def __contains__(self, item): return self.Contains(item) + def __contains__(self, item): + if hasattr('self', 'Contains'): + return self.Contains(item) + else: + from System.Collections.Generic import ICollection + return ICollection(self).Contains(item) try: abc_Collection = col.Collection From e258cee22bbe7e97bb0a333af5c45541fe2dea1b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 23 May 2022 08:29:24 +0200 Subject: [PATCH 026/183] Drop Python 3.6 support (#1795) Fixes https://github.com/pythonnet/pythonnet/issues/1640 Co-authored-by: Victor Nova --- .github/workflows/main.yml | 2 +- CHANGELOG.md | 2 +- setup.py | 1 - src/runtime/Mixins/collections.py | 2 +- src/runtime/Native/TypeOffset36.cs | 136 ----------------------------- src/runtime/Util/Util.cs | 2 +- 6 files changed, 4 insertions(+), 141 deletions(-) delete mode 100644 src/runtime/Native/TypeOffset36.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 284658620..2a77682f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [windows, ubuntu, macos] - python: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python: ["3.7", "3.8", "3.9", "3.10"] platform: [x64, x86] exclude: - os: ubuntu diff --git a/CHANGELOG.md b/CHANGELOG.md index afd2f58c4..31deb70ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ and other `PyObject` derived types when called from Python. ### Changed -- Drop support for Python 2, 3.4, and 3.5 +- Drop support for Python 2, 3.4, 3.5, and 3.6 - `wchar_t` size aka `Runtime.UCS` is now determined at runtime - `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more details about the cause of the failure diff --git a/setup.py b/setup.py index 527bcc893..84f6f3ad2 100644 --- a/setup.py +++ b/setup.py @@ -165,7 +165,6 @@ def finalize_options(self): "License :: OSI Approved :: MIT License", "Programming Language :: C#", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/src/runtime/Mixins/collections.py b/src/runtime/Mixins/collections.py index 3203ad96f..95a6d8162 100644 --- a/src/runtime/Mixins/collections.py +++ b/src/runtime/Mixins/collections.py @@ -1,6 +1,6 @@ """ Implements collections.abc for common .NET types -https://docs.python.org/3.6/library/collections.abc.html +https://docs.python.org/3/library/collections.abc.html """ import collections.abc as col diff --git a/src/runtime/Native/TypeOffset36.cs b/src/runtime/Native/TypeOffset36.cs deleted file mode 100644 index 4b3b8bfb9..000000000 --- a/src/runtime/Native/TypeOffset36.cs +++ /dev/null @@ -1,136 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.6: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset36 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset36() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_print { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - } -} diff --git a/src/runtime/Util/Util.cs b/src/runtime/Util/Util.cs index 89f5bdf4c..2429c460b 100644 --- a/src/runtime/Util/Util.cs +++ b/src/runtime/Util/Util.cs @@ -13,7 +13,7 @@ internal static class Util internal const string UnstableApiMessage = "This API is unstable, and might be changed or removed in the next minor release"; internal const string MinimalPythonVersionRequired = - "Only Python 3.6 or newer is supported"; + "Only Python 3.7 or newer is supported"; internal const string InternalUseOnly = "This API is for internal use only"; From 987b2eec097b9c83920ea8285d5a97d6a19064b5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 23 May 2022 21:54:35 +0200 Subject: [PATCH 027/183] Move to modern setuptools with pyproject.toml (#1793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move to modern setuptools with pyproject.toml Also moves the version to a common ̀`version.txt` that is read by both the .NET and Python builds. * Allow explicitly overriding the version suffix for .NET builds --- Directory.Build.props | 7 ++++-- MANIFEST.in | 1 + pyproject.toml | 48 ++++++++++++++++++++++++++++++++++++++- setup.py | 53 ++++++------------------------------------- version.txt | 1 + 5 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 version.txt diff --git a/Directory.Build.props b/Directory.Build.props index 496060877..8c5b53685 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,14 @@ + - 3.0.0 - Copyright (c) 2006-2021 The Contributors of the Python.NET Project + Copyright (c) 2006-2022 The Contributors of the Python.NET Project pythonnet Python.NET 10.0 false + $([System.IO.File]::ReadAllText("version.txt")) + $(FullVersion.Split('-', 2)[0]) + $(FullVersion.Split('-', 2)[1]) diff --git a/MANIFEST.in b/MANIFEST.in index 01f4156ca..4763ae70f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ recursive-include src/ * include Directory.Build.* include pythonnet.sln +include version.txt global-exclude **/obj/* **/bin/* diff --git a/pyproject.toml b/pyproject.toml index b6df82f71..5ee89d3b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,53 @@ [build-system] -requires = ["setuptools>=42", "wheel", "pycparser"] +requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" +[project] +name = "pythonnet" +description = ".NET and Mono integration for Python" +license = {text = "MIT"} + +readme = "README.rst" + +dependencies = [ + "clr_loader>=0.1.7" +] + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: C#", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", +] + +dynamic = ["version"] + +[[project.authors]] +name = "The Contributors of the Python.NET Project" +email = "pythonnet@python.org" + +[project.urls] +Homepage = "https://pythonnet.github.io/" +Sources = "https://github.com/pythonnet/pythonnet" + +[tool.setuptools] +zip-safe = false +py-modules = ["clr"] + +[tool.setuptools.dynamic.version] +file = "version.txt" + +[tool.setuptools.packages.find] +include = ["pythonnet*"] + [tool.pytest.ini_options] xfail_strict = true testpaths = [ diff --git a/setup.py b/setup.py index 84f6f3ad2..7c02b7710 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,18 @@ #!/usr/bin/env python -from setuptools import setup, Command, Extension -from setuptools.command.build_ext import build_ext import distutils -from distutils.command import build -from subprocess import check_output, check_call +from distutils.command.build import build as _build +from setuptools.command.develop import develop as _develop +from wheel.bdist_wheel import bdist_wheel as _bdist_wheel +from setuptools import Distribution +from setuptools import setup, Command -import sys, os +import os # Disable SourceLink during the build until it can read repo-format v1, #1613 os.environ["EnableSourceControlManagerQueries"] = "false" + class DotnetLib: def __init__(self, name, path, **kwargs): self.name = name @@ -91,13 +93,6 @@ def run(self): # Add build_dotnet to the build tasks: -from distutils.command.build import build as _build -from setuptools.command.develop import develop as _develop -from wheel.bdist_wheel import bdist_wheel as _bdist_wheel -from setuptools import Distribution -import setuptools - - class build(_build): sub_commands = _build.sub_commands + [("build_dotnet", None)] @@ -129,10 +124,6 @@ def finalize_options(self): "bdist_wheel": bdist_wheel, } - -with open("README.rst", "r") as f: - long_description = f.read() - dotnet_libs = [ DotnetLib( "python-runtime", @@ -143,35 +134,5 @@ def finalize_options(self): setup( cmdclass=cmdclass, - name="pythonnet", - version="3.0.0.dev1", - description=".Net and Mono integration for Python", - url="https://pythonnet.github.io/", - project_urls={ - "Source": "https://github.com/pythonnet/pythonnet", - }, - license="MIT", - author="The Contributors of the Python.NET Project", - author_email="pythonnet@python.org", - packages=["pythonnet", "pythonnet.find_libpython"], - install_requires=["clr_loader >= 0.1.7"], - long_description=long_description, - long_description_content_type="text/x-rst", - py_modules=["clr"], dotnet_libs=dotnet_libs, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: C#", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS :: MacOS X", - ], - zip_safe=False, ) diff --git a/version.txt b/version.txt new file mode 100644 index 000000000..e16bcc8be --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +3.0.0-dev1 From 1492d7d7c682257833b9f05c217186277a1567d1 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 25 May 2022 11:37:48 -0700 Subject: [PATCH 028/183] expose Min/MaxSupportedVersion and IsSupportedVersion on PythonEngine fixes https://github.com/pythonnet/pythonnet/discussions/1724 --- CHANGELOG.md | 1 + src/runtime/PythonEngine.cs | 4 ++++ tests/test_engine.py | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31deb70ac..069629021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py). and other `PyObject` derived types when called from Python. - .NET classes, that have `__call__` method are callable from Python - `PyIterable` type, that wraps any iterable object in Python +- `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` ### Changed diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index c4c9fad61..f184ebe88 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -127,6 +127,10 @@ public static string PythonPath } } + public static Version MinSupportedVersion => new(3, 7); + public static Version MaxSupportedVersion => new(3, 10, int.MaxValue, int.MaxValue); + public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; + public static string Version { get { return Marshal.PtrToStringAnsi(Runtime.Py_GetVersion()); } diff --git a/tests/test_engine.py b/tests/test_engine.py index 06a44d561..65aaa3ce8 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -19,6 +19,14 @@ def test_multiple_calls_to_initialize(): assert False # Initialize() raise an exception. +def test_supported_version(): + major, minor, build, *_ = sys.version_info + ver = System.Version(major, minor, build) + assert PythonEngine.IsSupportedVersion(ver) + assert ver >= PythonEngine.MinSupportedVersion + assert ver <= PythonEngine.MaxSupportedVersion + + @pytest.mark.skip(reason="FIXME: test crashes") def test_import_module(): """Test module import.""" From 26d1039c9702746c8deb50db1b2aee34a0c27dab Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 26 May 2022 20:58:28 +0200 Subject: [PATCH 029/183] Ensure that codecs are definitely clean in tests --- tests/test_codec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_codec.py b/tests/test_codec.py index 6c74bd8eb..0c1fb44f4 100644 --- a/tests/test_codec.py +++ b/tests/test_codec.py @@ -10,6 +10,7 @@ @pytest.fixture(autouse=True) def reset(): + CodecResetter.Reset() yield CodecResetter.Reset() From 2e1652b8f8878d9db3f1676e3f3a97674fcc3ecb Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 26 May 2022 21:05:34 +0200 Subject: [PATCH 030/183] Ensure that the RawProxyEncoder is removed again --- tests/test_conversion.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 4de286b14..6693d8000 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -4,7 +4,7 @@ import pytest import System -from Python.Test import ConversionTest, MethodResolutionInt, UnicodeString +from Python.Test import ConversionTest, MethodResolutionInt, UnicodeString, CodecResetter from Python.Runtime import PyObjectConversions from Python.Runtime.Codecs import RawProxyEncoder @@ -659,6 +659,8 @@ def CanEncode(self, clr_type): l.Add(42) assert ob.ListField.Count == 1 + CodecResetter.Reset() + def test_int_param_resolution_required(): """Test resolution of `int` parameters when resolution is needed""" From c1dab2712006badcbf69ac1d1390ef1ca75175d1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 31 May 2022 06:36:09 +0200 Subject: [PATCH 031/183] Add explicit tests for vararg call (#1805) --- src/embed_tests/NumPyTests.cs | 65 ++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index 8b76f4ca1..e102ddb99 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; + using NUnit.Framework; + using Python.Runtime; using Python.Runtime.Codecs; @@ -24,17 +26,6 @@ public void Dispose() [Test] public void TestReadme() { - dynamic np; - try - { - np = Py.Import("numpy"); - } - catch (PythonException) - { - Assert.Inconclusive("Numpy or dependency not installed"); - return; - } - Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); dynamic sin = np.sin; @@ -55,17 +46,9 @@ public void TestReadme() [Test] public void MultidimensionalNumPyArray() { - PyObject np; - try { - np = Py.Import("numpy"); - } catch (PythonException) { - Assert.Inconclusive("Numpy or dependency not installed"); - return; - } - var array = new[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2,2), ndarray.GetAttr("shape").As<(int,int)>()); + Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); } @@ -73,22 +56,42 @@ public void MultidimensionalNumPyArray() [Test] public void Int64Array() { - PyObject np; - try - { - np = Py.Import("numpy"); - } - catch (PythonException) - { - Assert.Inconclusive("Numpy or dependency not installed"); - return; - } - var array = new long[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); } + + [Test] + public void VarArg() + { + dynamic zX = np.array(new[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 8, 9, 0 } }); + dynamic grad = np.gradient(zX, 4.0, 5.0); + dynamic grad2 = np.InvokeMethod("gradient", new PyObject[] {zX, new PyFloat(4.0), new PyFloat(5.0)}); + + Assert.AreEqual(4.125, grad[0].sum().__float__().As(), 0.001); + Assert.AreEqual(-1.2, grad[1].sum().__float__().As(), 0.001); + Assert.AreEqual(4.125, grad2[0].sum().__float__().As(), 0.001); + Assert.AreEqual(-1.2, grad2[1].sum().__float__().As(), 0.001); + } + +#pragma warning disable IDE1006 + dynamic np + { + get + { + try + { + return Py.Import("numpy"); + } + catch (PythonException) + { + Assert.Inconclusive("Numpy or dependency not installed"); + return null; + } + } + } + } } From 31b9c4e56da54b9cde81630ea7cf032024624347 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 31 May 2022 14:47:29 +0200 Subject: [PATCH 032/183] Add necessary `Initialize()` call to the README --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index d5b280bfa..c2c2d53e1 100644 --- a/README.rst +++ b/README.rst @@ -69,6 +69,7 @@ Example static void Main(string[] args) { + PythonEngine.Initialize(); using (Py.GIL()) { dynamic np = Py.Import("numpy"); From 5ee587dbba7b2aec1fa1fc27c57a21a1093d731e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 30 May 2022 00:23:00 +0200 Subject: [PATCH 033/183] Fix demo scripts --- demo/helloform.py | 4 ++-- demo/splitter.py | 10 ++++++---- demo/wordpad.py | 9 ++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/demo/helloform.py b/demo/helloform.py index 6e404771a..503d1c8b5 100644 --- a/demo/helloform.py +++ b/demo/helloform.py @@ -3,8 +3,7 @@ import clr -SWF = clr.AddReference("System.Windows.Forms") -print (SWF.Location) +clr.AddReference("System.Windows.Forms") import System.Windows.Forms as WinForms from System.Drawing import Size, Point @@ -14,6 +13,7 @@ class HelloApp(WinForms.Form): winforms programming and event-based programming in Python.""" def __init__(self): + super().__init__() self.Text = "Hello World From Python" self.AutoScaleBaseSize = Size(5, 13) self.ClientSize = Size(392, 117) diff --git a/demo/splitter.py b/demo/splitter.py index 126499db6..c209de6ab 100644 --- a/demo/splitter.py +++ b/demo/splitter.py @@ -4,6 +4,7 @@ import clr import System +clr.AddReference("System.Windows.Forms") import System.Windows.Forms as WinForms from System.Drawing import Color, Size, Point @@ -14,6 +15,7 @@ class Splitter(WinForms.Form): 'Creating a Multipane User Interface with Windows Forms'.""" def __init__(self): + super().__init__() # Create an instance of each control being used. self.components = System.ComponentModel.Container() @@ -26,13 +28,13 @@ def __init__(self): # Set properties of TreeView control. self.treeView1.Dock = WinForms.DockStyle.Left - self.treeView1.Width = self.ClientSize.Width / 3 + self.treeView1.Width = self.ClientSize.Width // 3 self.treeView1.TabIndex = 0 self.treeView1.Nodes.Add("TreeView") # Set properties of ListView control. self.listView1.Dock = WinForms.DockStyle.Top - self.listView1.Height = self.ClientSize.Height * 2 / 3 + self.listView1.Height = self.ClientSize.Height * 2 // 3 self.listView1.TabIndex = 0 self.listView1.Items.Add("ListView") @@ -52,7 +54,7 @@ def __init__(self): self.splitter2.TabIndex = 1 # Set TabStop to false for ease of use when negotiating UI. - self.splitter2.TabStop = 0 + self.splitter2.TabStop = False # Set properties of Form's Splitter control. self.splitter1.Location = System.Drawing.Point(121, 0) @@ -61,7 +63,7 @@ def __init__(self): self.splitter1.TabIndex = 1 # Set TabStop to false for ease of use when negotiating UI. - self.splitter1.TabStop = 0 + self.splitter1.TabStop = False # Add the appropriate controls to the Panel. for item in (self.richTextBox1, self.splitter2, self.listView1): diff --git a/demo/wordpad.py b/demo/wordpad.py index 409c8ad4d..b0666024a 100644 --- a/demo/wordpad.py +++ b/demo/wordpad.py @@ -3,6 +3,7 @@ import clr import System +clr.AddReference("System.Windows.Forms") import System.Windows.Forms as WinForms from System.IO import File @@ -15,8 +16,9 @@ class Wordpad(WinForms.Form): """A simple example winforms application similar to wordpad.""" def __init__(self): + super().__init__() self.filename = '' - self.word_wrap = 1 + self.word_wrap = True self.doctype = 1 self.InitializeComponent() self.NewDocument() @@ -194,10 +196,10 @@ def InitializeComponent(self): self.richTextBox.Dock = WinForms.DockStyle.Fill self.richTextBox.Size = System.Drawing.Size(795, 485) self.richTextBox.TabIndex = 0 - self.richTextBox.AutoSize = 1 + self.richTextBox.AutoSize = True self.richTextBox.ScrollBars = WinForms.RichTextBoxScrollBars.ForcedBoth self.richTextBox.Font = System.Drawing.Font("Tahoma", 10.0) - self.richTextBox.AcceptsTab = 1 + self.richTextBox.AcceptsTab = True self.richTextBox.Location = System.Drawing.Point(0, 0) self.statusBar.BackColor = System.Drawing.SystemColors.Control @@ -360,6 +362,7 @@ def SaveChangesDialog(self): class AboutForm(WinForms.Form): def __init__(self): + super.__init__() self.InitializeComponent() def InitializeComponent(self): From a6e435339b18e387d9b80f93b1393201f08ae639 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 9 Jun 2022 07:14:16 +0200 Subject: [PATCH 034/183] Fix remaining issues with wordpad demo --- demo/wordpad.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/wordpad.py b/demo/wordpad.py index b0666024a..c7e998944 100644 --- a/demo/wordpad.py +++ b/demo/wordpad.py @@ -362,7 +362,7 @@ def SaveChangesDialog(self): class AboutForm(WinForms.Form): def __init__(self): - super.__init__() + super().__init__() self.InitializeComponent() def InitializeComponent(self): @@ -393,8 +393,8 @@ def InitializeComponent(self): self.Controls.AddRange((self.label1, self.btnClose)) self.FormBorderStyle = WinForms.FormBorderStyle.FixedDialog - self.MaximizeBox = 0 - self.MinimizeBox = 0 + self.MaximizeBox = False + self.MinimizeBox = False self.Name = "AboutForm" self.ShowInTaskbar = False self.StartPosition = WinForms.FormStartPosition.CenterScreen From 4d1e09a0765abcb633af859bfa96357373e500a9 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 13 Jun 2022 14:15:35 +0200 Subject: [PATCH 035/183] fixed ForbidPythonThreadsAttribute being ignored in certain scenarios (#1815) Co-authored-by: Victor Nova --- src/runtime/Types/MethodObject.cs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 55ad06e2d..05198da76 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -27,7 +27,7 @@ internal class MethodObject : ExtensionType internal PyString? doc; internal MaybeType type; - public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads) { this.type = type; this.name = name; @@ -45,6 +45,11 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t binder.allow_threads = allow_threads; } + public MethodObject(MaybeType type, string name, MethodBase[] info) + : this(type, name, info, allow_threads: AllowThreads(info)) + { + } + public bool IsInstanceConstructor => name == "__init__"; public MethodObject WithOverloads(MethodBase[] overloads) @@ -206,5 +211,27 @@ public static NewReference tp_repr(BorrowedReference ob) var self = (MethodObject)GetManagedObject(ob)!; return Runtime.PyString_FromString($""); } + + static bool AllowThreads(MethodBase[] methods) + { + bool hasAllowOverload = false, hasForbidOverload = false; + foreach (var method in methods) + { + bool forbidsThreads = method.GetCustomAttribute(inherit: false) != null; + if (forbidsThreads) + { + hasForbidOverload = true; + } + else + { + hasAllowOverload = true; + } + } + + if (hasAllowOverload && hasForbidOverload) + throw new NotImplementedException("All method overloads currently must either allow or forbid Python threads together"); + + return !hasForbidOverload; + } } } From 43d16404b2466b2ee5c495abcb91d2950624fbe4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 13 Jun 2022 15:33:59 +0200 Subject: [PATCH 036/183] Add test for simple enum int conversion (#1811) --- tests/test_enum.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_enum.py b/tests/test_enum.py index b2eb0569f..981fb735c 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -87,6 +87,11 @@ def test_ulong_enum(): assert Test.ULongEnum.Two == Test.ULongEnum(2) +def test_simple_enum_to_int(): + from System import DayOfWeek + assert int(DayOfWeek.Sunday) == 0 + + def test_long_enum_to_int(): assert int(Test.LongEnum.Max) == 9223372036854775807 assert int(Test.LongEnum.Min) == -9223372036854775808 @@ -138,6 +143,7 @@ def test_enum_undefined_value(): # explicitly permit undefined values Test.FieldTest().EnumField = Test.ShortEnum(20, True) + def test_enum_conversion(): """Test enumeration conversion.""" ob = Test.FieldTest() From f5de0bf9eb73708b80a75416a5e33c778e4228dc Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 14 Jun 2022 08:22:33 +0200 Subject: [PATCH 037/183] Drop vendored find_libpython --- .github/workflows/ARM.yml | 2 +- .github/workflows/main.yml | 4 +- .github/workflows/nuget-preview.yml | 2 +- pythonnet/find_libpython/__init__.py | 400 ----------------------- pythonnet/find_libpython/__main__.py | 2 - pythonnet/util/__init__.py | 1 - requirements.txt | 3 + tests/domain_tests/test_domain_reload.py | 2 +- 8 files changed, 8 insertions(+), 408 deletions(-) delete mode 100644 pythonnet/find_libpython/__init__.py delete mode 100644 pythonnet/find_libpython/__main__.py delete mode 100644 pythonnet/util/__init__.py diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index 66f68366d..db580f741 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -36,7 +36,7 @@ jobs: - name: Set Python DLL path (non Windows) run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Embedding tests run: dotnet test --logger "console;verbosity=detailed" src/embed_tests/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a77682f7..4a0fda9e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -57,12 +57,12 @@ jobs: - name: Set Python DLL path (non Windows) if: ${{ matrix.os != 'windows' }} run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Set Python DLL path (Windows) if: ${{ matrix.os == 'windows' }} run: | - python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONNET_PYDLL=$(python -m find_libpython)" - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 1dfa17d5a..d652f4b1e 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -46,7 +46,7 @@ jobs: - name: Set Python DLL path (non Windows) if: ${{ matrix.os != 'windows' }} run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Python Tests run: pytest diff --git a/pythonnet/find_libpython/__init__.py b/pythonnet/find_libpython/__init__.py deleted file mode 100644 index 3ae28970e..000000000 --- a/pythonnet/find_libpython/__init__.py +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env python - -""" -Locate libpython associated with this Python executable. -""" - -# License -# -# Copyright 2018, Takafumi Arakaki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from __future__ import print_function, absolute_import - -from logging import getLogger -import ctypes.util -import functools -import os -import sys -import sysconfig - -logger = getLogger("find_libpython") - -is_windows = os.name == "nt" -is_apple = sys.platform == "darwin" - -SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX") -if SHLIB_SUFFIX is None: - if is_windows: - SHLIB_SUFFIX = ".dll" - else: - SHLIB_SUFFIX = ".so" -if is_apple: - # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS. - # Let's not use the value from sysconfig. - SHLIB_SUFFIX = ".dylib" - - -def linked_libpython(): - """ - Find the linked libpython using dladdr (in *nix). - - Returns - ------- - path : str or None - A path to linked libpython. Return `None` if statically linked. - """ - if is_windows: - return _linked_libpython_windows() - return _linked_libpython_unix() - - -class Dl_info(ctypes.Structure): - _fields_ = [ - ("dli_fname", ctypes.c_char_p), - ("dli_fbase", ctypes.c_void_p), - ("dli_sname", ctypes.c_char_p), - ("dli_saddr", ctypes.c_void_p), - ] - - -def _linked_libpython_unix(): - if not sysconfig.get_config_var("Py_ENABLE_SHARED"): - return None - - libdl = ctypes.CDLL(ctypes.util.find_library("dl")) - libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] - libdl.dladdr.restype = ctypes.c_int - - dlinfo = Dl_info() - retcode = libdl.dladdr( - ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), - ctypes.pointer(dlinfo)) - if retcode == 0: # means error - return None - path = os.path.realpath(dlinfo.dli_fname.decode()) - return path - - -def _linked_libpython_windows(): - """ - Based on: https://stackoverflow.com/a/16659821 - """ - from ctypes.wintypes import HANDLE, LPWSTR, DWORD - - GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW - GetModuleFileName.argtypes = [HANDLE, LPWSTR, DWORD] - GetModuleFileName.restype = DWORD - - MAX_PATH = 260 - try: - buf = ctypes.create_unicode_buffer(MAX_PATH) - GetModuleFileName(ctypes.pythonapi._handle, buf, MAX_PATH) - return buf.value - except (ValueError, OSError): - return None - - - -def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): - """ - Convert a file basename `name` to a library name (no "lib" and ".so" etc.) - - >>> library_name("libpython3.7m.so") # doctest: +SKIP - 'python3.7m' - >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False) - 'python3.7m' - >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False) - 'python3.7m' - >>> library_name("python37.dll", suffix=".dll", is_windows=True) - 'python37' - """ - if not is_windows and name.startswith("lib"): - name = name[len("lib"):] - if suffix and name.endswith(suffix): - name = name[:-len(suffix)] - return name - - -def append_truthy(list, item): - if item: - list.append(item) - - -def uniquifying(items): - """ - Yield items while excluding the duplicates and preserving the order. - - >>> list(uniquifying([1, 2, 1, 2, 3])) - [1, 2, 3] - """ - seen = set() - for x in items: - if x not in seen: - yield x - seen.add(x) - - -def uniquified(func): - """ Wrap iterator returned from `func` by `uniquifying`. """ - @functools.wraps(func) - def wrapper(*args, **kwds): - return uniquifying(func(*args, **kwds)) - return wrapper - - -@uniquified -def candidate_names(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate file names of libpython. - - Yields - ------ - name : str - Candidate name libpython. - """ - LDLIBRARY = sysconfig.get_config_var("LDLIBRARY") - if LDLIBRARY and not LDLIBRARY.endswith(".a"): - yield LDLIBRARY - - LIBRARY = sysconfig.get_config_var("LIBRARY") - if LIBRARY and not LIBRARY.endswith(".a"): - yield os.path.splitext(LIBRARY)[0] + suffix - - dlprefix = "" if is_windows else "lib" - sysdata = dict( - v=sys.version_info, - # VERSION is X.Y in Linux/macOS and XY in Windows: - VERSION=(sysconfig.get_python_version() or - "{v.major}.{v.minor}".format(v=sys.version_info) or - sysconfig.get_config_var("VERSION")), - ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or - sysconfig.get_config_var("abiflags") or ""), - ) - - for stem in [ - "python{VERSION}{ABIFLAGS}".format(**sysdata), - "python{VERSION}".format(**sysdata), - "python{v.major}".format(**sysdata), - "python", - ]: - yield dlprefix + stem + suffix - - - -@uniquified -def candidate_paths(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate paths of libpython. - - Yields - ------ - path : str or None - Candidate path to libpython. The path may not be a fullpath - and may not exist. - """ - - yield linked_libpython() - - # List candidates for directories in which libpython may exist - lib_dirs = [] - append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL')) - append_truthy(lib_dirs, sysconfig.get_config_var('srcdir')) - append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) - - # LIBPL seems to be the right config_var to use. It is the one - # used in python-config when shared library is not enabled: - # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 - # - # But we try other places just in case. - - if is_windows: - lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) - else: - lib_dirs.append(os.path.join( - os.path.dirname(os.path.dirname(sys.executable)), - "lib")) - - # For macOS: - append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) - - lib_dirs.append(sys.exec_prefix) - lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) - - lib_basenames = list(candidate_names(suffix=suffix)) - - for directory in lib_dirs: - for basename in lib_basenames: - yield os.path.join(directory, basename) - - # In macOS and Windows, ctypes.util.find_library returns a full path: - for basename in lib_basenames: - yield ctypes.util.find_library(library_name(basename)) - -# Possibly useful links: -# * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist -# * https://github.com/Valloric/ycmd/issues/518 -# * https://github.com/Valloric/ycmd/pull/519 - - -def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): - """ - Normalize shared library `path` to a real path. - - If `path` is not a full path, `None` is returned. If `path` does - not exists, append `SHLIB_SUFFIX` and check if it exists. - Finally, the path is canonicalized by following the symlinks. - - Parameters - ---------- - path : str ot None - A candidate path to a shared library. - """ - if not path: - return None - if not os.path.isabs(path): - return None - if os.path.exists(path): - return os.path.realpath(path) - if os.path.exists(path + suffix): - return os.path.realpath(path + suffix) - if is_apple: - return normalize_path(_remove_suffix_apple(path), - suffix=".so", is_apple=False) - return None - - -def _remove_suffix_apple(path): - """ - Strip off .so or .dylib. - - >>> _remove_suffix_apple("libpython.so") - 'libpython' - >>> _remove_suffix_apple("libpython.dylib") - 'libpython' - >>> _remove_suffix_apple("libpython3.7") - 'libpython3.7' - """ - if path.endswith(".dylib"): - return path[:-len(".dylib")] - if path.endswith(".so"): - return path[:-len(".so")] - return path - - -@uniquified -def finding_libpython(): - """ - Iterate over existing libpython paths. - - The first item is likely to be the best one. - - Yields - ------ - path : str - Existing path to a libpython. - """ - logger.debug("is_windows = %s", is_windows) - logger.debug("is_apple = %s", is_apple) - for path in candidate_paths(): - logger.debug("Candidate: %s", path) - normalized = normalize_path(path) - if normalized: - logger.debug("Found: %s", normalized) - yield normalized - else: - logger.debug("Not found.") - - -def find_libpython(): - """ - Return a path (`str`) to libpython or `None` if not found. - - Parameters - ---------- - path : str or None - Existing path to the (supposedly) correct libpython. - """ - for path in finding_libpython(): - return os.path.realpath(path) - - -def print_all(items): - for x in items: - print(x) - - -def cli_find_libpython(cli_op, verbose, export): - import logging - # Importing `logging` module here so that using `logging.debug` - # instead of `logger.debug` outside of this function becomes an - # error. - - if verbose: - logging.basicConfig( - format="%(levelname)s %(message)s", - level=logging.DEBUG) - - if cli_op == "list-all": - print_all(finding_libpython()) - elif cli_op == "candidate-names": - print_all(candidate_names()) - elif cli_op == "candidate-paths": - print_all(p for p in candidate_paths() if p and os.path.isabs(p)) - else: - path = find_libpython() - if path is None: - return 1 - if export: - print(f"PYTHONNET_PYDLL={path}") - else: - print(path, end="") - - -def main(args=None): - import argparse - parser = argparse.ArgumentParser( - description=__doc__) - parser.add_argument( - "--verbose", "-v", action="store_true", - help="Print debugging information.") - - group = parser.add_mutually_exclusive_group() - group.add_argument( - "--list-all", - action="store_const", dest="cli_op", const="list-all", - help="Print list of all paths found.") - group.add_argument( - "--candidate-names", - action="store_const", dest="cli_op", const="candidate-names", - help="Print list of candidate names of libpython.") - group.add_argument( - "--candidate-paths", - action="store_const", dest="cli_op", const="candidate-paths", - help="Print list of candidate paths of libpython.") - group.add_argument( - "--export", - action="store_true", - help="Print as an environment export expression" - ) - - ns = parser.parse_args(args) - parser.exit(cli_find_libpython(**vars(ns))) diff --git a/pythonnet/find_libpython/__main__.py b/pythonnet/find_libpython/__main__.py deleted file mode 100644 index 031df43e1..000000000 --- a/pythonnet/find_libpython/__main__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import main -main() diff --git a/pythonnet/util/__init__.py b/pythonnet/util/__init__.py deleted file mode 100644 index 75d4bad8c..000000000 --- a/pythonnet/util/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .find_libpython import find_libpython diff --git a/requirements.txt b/requirements.txt index f5aedfc3f..8e911ef5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,6 @@ wheel pycparser setuptools clr-loader + +# Discover libpython +find_libpython \ No newline at end of file diff --git a/tests/domain_tests/test_domain_reload.py b/tests/domain_tests/test_domain_reload.py index d04d5a1f6..8999e481b 100644 --- a/tests/domain_tests/test_domain_reload.py +++ b/tests/domain_tests/test_domain_reload.py @@ -4,7 +4,7 @@ import pytest -from pythonnet.find_libpython import find_libpython +from find_libpython import find_libpython libpython = find_libpython() pytestmark = pytest.mark.xfail(libpython is None, reason="Can't find suitable libpython") From 7da78892d7c555284591f17d0c30c0ab2ae28ef2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 17 Jun 2022 13:04:20 +0200 Subject: [PATCH 038/183] Allow configuring .NET runtimes via environment variables --- .github/workflows/ARM.yml | 2 +- .github/workflows/main.yml | 5 +- CHANGELOG.md | 1 + pythonnet/__init__.py | 116 ++++++++++++++++++++++++++++++------- tests/conftest.py | 92 +++++++++++++---------------- 5 files changed, 142 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index db580f741..af257bcb8 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -45,7 +45,7 @@ jobs: run: python -m pytest --runtime mono - name: Python Tests (.NET Core) - run: python -m pytest --runtime netcore + run: python -m pytest --runtime coreclr - name: Python tests run from .NET run: dotnet test src/python_tests_runner/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a0fda9e9..2cc793621 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Main (x64) +name: Main on: push: @@ -73,9 +73,10 @@ jobs: if: ${{ matrix.os != 'windows' }} run: pytest --runtime mono + # TODO: Run these tests on Windows x86 - name: Python Tests (.NET Core) if: ${{ matrix.platform == 'x64' }} - run: pytest --runtime netcore + run: pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os == 'windows' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 069629021..52ee08484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and other `PyObject` derived types when called from Python. - .NET classes, that have `__call__` method are callable from Python - `PyIterable` type, that wraps any iterable object in Python - `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` +- The runtime that is loaded on `import clr` can now be configured via environment variables ### Changed diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 10dc403e4..fa6ed45cf 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -1,43 +1,115 @@ import sys +from pathlib import Path +from typing import Dict, Optional, Union import clr_loader -_RUNTIME = None -_LOADER_ASSEMBLY = None -_FFI = None -_LOADED = False +__all__ = ["set_runtime", "set_default_runtime", "load"] +_RUNTIME: Optional[clr_loader.Runtime] = None +_LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None +_LOADED: bool = False + + +def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: + """Set up a clr_loader runtime without loading it + + :param runtime: Either an already initialised `clr_loader` runtime, or one + of netfx, coreclr, mono, or default. If a string parameter is given, the + runtime will be created.""" -def set_runtime(runtime): global _RUNTIME if _LOADED: - raise RuntimeError("The runtime {} has already been loaded".format(_RUNTIME)) + raise RuntimeError(f"The runtime {_RUNTIME} has already been loaded") + + if isinstance(runtime, str): + runtime = _create_runtime_from_spec(runtime, params) _RUNTIME = runtime -def set_default_runtime() -> None: - if sys.platform == "win32": - set_runtime(clr_loader.get_netfx()) +def _get_params_from_env(prefix: str) -> Dict[str, str]: + from os import environ + + full_prefix = f"PYTHONNET_{prefix.upper()}" + len_ = len(full_prefix) + + env_vars = { + (k[len_:].lower()): v + for k, v in environ.items() + if k.upper().startswith(full_prefix) + } + + return env_vars + + +def _create_runtime_from_spec( + spec: str, params: Optional[Dict[str, str]] = None +) -> clr_loader.Runtime: + if spec == "default": + if sys.platform == "win32": + spec = "netfx" + else: + spec = "mono" + + params = params or _get_params_from_env(spec) + + if spec == "netfx": + return clr_loader.get_netfx(**params) + elif spec == "mono": + return clr_loader.get_mono(**params) + elif spec == "coreclr": + return clr_loader.get_coreclr(**params) else: - set_runtime(clr_loader.get_mono()) + raise RuntimeError(f"Invalid runtime name: '{spec}'") -def load(): - global _FFI, _LOADED, _LOADER_ASSEMBLY +def set_default_runtime() -> None: + """Set up the default runtime + + This will use the environment variable PYTHONNET_RUNTIME to decide the + runtime to use, which may be one of netfx, coreclr or mono. The parameters + of the respective clr_loader.get_ functions can also be given as + environment variables, named `PYTHONNET__`. In + particular, to use `PYTHONNET_RUNTIME=coreclr`, the variable + `PYTHONNET_CORECLR_RUNTIME_CONFIG` has to be set to a valid + `.runtimeconfig.json`. + + If no environment variable is specified, a globally installed Mono is used + for all environments but Windows, on Windows the legacy .NET Framework is + used. + """ + from os import environ + + print("Set default RUNTIME") + raise RuntimeError("Shouldn't be called here") + + spec = environ.get("PYTHONNET_RUNTIME", "default") + runtime = _create_runtime_from_spec(spec) + set_runtime(runtime) + + +def load( + runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] +) -> None: + """Load Python.NET in the specified runtime + + The same parameters as for `set_runtime` can be used. By default, + `set_default_runtime` is called if no environment has been set yet and no + parameters are passed.""" + global _LOADED, _LOADER_ASSEMBLY if _LOADED: return - from os.path import join, dirname + if _RUNTIME is None: + set_runtime(runtime, **params) if _RUNTIME is None: - # TODO: Warn, in the future the runtime must be set explicitly, either - # as a config/env variable or via set_runtime - set_default_runtime() + raise RuntimeError("No valid runtime selected") - dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll") + dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll" - _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path) + _LOADER_ASSEMBLY = _RUNTIME.get_assembly(str(dll_path)) func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] if func(b"") != 0: @@ -48,13 +120,17 @@ def load(): atexit.register(unload) -def unload(): - global _RUNTIME +def unload() -> None: + """Explicitly unload a laoded runtime and shut down Python.NET""" + + global _RUNTIME, _LOADER_ASSEMBLY if _LOADER_ASSEMBLY is not None: func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] if func(b"full_shutdown") != 0: raise RuntimeError("Failed to call Python.NET shutdown") + _LOADER_ASSEMBLY = None + if _RUNTIME is not None: # TODO: Add explicit `close` to clr_loader _RUNTIME = None diff --git a/tests/conftest.py b/tests/conftest.py index 89db46eca..fcd1d224a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,91 +8,83 @@ import os import sys import sysconfig +from pathlib import Path from subprocess import check_call from tempfile import mkdtemp import shutil import pytest -from pythonnet import set_runtime - # Add path for `Python.Test` -cwd = os.path.dirname(__file__) -fixtures_path = os.path.join(cwd, "fixtures") -sys.path.append(fixtures_path) +cwd = Path(__file__).parent +fixtures_path = cwd / "fixtures" +sys.path.append(str(fixtures_path)) + def pytest_addoption(parser): parser.addoption( "--runtime", action="store", default="default", - help="Must be one of default, netcore, netfx and mono" + help="Must be one of default, coreclr, netfx and mono", ) + collect_ignore = [] + def pytest_configure(config): global bin_path if "clr" in sys.modules: # Already loaded (e.g. by the C# test runner), skip build import clr + clr.AddReference("Python.Test") return runtime_opt = config.getoption("runtime") - - test_proj_path = os.path.join(cwd, "..", "src", "testing") - - if runtime_opt not in ["netcore", "netfx", "mono", "default"]: + if runtime_opt not in ["coreclr", "netfx", "mono", "default"]: raise RuntimeError(f"Invalid runtime: {runtime_opt}") - bin_path = mkdtemp() - - # tmpdir_factory.mktemp(f"pythonnet-{runtime_opt}") - - fw = "net6.0" if runtime_opt == "netcore" else "netstandard2.0" - - check_call(["dotnet", "publish", "-f", fw, "-o", bin_path, test_proj_path]) - - sys.path.append(bin_path) - - if runtime_opt == "default": - pass - elif runtime_opt == "netfx": - from clr_loader import get_netfx - runtime = get_netfx() - set_runtime(runtime) - elif runtime_opt == "mono": - from clr_loader import get_mono - runtime = get_mono() - set_runtime(runtime) - elif runtime_opt == "netcore": - from clr_loader import get_coreclr - rt_config_path = os.path.join(bin_path, "Python.Test.runtimeconfig.json") - runtime = get_coreclr(rt_config_path) - set_runtime(runtime) - - import clr - clr.AddReference("Python.Test") + test_proj_path = cwd.parent / "src" / "testing" + bin_path = Path(mkdtemp()) - soft_mode = False - try: - os.environ['PYTHONNET_SHUTDOWN_MODE'] == 'Soft' - except: pass + fw = "netstandard2.0" + runtime_params = {} - if config.getoption("--runtime") == "netcore" or soft_mode\ - : + if runtime_opt == "coreclr": + fw = "net6.0" + runtime_params["runtime_config"] = str( + bin_path / "Python.Test.runtimeconfig.json" + ) collect_ignore.append("domain_tests/test_domain_reload.py") else: - domain_tests_dir = os.path.join(os.path.dirname(__file__), "domain_tests") - bin_path = os.path.join(domain_tests_dir, "bin") - build_cmd = ["dotnet", "build", domain_tests_dir, "-o", bin_path] + domain_tests_dir = cwd / "domain_tests" + domain_bin_path = domain_tests_dir / "bin" + build_cmd = [ + "dotnet", + "build", + str(domain_tests_dir), + "-o", + str(domain_bin_path), + ] is_64bits = sys.maxsize > 2**32 if not is_64bits: build_cmd += ["/p:Prefer32Bit=True"] check_call(build_cmd) + check_call( + ["dotnet", "publish", "-f", fw, "-o", str(bin_path), str(test_proj_path)] + ) + + from pythonnet import load + + load(runtime_opt, **runtime_params) + + import clr + sys.path.append(str(bin_path)) + clr.AddReference("Python.Test") def pytest_unconfigure(config): @@ -102,6 +94,7 @@ def pytest_unconfigure(config): except Exception: pass + def pytest_report_header(config): """Generate extra report headers""" # FIXME: https://github.com/pytest-dev/pytest/issues/2257 @@ -109,11 +102,8 @@ def pytest_report_header(config): arch = "x64" if is_64bits else "x86" ucs = ctypes.sizeof(ctypes.c_wchar) libdir = sysconfig.get_config_var("LIBDIR") - shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) - header = ("Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}, " - "Py_ENABLE_SHARED: {shared}".format(**locals())) - return header + return f"Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}" @pytest.fixture() From a49f3a885f8eb40bc0488b8d483ef0d5842f0834 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 27 Jun 2022 11:45:31 +0200 Subject: [PATCH 039/183] Take GIL when checking if error occurred on shutdown (#1836) --- src/runtime/PythonEngine.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index f184ebe88..6e9c4d1f1 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -355,12 +355,17 @@ public static void Shutdown() { return; } - if (Exceptions.ErrorOccurred()) + + using (Py.GIL()) { - throw new InvalidOperationException( - "Python error indicator is set", - innerException: PythonException.PeekCurrentOrNull(out _)); + if (Exceptions.ErrorOccurred()) + { + throw new InvalidOperationException( + "Python error indicator is set", + innerException: PythonException.PeekCurrentOrNull(out _)); + } } + // If the shutdown handlers trigger a domain unload, // don't call shutdown again. AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; From 28a78ed6e9a77c6854be8269ad82cb2792ad88a8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 27 Jun 2022 11:47:57 +0200 Subject: [PATCH 040/183] Bump version to 3.0.0-rc1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e16bcc8be..cf8213193 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-dev1 +3.0.0-rc1 From 21c5dcc950272d84cb739bc6d61ba4d280882180 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 Jun 2022 15:24:00 -0700 Subject: [PATCH 041/183] implemented dynamic equality and inequality for PyObject instances fixed unhandled Python errors during comparison attempts fixes https://github.com/pythonnet/pythonnet/issues/1848 --- CHANGELOG.md | 2 + src/embed_tests/dynamic.cs | 22 +++++++++ src/runtime/PythonTypes/PyObject.cs | 75 +++++++++++++++++++++++------ src/runtime/Runtime.cs | 25 ---------- 4 files changed, 83 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee08484..9b5dd1816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ details about the cause of the failure able to access members that are part of the implementation class, but not the interface. Use the new `__implementation__` or `__raw_implementation__` properties to if you need to "downcast" to the implementation class. +- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison + (previously was equivalent to `object.ReferenceEquals(,)`) - BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/dynamic.cs index 0a181231c..6e3bfc4cb 100644 --- a/src/embed_tests/dynamic.cs +++ b/src/embed_tests/dynamic.cs @@ -128,6 +128,28 @@ public void PassPyObjectInNet() Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); } + // regression test for https://github.com/pythonnet/pythonnet/issues/1848 + [Test] + public void EnumEquality() + { + using var scope = Py.CreateScope(); + scope.Exec(@" +import enum + +class MyEnum(enum.IntEnum): + OK = 1 + ERROR = 2 + +def get_status(): + return MyEnum.OK +" +); + + dynamic MyEnum = scope.Get("MyEnum"); + dynamic status = scope.Get("get_status").Invoke(); + Assert.IsTrue(status == MyEnum.OK); + } + // regression test for https://github.com/pythonnet/pythonnet/issues/1680 [Test] public void ForEach() diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index cfd3e7158..3d48e22ed 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other) { return true; } - int r = Runtime.PyObject_Compare(this, other); - if (Exceptions.ErrorOccurred()) - { - throw PythonException.ThrowLastAsClrException(); - } - return r == 0; + int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; } @@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result) return false; } + private bool TryCompare(PyObject arg, int op, out object @out) + { + int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op); + @out = result != 0; + if (result < 0) + { + Exceptions.Clear(); + return false; + } + return true; + } + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) { using var _ = Py.GIL(); @@ -1352,11 +1361,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj); break; case ExpressionType.GreaterThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result); case ExpressionType.GreaterThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result); case ExpressionType.LeftShift: res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj); break; @@ -1364,11 +1371,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj); break; case ExpressionType.LessThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result); case ExpressionType.LessThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result); case ExpressionType.Modulo: res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj); break; @@ -1376,8 +1381,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj); break; case ExpressionType.NotEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result); + case ExpressionType.Equal: + return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result); case ExpressionType.Or: res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj); break; @@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg return true; } + public static bool operator ==(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return true; + } + if (a is null || b is null) + { + return false; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + + public static bool operator !=(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return false; + } + if (a is null || b is null) + { + return true; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509 // See https://github.com/pythonnet/pythonnet/pull/219 internal static object? CheckNone(PyObject pyObj) @@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? case ExpressionType.Not: r = Runtime.PyObject_Not(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsFalse: r = Runtime.PyObject_IsTrue(this.obj); result = r == 0; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsTrue: r = Runtime.PyObject_IsTrue(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.Decrement: case ExpressionType.Increment: diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6ad1d459f..1eeb96b54 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -962,31 +962,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args) internal static int PyObject_RichCompareBool(BorrowedReference value1, BorrowedReference value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid); - internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2) - { - int res; - res = PyObject_RichCompareBool(value1, value2, Py_LT); - if (-1 == res) - return -1; - else if (1 == res) - return -1; - - res = PyObject_RichCompareBool(value1, value2, Py_EQ); - if (-1 == res) - return -1; - else if (1 == res) - return 0; - - res = PyObject_RichCompareBool(value1, value2, Py_GT); - if (-1 == res) - return -1; - else if (1 == res) - return 1; - - Exceptions.SetError(Exceptions.SystemError, "Error comparing objects"); - return -1; - } - internal static int PyObject_IsInstance(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsInstance(ob, type); From e241a9d1ccd6b5168aed2457a6c96117d708306f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 Jun 2022 16:25:15 -0700 Subject: [PATCH 042/183] got rid of a few deprecation warnings that pollute GitHub code review --- src/embed_tests/TestConverter.cs | 9 ++++++--- src/embed_tests/TestDomainReload.cs | 3 +-- src/embed_tests/TestFinalizer.cs | 7 +++++-- src/embed_tests/TestNativeTypeOffset.cs | 3 ++- src/embed_tests/TestPythonException.cs | 2 +- src/runtime/InternString.cs | 4 ++-- src/runtime/PythonTypes/PyObject.cs | 4 +++- src/runtime/Types/ReflectedClrType.cs | 2 +- src/runtime/Util/PythonReferenceComparer.cs | 4 ++-- 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index e586eda1b..0686d528b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -148,7 +148,7 @@ public void PyIntImplicit() { var i = new PyInt(1); var ni = (PyObject)i.As(); - Assert.AreEqual(i.rawPtr, ni.rawPtr); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); } [Test] @@ -178,8 +178,11 @@ public void RawPyObjectProxy() var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy); Assert.AreSame(pyObject, clrObject.inst); - var proxiedHandle = pyObjectProxy.GetAttr("Handle").As(); - Assert.AreEqual(pyObject.Handle, proxiedHandle); +#pragma warning disable CS0612 // Type or member is obsolete + const string handlePropertyName = nameof(PyObject.Handle); +#pragma warning restore CS0612 // Type or member is obsolete + var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); + Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); } // regression for https://github.com/pythonnet/pythonnet/issues/451 diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index 498119d1e..a0f9b63eb 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -99,8 +99,7 @@ from Python.EmbeddingTest.Domain import MyClass { Debug.Assert(obj.AsManagedObject(type).GetType() == type); // We only needs its Python handle - PyRuntime.XIncref(obj); - return obj.Handle; + return new NewReference(obj).DangerousMoveToPointer(); } } } diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index 40ab03395..b748a2244 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -212,7 +212,9 @@ public void ValidateRefCount() Assert.AreEqual(ptr, e.Handle); Assert.AreEqual(2, e.ImpactedObjects.Count); // Fix for this test, don't do this on general environment +#pragma warning disable CS0618 // Type or member is obsolete Runtime.Runtime.XIncref(e.Reference); +#pragma warning restore CS0618 // Type or member is obsolete return false; }; Finalizer.Instance.IncorrectRefCntResolver += handler; @@ -234,8 +236,9 @@ private static IntPtr CreateStringGarbage() { PyString s1 = new PyString("test_string"); // s2 steal a reference from s1 - PyString s2 = new PyString(StolenReference.DangerousFromPointer(s1.Handle)); - return s1.Handle; + IntPtr address = s1.Reference.DangerousGetAddress(); + PyString s2 = new (StolenReference.DangerousFromPointer(address)); + return address; } } } diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index 2d31fe506..d692c24e6 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -33,7 +33,8 @@ public void LoadNativeTypeOffsetClass() { PyObject sys = Py.Import("sys"); // We can safely ignore the "m" abi flag - var abiflags = sys.GetAttr("abiflags", "".ToPython()).ToString().Replace("m", ""); + var abiflags = sys.HasAttr("abiflags") ? sys.GetAttr("abiflags").ToString() : ""; + abiflags = abiflags.Replace("m", ""); if (!string.IsNullOrEmpty(abiflags)) { string typeName = "Python.Runtime.NativeTypeOffset, Python.Runtime"; diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a7cf05c83..a248b6a1f 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -161,7 +161,7 @@ def __init__(self, val): using var tbObj = tbPtr.MoveToPyObject(); // the type returned from PyErr_NormalizeException should not be the same type since a new // exception was raised by initializing the exception - Assert.AreNotEqual(type.Handle, typeObj.Handle); + Assert.IsFalse(PythonReferenceComparer.Instance.Equals(type, typeObj)); // 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()); } diff --git a/src/runtime/InternString.cs b/src/runtime/InternString.cs index b6d9a0e4a..decb3981d 100644 --- a/src/runtime/InternString.cs +++ b/src/runtime/InternString.cs @@ -42,7 +42,7 @@ public static void Initialize() Debug.Assert(name == op.As()); SetIntern(name, op); var field = type.GetField("f" + name, PyIdentifierFieldFlags)!; - field.SetValue(null, op.rawPtr); + field.SetValue(null, op.DangerousGetAddressOrNull()); } } @@ -76,7 +76,7 @@ public static bool TryGetInterned(BorrowedReference op, out string s) private static void SetIntern(string s, PyString op) { _string2interns.Add(s, op); - _intern2strings.Add(op.rawPtr, s); + _intern2strings.Add(op.Reference.DangerousGetAddress(), s); } } } diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index 3d48e22ed..ce86753eb 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -27,7 +27,7 @@ public partial class PyObject : DynamicObject, IDisposable, ISerializable public StackTrace Traceback { get; } = new StackTrace(1); #endif - protected internal IntPtr rawPtr = IntPtr.Zero; + protected IntPtr rawPtr = IntPtr.Zero; internal readonly int run = Runtime.GetRun(); internal BorrowedReference obj => new (rawPtr); @@ -252,6 +252,8 @@ internal void Leak() rawPtr = IntPtr.Zero; } + internal IntPtr DangerousGetAddressOrNull() => rawPtr; + internal void CheckRun() { if (run != Runtime.GetRun()) diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 2e8f95924..e92a28018 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -117,6 +117,6 @@ static ReflectedClrType AllocateClass(Type clrType) return new ReflectedClrType(type.Steal()); } - public override bool Equals(PyObject? other) => other != null && rawPtr == other.rawPtr; + public override bool Equals(PyObject? other) => rawPtr == other?.DangerousGetAddressOrNull(); public override int GetHashCode() => rawPtr.GetHashCode(); } diff --git a/src/runtime/Util/PythonReferenceComparer.cs b/src/runtime/Util/PythonReferenceComparer.cs index dd78f912d..63c35df57 100644 --- a/src/runtime/Util/PythonReferenceComparer.cs +++ b/src/runtime/Util/PythonReferenceComparer.cs @@ -13,10 +13,10 @@ public sealed class PythonReferenceComparer : IEqualityComparer public static PythonReferenceComparer Instance { get; } = new PythonReferenceComparer(); public bool Equals(PyObject? x, PyObject? y) { - return x?.rawPtr == y?.rawPtr; + return x?.DangerousGetAddressOrNull() == y?.DangerousGetAddressOrNull(); } - public int GetHashCode(PyObject obj) => obj.rawPtr.GetHashCode(); + public int GetHashCode(PyObject obj) => obj.DangerousGetAddressOrNull().GetHashCode(); private PythonReferenceComparer() { } } From e84731f8e8834d3eeac2fa442ccb4d6848a1f9c5 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 Jun 2022 15:24:00 -0700 Subject: [PATCH 043/183] implemented dynamic equality and inequality for PyObject instances fixed unhandled Python errors during comparison attempts fixes https://github.com/pythonnet/pythonnet/issues/1848 --- CHANGELOG.md | 2 + src/embed_tests/dynamic.cs | 22 +++++++++ src/runtime/PythonTypes/PyObject.cs | 75 +++++++++++++++++++++++------ src/runtime/Runtime.cs | 25 ---------- 4 files changed, 83 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee08484..9b5dd1816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ details about the cause of the failure able to access members that are part of the implementation class, but not the interface. Use the new `__implementation__` or `__raw_implementation__` properties to if you need to "downcast" to the implementation class. +- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison + (previously was equivalent to `object.ReferenceEquals(,)`) - BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/dynamic.cs index 0a181231c..6e3bfc4cb 100644 --- a/src/embed_tests/dynamic.cs +++ b/src/embed_tests/dynamic.cs @@ -128,6 +128,28 @@ public void PassPyObjectInNet() Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); } + // regression test for https://github.com/pythonnet/pythonnet/issues/1848 + [Test] + public void EnumEquality() + { + using var scope = Py.CreateScope(); + scope.Exec(@" +import enum + +class MyEnum(enum.IntEnum): + OK = 1 + ERROR = 2 + +def get_status(): + return MyEnum.OK +" +); + + dynamic MyEnum = scope.Get("MyEnum"); + dynamic status = scope.Get("get_status").Invoke(); + Assert.IsTrue(status == MyEnum.OK); + } + // regression test for https://github.com/pythonnet/pythonnet/issues/1680 [Test] public void ForEach() diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index cfd3e7158..3d48e22ed 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other) { return true; } - int r = Runtime.PyObject_Compare(this, other); - if (Exceptions.ErrorOccurred()) - { - throw PythonException.ThrowLastAsClrException(); - } - return r == 0; + int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; } @@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result) return false; } + private bool TryCompare(PyObject arg, int op, out object @out) + { + int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op); + @out = result != 0; + if (result < 0) + { + Exceptions.Clear(); + return false; + } + return true; + } + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) { using var _ = Py.GIL(); @@ -1352,11 +1361,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj); break; case ExpressionType.GreaterThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result); case ExpressionType.GreaterThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result); case ExpressionType.LeftShift: res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj); break; @@ -1364,11 +1371,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj); break; case ExpressionType.LessThan: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result); case ExpressionType.LessThanOrEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result); case ExpressionType.Modulo: res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj); break; @@ -1376,8 +1381,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj); break; case ExpressionType.NotEqual: - result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0; - return true; + return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result); + case ExpressionType.Equal: + return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result); case ExpressionType.Or: res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj); break; @@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg return true; } + public static bool operator ==(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return true; + } + if (a is null || b is null) + { + return false; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + + public static bool operator !=(PyObject? a, PyObject? b) + { + if (a is null && b is null) + { + return false; + } + if (a is null || b is null) + { + return true; + } + + using var _ = Py.GIL(); + int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE); + if (result < 0) throw PythonException.ThrowLastAsClrException(); + return result != 0; + } + // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509 // See https://github.com/pythonnet/pythonnet/pull/219 internal static object? CheckNone(PyObject pyObj) @@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? case ExpressionType.Not: r = Runtime.PyObject_Not(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsFalse: r = Runtime.PyObject_IsTrue(this.obj); result = r == 0; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.IsTrue: r = Runtime.PyObject_IsTrue(this.obj); result = r == 1; + if (r == -1) Exceptions.Clear(); return r != -1; case ExpressionType.Decrement: case ExpressionType.Increment: diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6ad1d459f..1eeb96b54 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -962,31 +962,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args) internal static int PyObject_RichCompareBool(BorrowedReference value1, BorrowedReference value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid); - internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2) - { - int res; - res = PyObject_RichCompareBool(value1, value2, Py_LT); - if (-1 == res) - return -1; - else if (1 == res) - return -1; - - res = PyObject_RichCompareBool(value1, value2, Py_EQ); - if (-1 == res) - return -1; - else if (1 == res) - return 0; - - res = PyObject_RichCompareBool(value1, value2, Py_GT); - if (-1 == res) - return -1; - else if (1 == res) - return 1; - - Exceptions.SetError(Exceptions.SystemError, "Error comparing objects"); - return -1; - } - internal static int PyObject_IsInstance(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsInstance(ob, type); From 2edd8000e49caa24b6a7feb3f192eac0fd09161b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 28 Jun 2022 14:45:43 +0200 Subject: [PATCH 044/183] Fix broken prefix and debug leftover Additionally, fixes a type hint and makes sure that the new default behaviour is to use the environment variable if given. --- pythonnet/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index fa6ed45cf..9876a0bec 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -3,7 +3,7 @@ from typing import Dict, Optional, Union import clr_loader -__all__ = ["set_runtime", "set_default_runtime", "load"] +__all__ = ["set_runtime", "set_runtime_from_env", "load"] _RUNTIME: Optional[clr_loader.Runtime] = None _LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None @@ -30,7 +30,7 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: def _get_params_from_env(prefix: str) -> Dict[str, str]: from os import environ - full_prefix = f"PYTHONNET_{prefix.upper()}" + full_prefix = f"PYTHONNET_{prefix.upper()}_" len_ = len(full_prefix) env_vars = { @@ -63,8 +63,8 @@ def _create_runtime_from_spec( raise RuntimeError(f"Invalid runtime name: '{spec}'") -def set_default_runtime() -> None: - """Set up the default runtime +def set_runtime_from_env() -> None: + """Set up the runtime using the environment This will use the environment variable PYTHONNET_RUNTIME to decide the runtime to use, which may be one of netfx, coreclr or mono. The parameters @@ -80,16 +80,13 @@ def set_default_runtime() -> None: """ from os import environ - print("Set default RUNTIME") - raise RuntimeError("Shouldn't be called here") - spec = environ.get("PYTHONNET_RUNTIME", "default") runtime = _create_runtime_from_spec(spec) set_runtime(runtime) def load( - runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] + runtime: Union[clr_loader.Runtime, str, None] = None, **params: str ) -> None: """Load Python.NET in the specified runtime @@ -102,7 +99,10 @@ def load( return if _RUNTIME is None: - set_runtime(runtime, **params) + if runtime is None: + set_runtime_from_env() + else: + set_runtime(runtime, **params) if _RUNTIME is None: raise RuntimeError("No valid runtime selected") From 4b6ac1246362a6ecccbaf54bcdd15b105c11ea5d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 Jul 2022 23:10:42 +0200 Subject: [PATCH 045/183] Bump release candidate version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cf8213193..d15be84db 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc1 +3.0.0-rc2 From ce3afa64c85e5bf88a54d581eae4719810f85e19 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 28 Jun 2022 14:45:43 +0200 Subject: [PATCH 046/183] Fix broken prefix and debug leftover Additionally, fixes a type hint and makes sure that the new default behaviour is to use the environment variable if given. --- pythonnet/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index fa6ed45cf..9876a0bec 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -3,7 +3,7 @@ from typing import Dict, Optional, Union import clr_loader -__all__ = ["set_runtime", "set_default_runtime", "load"] +__all__ = ["set_runtime", "set_runtime_from_env", "load"] _RUNTIME: Optional[clr_loader.Runtime] = None _LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None @@ -30,7 +30,7 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: def _get_params_from_env(prefix: str) -> Dict[str, str]: from os import environ - full_prefix = f"PYTHONNET_{prefix.upper()}" + full_prefix = f"PYTHONNET_{prefix.upper()}_" len_ = len(full_prefix) env_vars = { @@ -63,8 +63,8 @@ def _create_runtime_from_spec( raise RuntimeError(f"Invalid runtime name: '{spec}'") -def set_default_runtime() -> None: - """Set up the default runtime +def set_runtime_from_env() -> None: + """Set up the runtime using the environment This will use the environment variable PYTHONNET_RUNTIME to decide the runtime to use, which may be one of netfx, coreclr or mono. The parameters @@ -80,16 +80,13 @@ def set_default_runtime() -> None: """ from os import environ - print("Set default RUNTIME") - raise RuntimeError("Shouldn't be called here") - spec = environ.get("PYTHONNET_RUNTIME", "default") runtime = _create_runtime_from_spec(spec) set_runtime(runtime) def load( - runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] + runtime: Union[clr_loader.Runtime, str, None] = None, **params: str ) -> None: """Load Python.NET in the specified runtime @@ -102,7 +99,10 @@ def load( return if _RUNTIME is None: - set_runtime(runtime, **params) + if runtime is None: + set_runtime_from_env() + else: + set_runtime(runtime, **params) if _RUNTIME is None: raise RuntimeError("No valid runtime selected") From 14aae2ebc8de4fd5f9eaac520573d89bbfc89a3e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 6 Jul 2022 08:32:51 +0200 Subject: [PATCH 047/183] Ensure that version.txt is always read from repo root Allows the project to be referenced in other .NET projects without adjusting its project file (#1853). --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8c5b53685..965610f91 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ Python.NET 10.0 false - $([System.IO.File]::ReadAllText("version.txt")) + $([System.IO.File]::ReadAllText($(MSBuildThisFileDirectory)version.txt)) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) From 201637127f158a3a930ef4cc74de29f557d62313 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 6 Jul 2022 08:52:38 +0200 Subject: [PATCH 048/183] Make MANIFEST.in more explicit - Only include src/runtime, no other .NET subproject - In particular, tests are not contained anymore in the sdist - Fix accidentally including obj and bin directories in the sdist --- MANIFEST.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4763ae70f..6458d5778 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ -recursive-include src/ * +graft src/runtime +prune src/runtime/obj +prune src/runtime/bin include Directory.Build.* include pythonnet.sln include version.txt -global-exclude **/obj/* **/bin/* From d48479703ed448c0a3063794cb302d8088b7362e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 8 Jul 2022 17:33:28 +0200 Subject: [PATCH 049/183] Use informational version as clr's __version__ attribute --- src/runtime/PythonEngine.cs | 15 ++++++++++++--- src/runtime/Resources/clr.py | 2 -- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 6e9c4d1f1..e5879ae67 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -228,6 +228,7 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, Assembly assembly = Assembly.GetExecutingAssembly(); // add the contents of clr.py to the module string clr_py = assembly.ReadStringResource("clr.py"); + Exec(clr_py, module_globals, locals.Reference); LoadSubmodule(module_globals, "clr.interop", "interop.py"); @@ -237,14 +238,22 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, // add the imported module to the clr module, and copy the API functions // and decorators into the main clr module. Runtime.PyDict_SetItemString(clr_dict, "_extras", module); + + // append version + var version = typeof(PythonEngine) + .Assembly + .GetCustomAttribute() + .InformationalVersion; + using var versionObj = Runtime.PyString_FromString(version); + Runtime.PyDict_SetItemString(clr_dict, "__version__", versionObj.Borrow()); + using var keys = locals.Keys(); foreach (PyObject key in keys) { - if (!key.ToString()!.StartsWith("_") || key.ToString()!.Equals("__version__")) + if (!key.ToString()!.StartsWith("_")) { - PyObject value = locals[key]; + using PyObject value = locals[key]; Runtime.PyDict_SetItem(clr_dict, key.Reference, value.Reference); - value.Dispose(); } key.Dispose(); } diff --git a/src/runtime/Resources/clr.py b/src/runtime/Resources/clr.py index 2254e7430..d4330a4d5 100644 --- a/src/runtime/Resources/clr.py +++ b/src/runtime/Resources/clr.py @@ -2,8 +2,6 @@ Code in this module gets loaded into the main clr module. """ -__version__ = "3.0.0dev" - class clrproperty(object): """ From 25f21f99b2a8f1100d7f4a9d78f7caab7d3f99b6 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 11:12:14 +0200 Subject: [PATCH 050/183] Drop obsolete packages.config --- src/embed_tests/packages.config | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/embed_tests/packages.config diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config deleted file mode 100644 index 590eaef8c..000000000 --- a/src/embed_tests/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From 332f8e79ef86a83ed4da927797f2499b17666957 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 14:38:54 +0200 Subject: [PATCH 051/183] Fix (U)IntPtr construction (#1861) * Add unit tests for (U)IntPtr conversions * Special-case construction of (U)IntPtr * Check (U)IntPtr size explicitly --- src/runtime/Converter.cs | 2 +- src/runtime/Runtime.cs | 12 ++- src/runtime/Types/ClassObject.cs | 143 +++++++++++++++++++++++++++---- src/testing/conversiontest.cs | 7 +- tests/test_conversion.py | 36 +++++++- 5 files changed, 174 insertions(+), 26 deletions(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index f86ba7900..e1820f05b 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -361,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, // conversions (Python string -> managed string). if (obType == objectType) { - if (Runtime.IsStringType(value)) + if (Runtime.PyString_Check(value)) { return ToPrimitive(value, stringType, out result, setError); } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 1eeb96b54..20bef23d4 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -59,6 +59,11 @@ private static string GetDefaultDllName(Version version) internal static bool TypeManagerInitialized => _typesInitialized; internal static readonly bool Is32Bit = IntPtr.Size == 4; + // Available in newer .NET Core versions (>= 5) as IntPtr.MaxValue etc. + internal static readonly long IntPtrMaxValue = Is32Bit ? Int32.MaxValue : Int64.MaxValue; + internal static readonly long IntPtrMinValue = Is32Bit ? Int32.MinValue : Int64.MinValue; + internal static readonly ulong UIntPtrMaxValue = Is32Bit ? UInt32.MaxValue : UInt64.MaxValue; + // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; @@ -1281,13 +1286,6 @@ internal static bool PyFloat_Check(BorrowedReference ob) //==================================================================== // Python string API //==================================================================== - internal static bool IsStringType(BorrowedReference op) - { - BorrowedReference t = PyObject_TYPE(op); - return (t == PyStringType) - || (t == PyUnicodeType); - } - internal static bool PyString_Check(BorrowedReference ob) { return PyObject_TYPE(ob) == PyStringType; diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 474e9dd7b..70ec53b18 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -70,22 +70,9 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo // Primitive types do not have constructors, but they look like // they do from Python. If the ClassObject represents one of the // convertible primitive types, just convert the arg directly. - if (type.IsPrimitive || type == typeof(string)) + if (type.IsPrimitive) { - if (Runtime.PyTuple_Size(args) != 1) - { - Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); - return default; - } - - BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); - - if (!Converter.ToManaged(op, type, out var result, true)) - { - return default; - } - - return CLRObject.GetReference(result!, tp); + return NewPrimitive(tp, args, type); } if (type.IsAbstract) @@ -99,6 +86,11 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return NewEnum(type, args, tp); } + if (type == typeof(string)) + { + return NewString(args, tp); + } + if (IsGenericNullable(type)) { // Nullable has special handling in .NET runtime. @@ -112,6 +104,127 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return self.NewObjectToPython(obj, tp); } + /// + /// Construct a new .NET String object from Python args + /// + private static NewReference NewString(BorrowedReference args, BorrowedReference tp) + { + if (Runtime.PyTuple_Size(args) == 1) + { + BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0); + if (Runtime.PyString_Check(ob)) + { + if (Runtime.GetManagedString(ob) is string val) + return CLRObject.GetReference(val, tp); + } + + // TODO: Initialise using constructors instead + + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + return default; + } + + /// + /// Create a new Python object for a primitive type + /// + /// The primitive types are Boolean, Byte, SByte, Int16, UInt16, + /// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, + /// and Single. + /// + /// All numeric types and Boolean can be handled by a simple + /// conversion, (U)IntPtr has to be handled separately as we + /// do not want to convert them automically to/from integers. + /// + /// .NET type to construct + /// Corresponding Python type + /// Constructor arguments + private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference args, Type type) + { + // TODO: Handle IntPtr + if (Runtime.PyTuple_Size(args) != 1) + { + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); + object? result = null; + + if (type == typeof(IntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nint val: + result = new IntPtr(val); + break; + case Int64 val: + result = new IntPtr(val); + break; + case Int32 val: + result = new IntPtr(val); + break; + } + } + else if (Runtime.PyInt_Check(op)) + { + long? num = Runtime.PyLong_AsLongLong(op); + if (num is long n && n >= Runtime.IntPtrMinValue && n <= Runtime.IntPtrMaxValue) + { + result = new IntPtr(n); + } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for IntPtr"); + return default; + } + } + } + + if (type == typeof(UIntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nuint val: + result = new UIntPtr(val); + break; + case UInt64 val: + result = new UIntPtr(val); + break; + case UInt32 val: + result = new UIntPtr(val); + break; + } + } + else if (Runtime.PyInt_Check(op)) + { + ulong? num = Runtime.PyLong_AsUnsignedLongLong(op); + if (num is ulong n && n <= Runtime.UIntPtrMaxValue) + { + result = new UIntPtr(n); + } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for UIntPtr"); + return default; + } + } + } + + if (result == null && !Converter.ToManaged(op, type, out result, true)) + { + return default; + } + + return CLRObject.GetReference(result!, tp); + } + protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder) { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_new, new Interop.BBB_N(tp_new_impl), slotsHolder); diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 7a00f139e..272bb74c2 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -1,5 +1,6 @@ namespace Python.Test { + using System; using System.Collections.Generic; /// @@ -26,6 +27,8 @@ public ConversionTest() public ulong UInt64Field = 0; public float SingleField = 0.0F; public double DoubleField = 0.0; + public IntPtr IntPtrField = IntPtr.Zero; + public UIntPtr UIntPtrField = UIntPtr.Zero; public decimal DecimalField = 0; public string StringField; public ShortEnum EnumField; @@ -42,7 +45,7 @@ public ConversionTest() } - + public interface ISpam { @@ -63,7 +66,7 @@ public string GetValue() return value; } } - + public class UnicodeString { public string value = "안녕"; diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 6693d8000..a5b4c6fd9 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -25,7 +25,7 @@ def test_bool_conversion(): with pytest.raises(TypeError): ob.BooleanField = 1 - + with pytest.raises(TypeError): ob.BooleanField = 0 @@ -679,3 +679,37 @@ def test_iconvertible_conversion(): assert 1024 == change_type(1024, System.Int32) assert 1024 == change_type(1024, System.Int64) assert 1024 == change_type(1024, System.Int16) + +def test_intptr_construction(): + from System import IntPtr, UIntPtr, Int64, UInt64 + from ctypes import sizeof, c_void_p + + ptr_size = sizeof(c_void_p) + max_intptr = 2 ** (ptr_size * 8 - 1) - 1 + min_intptr = -max_intptr - 1 + max_uintptr = 2 ** (ptr_size * 8) - 1 + min_uintptr = 0 + + ob = ConversionTest() + + assert ob.IntPtrField == IntPtr.Zero + assert ob.UIntPtrField == UIntPtr.Zero + + for v in [0, -1, 1024, max_intptr, min_intptr]: + ob.IntPtrField = IntPtr(Int64(v)) + assert ob.IntPtrField == IntPtr(v) + assert ob.IntPtrField.ToInt64() == v + + for v in [min_intptr - 1, max_intptr + 1]: + with pytest.raises(OverflowError): + IntPtr(v) + + for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]: + ob.UIntPtrField = UIntPtr(UInt64(v)) + assert ob.UIntPtrField == UIntPtr(v) + assert ob.UIntPtrField.ToUInt64() == v + + for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]: + with pytest.raises(OverflowError): + UIntPtr(v) + From 60a719c1f5039b40e0780817f04fd08e2e811a6c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 16:55:02 +0200 Subject: [PATCH 052/183] Implement non-simple String constructors explicitly (#1862) --- src/runtime/Types/ClassObject.cs | 51 ++++++++++++++++++++++++++++---- tests/test_constructors.py | 17 +++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 70ec53b18..cc42039e8 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -106,24 +106,63 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo /// /// Construct a new .NET String object from Python args + /// + /// This manual implementation of all individual relevant constructors + /// is required because System.String can't be allocated uninitialized. + /// + /// Additionally, it implements `String(pythonStr)` /// private static NewReference NewString(BorrowedReference args, BorrowedReference tp) { - if (Runtime.PyTuple_Size(args) == 1) + var argCount = Runtime.PyTuple_Size(args); + + string? result = null; + if (argCount == 1) { BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0); if (Runtime.PyString_Check(ob)) { if (Runtime.GetManagedString(ob) is string val) - return CLRObject.GetReference(val, tp); + result = val; } + else if (Converter.ToManagedValue(ob, typeof(char[]), out object? arr, false)) + { + result = new String((char[])arr!); + } + } + else if (argCount == 2) + { + BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0); + BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1); - // TODO: Initialise using constructors instead - - Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); - return default; + if ( + Converter.ToManagedValue(p1, typeof(char), out object? chr, false) && + Converter.ToManagedValue(p2, typeof(int), out object? count, false) + ) + { + result = new String((char)chr!, (int)count!); + } + } + else if (argCount == 3) + { + BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0); + BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1); + BorrowedReference p3 = Runtime.PyTuple_GetItem(args, 2); + + if ( + Converter.ToManagedValue(p1, typeof(char[]), out object? arr, false) && + Converter.ToManagedValue(p2, typeof(int), out object? offset, false) && + Converter.ToManagedValue(p3, typeof(int), out object? length, false) + ) + { + result = new String((char[])arr!, (int)offset!, (int)length!); + } } + if (result != null) + return CLRObject.GetReference(result!, tp); + + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); return default; } diff --git a/tests/test_constructors.py b/tests/test_constructors.py index 8e7ef2794..6d8f00c12 100644 --- a/tests/test_constructors.py +++ b/tests/test_constructors.py @@ -69,3 +69,20 @@ def test_default_constructor_fallback(): with pytest.raises(TypeError): ob = DefaultConstructorMatching("2") + + +def test_string_constructor(): + from System import String, Char, Array + + ob = String('A', 10) + assert ob == 'A' * 10 + + arr = Array[Char](10) + for i in range(10): + arr[i] = Char(str(i)) + + ob = String(arr) + assert ob == "0123456789" + + ob = String(arr, 5, 4) + assert ob == "5678" From b79ae2f405ddb457c7449f81bb350209cc84d522 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 17:13:54 +0200 Subject: [PATCH 053/183] Fix missing docstring for types with custom constructors (#1865) --- src/runtime/ClassManager.cs | 15 ++++++++++++--- tests/test_docstring.py | 7 +++++++ tests/test_enum.py | 5 ++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 6c5558f3a..79ab20e82 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -87,7 +87,7 @@ internal static ClassManagerState SaveRuntimeData() if ((Runtime.PyDict_DelItemString(dict.Borrow(), member) == -1) && (Exceptions.ExceptionMatches(Exceptions.KeyError))) { - // Trying to remove a key that's not in the dictionary + // Trying to remove a key that's not in the dictionary // raises an error. We don't care about it. Runtime.PyErr_Clear(); } @@ -215,7 +215,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p impl.indexer = info.indexer; impl.richcompare.Clear(); - + // Finally, initialize the class __dict__ and return the object. using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference); BorrowedReference dict = newDict.Borrow(); @@ -271,6 +271,15 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow()); } } + + if (Runtime.PySequence_Contains(dict, PyIdentifier.__doc__) != 1) + { + // Ensure that at least some doc string is set + using var fallbackDoc = Runtime.PyString_FromString( + $"Python wrapper for .NET type {type}" + ); + Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, fallbackDoc.Borrow()); + } } doc.Dispose(); @@ -562,7 +571,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) return ci; } - + /// /// This class owns references to PyObjects in the `members` member. /// The caller has responsibility to DECREF them. diff --git a/tests/test_docstring.py b/tests/test_docstring.py index 640a61915..36c925a74 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -25,3 +25,10 @@ def test_doc_without_ctor(): assert DocWithoutCtorTest.__doc__ == 'DocWithoutCtorTest Class' assert DocWithoutCtorTest.TestMethod.__doc__ == 'DocWithoutCtorTest TestMethod' assert DocWithoutCtorTest.StaticTestMethod.__doc__ == 'DocWithoutCtorTest StaticTestMethod' + + +def test_doc_primitve(): + from System import Int64, String + + assert Int64.__doc__ is not None + assert String.__doc__ is not None diff --git a/tests/test_enum.py b/tests/test_enum.py index 981fb735c..f24f95b36 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -15,7 +15,6 @@ def test_enum_standard_attrs(): assert DayOfWeek.__name__ == 'DayOfWeek' assert DayOfWeek.__module__ == 'System' assert isinstance(DayOfWeek.__dict__, DictProxyType) - assert DayOfWeek.__doc__ is None def test_enum_get_member(): @@ -139,7 +138,7 @@ def test_enum_undefined_value(): # This should fail because our test enum doesn't have it. with pytest.raises(ValueError): Test.FieldTest().EnumField = Test.ShortEnum(20) - + # explicitly permit undefined values Test.FieldTest().EnumField = Test.ShortEnum(20, True) @@ -157,6 +156,6 @@ def test_enum_conversion(): with pytest.raises(TypeError): Test.FieldTest().EnumField = "str" - + with pytest.raises(TypeError): Test.FieldTest().EnumField = 1 From 863397a2316f8e6a1a1a5fc20030d5d32b6a3431 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 17:15:23 +0200 Subject: [PATCH 054/183] Bump release candidate version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d15be84db..b9eb748c0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc2 +3.0.0-rc3 From 59b1c51daad161fc5f85452102188b75cc7284e3 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 14 Jul 2022 11:17:25 -0700 Subject: [PATCH 055/183] Finalizer.Instance.Collect() and Runtime.TryCollectingGarbage(...) are now callable from Python --- src/runtime/Finalizer.cs | 1 + src/runtime/Runtime.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index e796cacd1..f4b465ecb 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -106,6 +106,7 @@ internal IncorrectRefCountException(IntPtr ptr) #endregion + [ForbidPythonThreads] public void Collect() => this.DisposeAll(); internal void ThrottledCollect() diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 20bef23d4..88a8ed173 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -359,6 +359,7 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) /// /// Total number of GC loops to run /// true if a steady state was reached upon the requested number of tries (e.g. on the last try no objects were collected). + [ForbidPythonThreads] public static bool TryCollectingGarbage(int runs) => TryCollectingGarbage(runs, forceBreakLoops: false); From 469ec671fc9d7ee2f149a6687b5c7aeb5f861563 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 14 Jul 2022 11:18:01 -0700 Subject: [PATCH 056/183] fixed leak in NewReference.Move fixes https://github.com/pythonnet/pythonnet/issues/1872 --- src/runtime/Native/NewReference.cs | 2 +- tests/test_constructors.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/runtime/Native/NewReference.cs b/src/runtime/Native/NewReference.cs index 25145fc4f..f7a030818 100644 --- a/src/runtime/Native/NewReference.cs +++ b/src/runtime/Native/NewReference.cs @@ -47,7 +47,7 @@ public PyObject MoveToPyObject() /// public NewReference Move() { - var result = new NewReference(this); + var result = DangerousFromPointer(this.DangerousGetAddress()); this.pointer = default; return result; } diff --git a/tests/test_constructors.py b/tests/test_constructors.py index 6d8f00c12..3e0b1bb93 100644 --- a/tests/test_constructors.py +++ b/tests/test_constructors.py @@ -3,6 +3,7 @@ """Test CLR class constructor support.""" import pytest +import sys import System @@ -71,6 +72,19 @@ def test_default_constructor_fallback(): ob = DefaultConstructorMatching("2") +def test_constructor_leak(): + from System import Uri + from Python.Runtime import Runtime + + uri = Uri("http://www.python.org") + Runtime.TryCollectingGarbage(20) + ref_count = sys.getrefcount(uri) + + # check disabled due to GC uncertainty + # assert ref_count == 1 + + + def test_string_constructor(): from System import String, Char, Array From 93631aff83b34a0665374cd41313c8552b88b545 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 16 Jul 2022 12:08:59 +0200 Subject: [PATCH 057/183] Bump release candidate version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b9eb748c0..dc72b3783 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc3 +3.0.0-rc4 From a5e9b55bb0aa6620c59e9f108d17e1a617c07b78 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 16 Jul 2022 12:00:05 -0700 Subject: [PATCH 058/183] Generate XML documentation on build and add to it NuGet package (#1878) also fixed issues with xml docs in the code implements https://github.com/pythonnet/pythonnet/issues/1876 --- src/runtime/AssemblyManager.cs | 2 +- src/runtime/Python.Runtime.csproj | 2 ++ src/runtime/Runtime.cs | 1 + src/runtime/Types/ArrayObject.cs | 2 +- src/runtime/Types/EventBinding.cs | 1 - src/runtime/Types/ReflectedClrType.cs | 1 - 6 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index d09d2d76e..a8bbd1f6c 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -334,7 +334,7 @@ public static bool IsValidNamespace(string name) } /// - /// Returns an IEnumerable containing the namepsaces exported + /// Returns an enumerable collection containing the namepsaces exported /// by loaded assemblies in the current app domain. /// public static IEnumerable GetNamespaces () diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index fad5b9da8..5072f23cd 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -23,6 +23,8 @@ true snupkg + True + ..\pythonnet.snk true diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 88a8ed173..4dc904f43 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -99,6 +99,7 @@ internal static int GetRun() internal static bool HostedInPython; internal static bool ProcessIsTerminating; + /// /// Initialize the runtime... /// /// Always call this method from the Main thread. After the diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index bda717e56..b95934baf 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -520,7 +520,7 @@ static IntPtr AllocateBufferProcs() #endregion /// - /// + /// /// public static void InitializeSlots(PyType type, ISet initialized, SlotsHolder slotsHolder) { diff --git a/src/runtime/Types/EventBinding.cs b/src/runtime/Types/EventBinding.cs index 9eb2382ec..5c47d4aab 100644 --- a/src/runtime/Types/EventBinding.cs +++ b/src/runtime/Types/EventBinding.cs @@ -70,7 +70,6 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef return new NewReference(ob); } - /// public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val) => EventObject.tp_descr_set(ds, ob, val); diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 2e8f95924..b787939be 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -22,7 +22,6 @@ internal ReflectedClrType(BorrowedReference original) : base(original) { } /// /// /// Returned might be partially initialized. - /// If you need fully initialized type, use /// public static ReflectedClrType GetOrCreate(Type type) { From 60463a31b28b645114e17f9f55a8283d9ca74257 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 17 Jul 2022 09:09:05 +1000 Subject: [PATCH 059/183] docs: Fix a few typos There are small typos in: - pythonnet/__init__.py - tests/test_import.py Fixes: - Should read `splitted` rather than `splited`. - Should read `loaded` rather than `laoded`. Signed-off-by: Tim Gates --- pythonnet/__init__.py | 2 +- tests/test_import.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 9876a0bec..8f3478713 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -121,7 +121,7 @@ def load( def unload() -> None: - """Explicitly unload a laoded runtime and shut down Python.NET""" + """Explicitly unload a loaded runtime and shut down Python.NET""" global _RUNTIME, _LOADER_ASSEMBLY if _LOADER_ASSEMBLY is not None: diff --git a/tests/test_import.py b/tests/test_import.py index 25877be15..877eacd84 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -15,7 +15,7 @@ def test_relative_missing_import(): def test_import_all_on_second_time(): """Test import all attributes after a normal import without '*'. - Due to import * only allowed at module level, the test body splited + Due to import * only allowed at module level, the test body splitted to a module file.""" from . import importtest del sys.modules[importtest.__name__] From 85d0ca637d83c7dee2af6fb623dd3d1a05d66374 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 4 Aug 2022 11:01:17 -0700 Subject: [PATCH 060/183] mention the need for PythonEngine.Initialize and BeginAllowThreads in the README --- README.rst | 2 ++ src/runtime/README.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/README.rst b/README.rst index c2c2d53e1..ba4a56fbf 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,8 @@ Embedding Python in .NET (internal, derived from ``MissingMethodException``) upon calling ``Initialize``. Typical values are ``python38.dll`` (Windows), ``libpython3.8.dylib`` (Mac), ``libpython3.8.so`` (most other Unix-like operating systems). +- Then call ``PythonEngine.Initialize()``. If you plan to use Python objects from + multiple threads, also call ``PythonEngine.BeginAllowThreads()``. - All calls to python should be inside a ``using (Py.GIL()) {/* Your code here */}`` block. - Import python modules using ``dynamic mod = Py.Import("mod")``, then diff --git a/src/runtime/README.md b/src/runtime/README.md index ab75280ed..6c8eb2a6c 100644 --- a/src/runtime/README.md +++ b/src/runtime/README.md @@ -8,6 +8,9 @@ integrate Python engine and use Python libraries. (internal, derived from `MissingMethodException`) upon calling `Initialize`. Typical values are `python38.dll` (Windows), `libpython3.8.dylib` (Mac), `libpython3.8.so` (most other *nix). Full path may be required. +- Then call `PythonEngine.Initialize()`. If you plan to [use Python objects from + multiple threads](https://github.com/pythonnet/pythonnet/wiki/Threading), + also call `PythonEngine.BeginAllowThreads()`. - All calls to Python should be inside a `using (Py.GIL()) {/* Your code here */}` block. - Import python modules using `dynamic mod = Py.Import("mod")`, then From 5f63c67af223bc7703e58daa813ffe603bc9bc4f Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 8 Aug 2022 14:02:19 -0700 Subject: [PATCH 061/183] BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to (#1902) .NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the target type is a `System.Array` fixes https://github.com/pythonnet/pythonnet/issues/1900 --- CHANGELOG.md | 3 +++ src/runtime/Converter.cs | 5 ----- tests/test_conversion.py | 7 +++++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5dd1816..9f0f212e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,9 @@ or the DLL must be loaded in advance. This must be done before calling any other - BREAKING: disabled implicit conversion from C# enums to Python `int` and back. One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor (e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42). +- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to +.NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the +target type is a `System.Array`. - Sign Runtime DLL with a strong name - Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index e1820f05b..2e0edc3b5 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -389,11 +389,6 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, return true; } - if (Runtime.PySequence_Check(value)) - { - return ToArray(value, typeof(object[]), out result, setError); - } - result = new PyObject(value); return true; } diff --git a/tests/test_conversion.py b/tests/test_conversion.py index a5b4c6fd9..f8d8039c6 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -577,6 +577,13 @@ class Foo(object): ob.ObjectField = Foo assert ob.ObjectField == Foo + class PseudoSeq: + def __getitem__(self, idx): + return 0 + + ob.ObjectField = PseudoSeq() + assert ob.ObjectField.__class__.__name__ == "PseudoSeq" + def test_null_conversion(): """Test null conversion.""" From 090902112f3609d203f0c97a878e276336af336a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 12 Aug 2022 06:07:40 +0200 Subject: [PATCH 062/183] Explicit float and int conversion for builtin types (#1904) --- src/runtime/Native/ITypeOffsets.cs | 1 + src/runtime/Native/TypeOffset.cs | 1 + src/runtime/Types/ClassBase.cs | 54 +++++++++++++++++++++++++++++ src/runtime/Types/OperatorMethod.cs | 1 + tests/test_conversion.py | 25 +++++++++++++ 5 files changed, 82 insertions(+) diff --git a/src/runtime/Native/ITypeOffsets.cs b/src/runtime/Native/ITypeOffsets.cs index 2c4fdf59a..0920a312a 100644 --- a/src/runtime/Native/ITypeOffsets.cs +++ b/src/runtime/Native/ITypeOffsets.cs @@ -22,6 +22,7 @@ interface ITypeOffsets int nb_true_divide { get; } int nb_and { get; } int nb_int { get; } + int nb_float { get; } int nb_or { get; } int nb_xor { get; } int nb_lshift { get; } diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 59c5c3ee0..847f1766d 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -31,6 +31,7 @@ static partial class TypeOffset internal static int nb_or { get; private set; } internal static int nb_xor { get; private set; } internal static int nb_int { get; private set; } + internal static int nb_float { get; private set; } internal static int nb_lshift { get; private set; } internal static int nb_rshift { get; private set; } internal static int nb_remainder { get; private set; } diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 1e3c325cc..943841e25 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -529,6 +529,37 @@ static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, B return callBinder.Invoke(ob, args, kw); } + static NewReference DoConvert(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + using var python = self.inst.ToPython(); + return python.NewReferenceOrNull(); + } + + static NewReference DoConvertInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromLongLong(Convert.ToInt64(self.inst)); + } + + static NewReference DoConvertUInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromUnsignedLongLong(Convert.ToUInt64(self.inst)); + } + + static NewReference DoConvertBooleanInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyInt_FromInt32((bool)self.inst ? 1 : 0); + } + + static NewReference DoConvertFloat(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyFloat_FromDouble(Convert.ToDouble(self.inst)); + } + static IEnumerable GetCallImplementations(Type type) => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name == "__call__"); @@ -564,6 +595,29 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); } + + switch (Type.GetTypeCode(type.Value)) + { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertUInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Double: + case TypeCode.Single: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + } } public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index 0c6127f65..88939a51d 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -53,6 +53,7 @@ static OperatorMethod() ["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive), ["__int__"] = new SlotDefinition("__int__", TypeOffset.nb_int), + ["__float__"] = new SlotDefinition("__float__", TypeOffset.nb_float), }; ComparisonOpMap = new Dictionary { diff --git a/tests/test_conversion.py b/tests/test_conversion.py index f8d8039c6..69e7ec63f 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -720,3 +720,28 @@ def test_intptr_construction(): with pytest.raises(OverflowError): UIntPtr(v) +def test_explicit_conversion(): + from System import ( + Int64, UInt64, Int32, UInt32, Int16, UInt16, Byte, SByte, Boolean + ) + from System import Double, Single + + assert int(Boolean(False)) == 0 + assert int(Boolean(True)) == 1 + + for t in [UInt64, UInt32, UInt16, Byte]: + assert int(t(127)) == 127 + assert float(t(127)) == 127.0 + + for t in [Int64, Int32, Int16, SByte]: + assert int(t(127)) == 127 + assert int(t(-127)) == -127 + assert float(t(127)) == 127.0 + assert float(t(-127)) == -127.0 + + assert int(Int64.MaxValue) == 2**63 - 1 + assert int(Int64.MinValue) == -2**63 + assert int(UInt64.MaxValue) == 2**64 - 1 + + for t in [Single, Double]: + assert float(t(0.125)) == 0.125 From bd48dc118c62993307ad914f1776bd964a559563 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Aug 2022 23:43:38 +0200 Subject: [PATCH 063/183] Fill nb_index slot for integer types (#1907) --- src/runtime/Native/ITypeOffsets.cs | 1 + src/runtime/Native/TypeOffset.cs | 1 + src/runtime/Types/ClassBase.cs | 3 +++ src/runtime/Types/OperatorMethod.cs | 1 + src/runtime/Util/OpsHelper.cs | 6 +++++- tests/test_conversion.py | 13 ++++++++++--- 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/runtime/Native/ITypeOffsets.cs b/src/runtime/Native/ITypeOffsets.cs index 0920a312a..7d71b4b91 100644 --- a/src/runtime/Native/ITypeOffsets.cs +++ b/src/runtime/Native/ITypeOffsets.cs @@ -31,6 +31,7 @@ interface ITypeOffsets int nb_invert { get; } int nb_inplace_add { get; } int nb_inplace_subtract { get; } + int nb_index { get; } int ob_size { get; } int ob_type { get; } int qualname { get; } diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 847f1766d..803b86bfa 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -38,6 +38,7 @@ static partial class TypeOffset internal static int nb_invert { get; private set; } internal static int nb_inplace_add { get; private set; } internal static int nb_inplace_subtract { get; private set; } + internal static int nb_index { get; private set; } internal static int ob_size { get; private set; } internal static int ob_type { get; private set; } internal static int qualname { get; private set; } diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 943841e25..7296a1900 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -604,6 +604,7 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH case TypeCode.Int32: case TypeCode.Int64: TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; case TypeCode.Byte: @@ -611,10 +612,12 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH case TypeCode.UInt32: case TypeCode.UInt64: TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertUInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertUInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; case TypeCode.Double: case TypeCode.Single: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; } diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index 88939a51d..e3cc23370 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -54,6 +54,7 @@ static OperatorMethod() ["__int__"] = new SlotDefinition("__int__", TypeOffset.nb_int), ["__float__"] = new SlotDefinition("__float__", TypeOffset.nb_float), + ["__index__"] = new SlotDefinition("__index__", TypeOffset.nb_index), }; ComparisonOpMap = new Dictionary { diff --git a/src/runtime/Util/OpsHelper.cs b/src/runtime/Util/OpsHelper.cs index 12d0c7aa6..7c35f27eb 100644 --- a/src/runtime/Util/OpsHelper.cs +++ b/src/runtime/Util/OpsHelper.cs @@ -82,10 +82,14 @@ internal static class EnumOps where T : Enum { [ForbidPythonThreads] #pragma warning disable IDE1006 // Naming Styles - must match Python - public static PyInt __int__(T value) + public static PyInt __index__(T value) #pragma warning restore IDE1006 // Naming Styles => typeof(T).GetEnumUnderlyingType() == typeof(UInt64) ? new PyInt(Convert.ToUInt64(value)) : new PyInt(Convert.ToInt64(value)); + [ForbidPythonThreads] +#pragma warning disable IDE1006 // Naming Styles - must match Python + public static PyInt __int__(T value) => __index__(value); +#pragma warning restore IDE1006 // Naming Styles } } diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 69e7ec63f..bb686dd52 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -721,6 +721,7 @@ def test_intptr_construction(): UIntPtr(v) def test_explicit_conversion(): + from operator import index from System import ( Int64, UInt64, Int32, UInt32, Int16, UInt16, Byte, SByte, Boolean ) @@ -730,18 +731,24 @@ def test_explicit_conversion(): assert int(Boolean(True)) == 1 for t in [UInt64, UInt32, UInt16, Byte]: + assert index(t(127)) == 127 assert int(t(127)) == 127 assert float(t(127)) == 127.0 for t in [Int64, Int32, Int16, SByte]: + assert index(t(127)) == 127 + assert index(t(-127)) == -127 assert int(t(127)) == 127 assert int(t(-127)) == -127 assert float(t(127)) == 127.0 assert float(t(-127)) == -127.0 - assert int(Int64.MaxValue) == 2**63 - 1 - assert int(Int64.MinValue) == -2**63 - assert int(UInt64.MaxValue) == 2**64 - 1 + assert int(Int64(Int64.MaxValue)) == 2**63 - 1 + assert int(Int64(Int64.MinValue)) == -2**63 + assert int(UInt64(UInt64.MaxValue)) == 2**64 - 1 for t in [Single, Double]: assert float(t(0.125)) == 0.125 + assert int(t(123.4)) == 123 + with pytest.raises(TypeError): + index(t(123.4)) From 4241493468e1e939b7ab5670118ead68bc7a9e07 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 15 Sep 2022 21:08:45 -0700 Subject: [PATCH 064/183] fixed use of the process handle after Process instance goes out of scope (#1940) fixes https://github.com/pythonnet/pythonnet/issues/1939 --- src/runtime/Native/LibraryLoader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtime/Native/LibraryLoader.cs b/src/runtime/Native/LibraryLoader.cs index 6d67bea61..05f960481 100644 --- a/src/runtime/Native/LibraryLoader.cs +++ b/src/runtime/Native/LibraryLoader.cs @@ -137,17 +137,17 @@ public IntPtr GetFunction(IntPtr hModule, string procedureName) static IntPtr[] GetAllModules() { - var self = Process.GetCurrentProcess().Handle; + using var self = Process.GetCurrentProcess(); uint bytes = 0; var result = new IntPtr[0]; - if (!EnumProcessModules(self, result, bytes, out var needsBytes)) + if (!EnumProcessModules(self.Handle, result, bytes, out var needsBytes)) throw new Win32Exception(); while (bytes < needsBytes) { bytes = needsBytes; result = new IntPtr[bytes / IntPtr.Size]; - if (!EnumProcessModules(self, result, bytes, out needsBytes)) + if (!EnumProcessModules(self.Handle, result, bytes, out needsBytes)) throw new Win32Exception(); } return result.Take((int)(needsBytes / IntPtr.Size)).ToArray(); From bdf671eebaa994908d905ba10f298ffcca71785d Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 15 Sep 2022 21:09:50 -0700 Subject: [PATCH 065/183] fixed new line character at the end of informational version (#1941) --- Directory.Build.props | 2 +- pythonnet.sln | 1 + tests/test_module.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8c5b53685..72a519f4f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ Python.NET 10.0 false - $([System.IO.File]::ReadAllText("version.txt")) + $([System.IO.File]::ReadAllText("version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) diff --git a/pythonnet.sln b/pythonnet.sln index eb97cfbd0..d1a47892e 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F CHANGELOG.md = CHANGELOG.md LICENSE = LICENSE README.rst = README.rst + version.txt = version.txt EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/tests/test_module.py b/tests/test_module.py index 4e1a1a1ef..ddfa7bb36 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -31,6 +31,7 @@ def test_import_clr(): def test_version_clr(): import clr assert clr.__version__ >= "3.0.0" + assert clr.__version__[-1] != "\n" def test_preload_var(): From 3f124699e4bdcf0b0fd5904c5b7f4cf77d601064 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 16 Sep 2022 21:21:18 +0200 Subject: [PATCH 066/183] Bump clr-loader dependency to 0.2.3 and adjust interface - Supports loading without explicitly specifying the runtime config now - Exposes information on the loaded runtime --- pyproject.toml | 2 +- pythonnet/__init__.py | 21 ++++++++++++++------- tests/conftest.py | 10 ++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ee89d3b7..39c7c14fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = {text = "MIT"} readme = "README.rst" dependencies = [ - "clr_loader>=0.1.7" + "clr_loader>=0.2.2,<0.3.0" ] classifiers = [ diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 9876a0bec..c847c4c74 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -1,12 +1,12 @@ import sys from pathlib import Path -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Any import clr_loader __all__ = ["set_runtime", "set_runtime_from_env", "load"] _RUNTIME: Optional[clr_loader.Runtime] = None -_LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None +_LOADER_ASSEMBLY: Optional[clr_loader.Assembly] = None _LOADED: bool = False @@ -27,6 +27,13 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: _RUNTIME = runtime +def get_runtime_info() -> Optional[clr_loader.RuntimeInfo]: + if _RUNTIME is None: + return None + else: + return _RUNTIME.info() + + def _get_params_from_env(prefix: str) -> Dict[str, str]: from os import environ @@ -43,7 +50,7 @@ def _get_params_from_env(prefix: str) -> Dict[str, str]: def _create_runtime_from_spec( - spec: str, params: Optional[Dict[str, str]] = None + spec: str, params: Optional[Dict[str, Any]] = None ) -> clr_loader.Runtime: if spec == "default": if sys.platform == "win32": @@ -109,9 +116,9 @@ def load( dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll" - _LOADER_ASSEMBLY = _RUNTIME.get_assembly(str(dll_path)) + _LOADER_ASSEMBLY = assembly = _RUNTIME.get_assembly(str(dll_path)) + func = assembly.get_function("Python.Runtime.Loader.Initialize") - func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] if func(b"") != 0: raise RuntimeError("Failed to initialize Python.Runtime.dll") @@ -125,12 +132,12 @@ def unload() -> None: global _RUNTIME, _LOADER_ASSEMBLY if _LOADER_ASSEMBLY is not None: - func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] + func = _LOADER_ASSEMBLY.get_function("Python.Runtime.Loader.Shutdown") if func(b"full_shutdown") != 0: raise RuntimeError("Failed to call Python.NET shutdown") _LOADER_ASSEMBLY = None if _RUNTIME is not None: - # TODO: Add explicit `close` to clr_loader + _RUNTIME.shutdown() _RUNTIME = None diff --git a/tests/conftest.py b/tests/conftest.py index fcd1d224a..e61e3680e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,10 +53,12 @@ def pytest_configure(config): runtime_params = {} if runtime_opt == "coreclr": - fw = "net6.0" - runtime_params["runtime_config"] = str( - bin_path / "Python.Test.runtimeconfig.json" - ) + # This is optional now: + # + # fw = "net6.0" + # runtime_params["runtime_config"] = str( + # bin_path / "Python.Test.runtimeconfig.json" + # ) collect_ignore.append("domain_tests/test_domain_reload.py") else: domain_tests_dir = cwd / "domain_tests" From db52ddef247cdc008bae18b5ad0fee2f1c690b21 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 16 Sep 2022 21:34:45 +0200 Subject: [PATCH 067/183] Improve error message if the runtime fails to load --- pythonnet/__init__.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index c847c4c74..2cde01233 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -28,6 +28,8 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: def get_runtime_info() -> Optional[clr_loader.RuntimeInfo]: + """Retrieve information on the configured runtime""" + if _RUNTIME is None: return None else: @@ -52,7 +54,9 @@ def _get_params_from_env(prefix: str) -> Dict[str, str]: def _create_runtime_from_spec( spec: str, params: Optional[Dict[str, Any]] = None ) -> clr_loader.Runtime: + was_default = False if spec == "default": + was_default = True if sys.platform == "win32": spec = "netfx" else: @@ -60,14 +64,29 @@ def _create_runtime_from_spec( params = params or _get_params_from_env(spec) - if spec == "netfx": - return clr_loader.get_netfx(**params) - elif spec == "mono": - return clr_loader.get_mono(**params) - elif spec == "coreclr": - return clr_loader.get_coreclr(**params) - else: - raise RuntimeError(f"Invalid runtime name: '{spec}'") + try: + if spec == "netfx": + return clr_loader.get_netfx(**params) + elif spec == "mono": + return clr_loader.get_mono(**params) + elif spec == "coreclr": + return clr_loader.get_coreclr(**params) + else: + raise RuntimeError(f"Invalid runtime name: '{spec}'") + except Exception as exc: + if was_default: + raise RuntimeError( + f"""Failed to create a default .NET runtime, which would + have been "{spec}" on this system. Either install a + compatible runtime or configure it explicitly via + `set_runtime` or the `PYTHONNET_*` environment variables + (see set_runtime_from_env).""" + ) from exc + else: + raise RuntimeError( + f"""Failed to create a .NET runtime ({spec}) using the + parameters {params}.""" + ) from exc def set_runtime_from_env() -> None: @@ -92,9 +111,7 @@ def set_runtime_from_env() -> None: set_runtime(runtime) -def load( - runtime: Union[clr_loader.Runtime, str, None] = None, **params: str -) -> None: +def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> None: """Load Python.NET in the specified runtime The same parameters as for `set_runtime` can be used. By default, From fd8fd3b88edbc59237a9c00176006657a1a4dc63 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 17 Sep 2022 08:55:28 +0200 Subject: [PATCH 068/183] Fix manifest so we can build with python -m build --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 6458d5778..71473c2c3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ graft src/runtime prune src/runtime/obj prune src/runtime/bin +include src/pythonnet.snk include Directory.Build.* include pythonnet.sln include version.txt From 91d3e55f965ae7fcaf9845d48c3076149dd5a712 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 17 Sep 2022 01:48:28 -0700 Subject: [PATCH 069/183] reenabled test for https://github.com/pythonnet/pythonnet/issues/1256 : NoStackOverflowOnSystemType (#1943) --- src/embed_tests/Codecs.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index c9e83f03a..be416bc15 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -285,7 +285,6 @@ public void IterableDecoderTest() } // regression for https://github.com/pythonnet/pythonnet/issues/1427 - [Ignore("https://github.com/pythonnet/pythonnet/issues/1256")] [Test] public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() { From f1ef11de8e0e47004c51e4419b419b12976ff195 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 18 Sep 2022 13:06:06 +0200 Subject: [PATCH 070/183] Bump release candidate version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index dc72b3783..916e2438f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc4 +3.0.0-rc5 From a02799d5d2cafb89f00b3a7a57c0c609914420c2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 19 Sep 2022 18:03:38 +0200 Subject: [PATCH 071/183] Fix implicit float conversion for classes that derive from float --- src/runtime/Runtime.cs | 4 +--- tests/test_method.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 4dc904f43..d09c89e2c 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1145,9 +1145,7 @@ internal static NewReference PyLong_FromString(string value, int radix) } internal static bool PyFloat_Check(BorrowedReference ob) - { - return PyObject_TYPE(ob) == PyFloatType; - } + => PyObject_TypeCheck(ob, PyFloatType); /// /// Return value: New reference. diff --git a/tests/test_method.py b/tests/test_method.py index b24b525aa..b86bbd6b4 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -1256,3 +1256,41 @@ def test_method_encoding(): def test_method_with_pointer_array_argument(): with pytest.raises(TypeError): MethodTest.PointerArray([0]) + +def test_method_call_implicit_conversion(): + + class IntAnswerMixin: + # For Python >= 3.8 + def __index__(self): + return 42 + + # For Python < 3.10 + def __int__(self): + return 42 + + class Answer(int, IntAnswerMixin): + pass + + class FloatAnswer(float, IntAnswerMixin): + def __float__(self): + return 42.0 + + # TODO: This should also work for integer types but due to some complexities + # in the C-API functions (some call __int__/__index__, some don't), it's not + # supported, yet. + for v in [Answer(), FloatAnswer()]: + for t in [System.Double, System.Single]: + min_value = t(t.MinValue) + compare_to = min_value.CompareTo.__overloads__[t] + + assert compare_to(v) == -1 + + class SomeNonFloat: + def __float__(self): + return 42.0 + + for t in [System.Double, System.Single]: + with pytest.raises(TypeError): + min_value = t(t.MinValue) + compare_to = min_value.CompareTo.__overloads__[t] + assert compare_to(SomeNonFloat()) == -1 From 2194d6c3ee392b85825c01d134dbd68e7e91fc39 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 19 Sep 2022 18:04:14 +0200 Subject: [PATCH 072/183] Drop PyLong_Check (which checked for exact type) in favour of PyInt_Check (which checks for subtype) --- src/runtime/Converter.cs | 2 +- src/runtime/Runtime.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 2e0edc3b5..3c46e9034 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -710,7 +710,7 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec { if (Runtime.Is32Bit) { - if (!Runtime.PyLong_Check(value)) + if (!Runtime.PyInt_Check(value)) { goto type_error; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index d09c89e2c..6238119ff 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1101,11 +1101,6 @@ internal static bool PyBool_Check(BorrowedReference ob) internal static NewReference PyInt_FromInt64(long value) => PyLong_FromLongLong(value); - internal static bool PyLong_Check(BorrowedReference ob) - { - return PyObject_TYPE(ob) == PyLongType; - } - internal static NewReference PyLong_FromLongLong(long value) => Delegates.PyLong_FromLongLong(value); From 15a8b26f63af66baf811f880d24b81f2de2a431d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 20 Sep 2022 23:16:04 +0200 Subject: [PATCH 073/183] Bump release candidate version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 916e2438f..7f51b23b1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc5 +3.0.0-rc6 From 08fd638119b97e60f154f11fc1f103077bcd03ed Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 26 May 2022 13:42:57 +0200 Subject: [PATCH 074/183] Add initial documentation setup --- .github/workflows/docs.yml | 40 + doc/.gitignore | 2 + doc/Doxyfile | 2656 ++++++++++++++++++++++++++++++++++++ doc/Makefile | 20 + doc/make.bat | 35 + doc/requirements.txt | 12 + doc/source/_static/.keep | 0 doc/source/conf.py | 59 + doc/source/dotnet.md | 136 ++ doc/source/index.rst | 66 + doc/source/python.md | 496 +++++++ doc/source/reference.rst | 42 + 12 files changed, 3564 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 doc/.gitignore create mode 100644 doc/Doxyfile create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 doc/requirements.txt create mode 100644 doc/source/_static/.keep create mode 100644 doc/source/conf.py create mode 100644 doc/source/dotnet.md create mode 100644 doc/source/index.rst create mode 100644 doc/source/python.md create mode 100644 doc/source/reference.rst diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..5b782c8b4 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,40 @@ +name: Documentation + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Doxygen Action + uses: mattnotmitt/doxygen-action@1.9.4 + with: + working-directory: "doc/" + + - name: Build Sphinx documentation + run: | + pip install -r doc/requirements.txt + sphinx-build doc/source/ ./doc/build/html/ + + - name: Upload artifact + # Automatically uploads an artifact from the './_site' directory by default + uses: actions/upload-pages-artifact@v1 + with: + path: doc/build/html/ + + deploy: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 000000000..8bbfac5f7 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +doxygen_xml/ +build/ diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 000000000..c8eb3b525 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,2656 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Python.NET" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../src/runtime/ ../pythonnet/ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = NO + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /