diff --git a/src/embed_tests/Inheritance.cs b/src/embed_tests/Inheritance.cs new file mode 100644 index 000000000..50a461adb --- /dev/null +++ b/src/embed_tests/Inheritance.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class Inheritance + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + var locals = new PyDict(); + PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode, locals: locals.Handle); + ExtraBaseTypeProvider.ExtraBase = new PyType(locals[InheritanceTestBaseClassWrapper.ClassName]); + var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; + baseTypeProviders.Add(new ExtraBaseTypeProvider()); + baseTypeProviders.Add(new NoEffectBaseTypeProvider()); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void ExtraBase_PassesInstanceCheck() + { + var inherited = new Inherited(); + bool properlyInherited = PyIsInstance(inherited, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } + + static dynamic PyIsInstance => PythonEngine.Eval("isinstance"); + + [Test] + public void InheritingWithExtraBase_CreatesNewClass() + { + PyObject a = ExtraBaseTypeProvider.ExtraBase; + var inherited = new Inherited(); + PyObject inheritedClass = inherited.ToPython().GetAttr("__class__"); + Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a, inheritedClass)); + } + + [Test] + public void InheritedFromInheritedClassIsSelf() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInstance = b.Invoke(); + PyObject bInstanceClass = bInstance.GetAttr("__class__"); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b, bInstanceClass)); + } + + [Test] + public void Grandchild_PassesExtraBaseInstanceCheck() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInst = b.Invoke(); + bool properlyInherited = PyIsInstance(bInst, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } + + [Test] + public void CallInheritedClrMethod_WithExtraPythonBase() + { + var instance = new Inherited().ToPython(); + string result = instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As(); + Assert.AreEqual(result, nameof(PythonWrapperBase.WrapperBaseMethod)); + } + + [Test] + public void CallExtraBaseMethod() + { + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + int actual = instance.ToPython().InvokeMethod("callVirt").As(); + Assert.AreEqual(expected: Inherited.OverridenVirtValue, actual); + } + + [Test] + public void SetAdHocAttributes_WhenExtraBasePresent() + { + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + scope.Exec($"super({nameof(instance)}.__class__, {nameof(instance)}).set_x_to_42()"); + int actual = scope.Eval($"{nameof(instance)}.{nameof(Inherited.XProp)}"); + Assert.AreEqual(expected: Inherited.X, actual); + } + } + + class ExtraBaseTypeProvider : IPythonBaseTypeProvider + { + internal static PyType ExtraBase; + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type == typeof(InheritanceTestBaseClassWrapper)) + { + return new[] { PyType.Get(type.BaseType), ExtraBase }; + } + return existingBases; + } + } + + class NoEffectBaseTypeProvider : IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + => existingBases; + } + + public class PythonWrapperBase + { + public string WrapperBaseMethod() => nameof(WrapperBaseMethod); + } + + public class InheritanceTestBaseClassWrapper : PythonWrapperBase + { + public const string ClassName = "InheritanceTestBaseClass"; + public const string ClassSourceCode = "class " + ClassName + +@": + def virt(self): + return 42 + def set_x_to_42(self): + self.XProp = 42 + def callVirt(self): + return self.virt() + def __getattr__(self, name): + return '__getattr__:' + name + def __setattr__(self, name, value): + value[name] = name +" + ClassName + " = " + ClassName + "\n"; + } + + public class Inherited : InheritanceTestBaseClassWrapper + { + public const int OverridenVirtValue = -42; + public const int X = 42; + readonly Dictionary extras = new Dictionary(); + public int virt() => OverridenVirtValue; + public int XProp + { + get + { + using (var scope = Py.CreateScope()) + { + scope.Set("this", this); + try + { + return scope.Eval($"super(this.__class__, this).{nameof(XProp)}"); + } + catch (PythonException ex) when (ex.Type.Handle == Exceptions.AttributeError) + { + if (this.extras.TryGetValue(nameof(this.XProp), out object value)) + return (int)value; + throw; + } + } + } + set => this.extras[nameof(this.XProp)] = value; + } + } +} diff --git a/src/runtime/DefaultBaseTypeProvider.cs b/src/runtime/DefaultBaseTypeProvider.cs new file mode 100644 index 000000000..92acb47cf --- /dev/null +++ b/src/runtime/DefaultBaseTypeProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Python.Runtime +{ + /// Minimal Python base type provider + public sealed class DefaultBaseTypeProvider : IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + if (existingBases.Count > 0) + throw new ArgumentException("To avoid confusion, this type provider requires the initial set of base types to be empty"); + + return new[] { new PyType(GetBaseType(type)) }; + } + + static BorrowedReference GetBaseType(Type type) + { + if (type == typeof(Exception)) + return new BorrowedReference(Exceptions.Exception); + + return type.BaseType is not null + ? ClassManager.GetClass(type.BaseType).ObjectReference + : new BorrowedReference(Runtime.PyBaseObjectType); + } + + DefaultBaseTypeProvider(){} + public static DefaultBaseTypeProvider Instance { get; } = new DefaultBaseTypeProvider(); + } +} diff --git a/src/runtime/IPythonBaseTypeProvider.cs b/src/runtime/IPythonBaseTypeProvider.cs new file mode 100644 index 000000000..14e65afdc --- /dev/null +++ b/src/runtime/IPythonBaseTypeProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Python.Runtime +{ + public interface IPythonBaseTypeProvider + { + /// + /// Get Python types, that should be presented to Python as the base types + /// for the specified .NET type. + /// + IEnumerable GetBaseTypes(Type type, IList existingBases); + } +} diff --git a/src/runtime/InteropConfiguration.cs b/src/runtime/InteropConfiguration.cs new file mode 100644 index 000000000..6853115fe --- /dev/null +++ b/src/runtime/InteropConfiguration.cs @@ -0,0 +1,25 @@ +namespace Python.Runtime +{ + using System; + using System.Collections.Generic; + + public sealed class InteropConfiguration + { + internal readonly PythonBaseTypeProviderGroup pythonBaseTypeProviders + = new PythonBaseTypeProviderGroup(); + + /// Enables replacing base types of CLR types as seen from Python + public IList PythonBaseTypeProviders => this.pythonBaseTypeProviders; + + public static InteropConfiguration MakeDefault() + { + return new InteropConfiguration + { + PythonBaseTypeProviders = + { + DefaultBaseTypeProvider.Instance, + }, + }; + } + } +} diff --git a/src/runtime/PythonBaseTypeProviderGroup.cs b/src/runtime/PythonBaseTypeProviderGroup.cs new file mode 100644 index 000000000..d25ae473a --- /dev/null +++ b/src/runtime/PythonBaseTypeProviderGroup.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Python.Runtime +{ + class PythonBaseTypeProviderGroup : List, IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + + foreach (var provider in this) + { + existingBases = provider.GetBaseTypes(type, existingBases).ToList(); + } + + return existingBases; + } + } +} diff --git a/src/runtime/PythonReferenceComparer.cs b/src/runtime/PythonReferenceComparer.cs new file mode 100644 index 000000000..d05e5191f --- /dev/null +++ b/src/runtime/PythonReferenceComparer.cs @@ -0,0 +1,22 @@ +#nullable enable +using System.Collections.Generic; + +namespace Python.Runtime +{ + /// + /// Compares Python object wrappers by Python object references. + /// Similar to but for Python objects + /// + public sealed class PythonReferenceComparer : IEqualityComparer + { + public static PythonReferenceComparer Instance { get; } = new PythonReferenceComparer(); + public bool Equals(PyObject? x, PyObject? y) + { + return x?.Handle == y?.Handle; + } + + public int GetHashCode(PyObject obj) => obj.Handle.GetHashCode(); + + private PythonReferenceComparer() { } + } +} diff --git a/src/runtime/StolenReference.cs b/src/runtime/StolenReference.cs index 1130cff06..415fedc7f 100644 --- a/src/runtime/StolenReference.cs +++ b/src/runtime/StolenReference.cs @@ -35,6 +35,13 @@ public override bool Equals(object obj) [Pure] public override int GetHashCode() => Pointer.GetHashCode(); + + [Pure] + public static StolenReference DangerousFromPointer(IntPtr ptr) + { + if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr)); + return new StolenReference(ptr); + } } static class StolenReferenceExtensions diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 811b802c9..18b9f6911 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -252,6 +252,14 @@ private static ClassBase CreateClass(Type type) private static void InitClassBase(Type type, ClassBase impl) { + // Ensure, that matching Python type exists first. + // It is required for self-referential classes + // (e.g. with members, that refer to the same class) + var pyType = TypeManager.GetOrCreateClass(type); + + // Set the handle attributes on the implementing instance. + impl.tpHandle = impl.pyHandle = pyType.Handle; + // First, we introspect the managed type and build some class // information, including generating the member descriptors // that we'll be putting in the Python class __dict__. @@ -261,12 +269,12 @@ private static void InitClassBase(Type type, ClassBase impl) impl.indexer = info.indexer; impl.richcompare = new Dictionary(); - // Now we allocate the Python type object to reflect the given + // Now we force initialize the Python type object to reflect the given // managed type, filling the Python type slots with thunks that // point to the managed methods providing the implementation. - var pyType = TypeManager.GetType(impl, type); + TypeManager.GetOrInitializeClass(impl, type); // Finally, initialize the class __dict__ and return the object. using var dict = Runtime.PyObject_GenericGetDict(pyType.Reference); diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index f6340a59c..13df54a5d 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -26,6 +26,7 @@ public static ShutdownMode ShutdownMode private static IntPtr _pythonHome = IntPtr.Zero; private static IntPtr _programName = IntPtr.Zero; private static IntPtr _pythonPath = IntPtr.Zero; + private static InteropConfiguration interopConfiguration = InteropConfiguration.MakeDefault(); public PythonEngine() { @@ -68,6 +69,18 @@ internal static DelegateManager DelegateManager } } + public static InteropConfiguration InteropConfiguration + { + get => interopConfiguration; + set + { + if (IsInitialized) + throw new NotSupportedException("Changing interop configuration when engine is running is not supported"); + + interopConfiguration = value ?? throw new ArgumentNullException(nameof(InteropConfiguration)); + } + } + public static string ProgramName { get @@ -334,6 +347,8 @@ public static void Shutdown(ShutdownMode mode) PyObjectConversions.Reset(); initialized = false; + + InteropConfiguration = InteropConfiguration.MakeDefault(); } /// @@ -563,7 +578,7 @@ public static ulong GetPythonThreadID() /// Interrupts the execution of a thread. /// /// The Python thread ID. - /// The number of thread states modified; this is normally one, but will be zero if the thread id isn’t found. + /// The number of thread states modified; this is normally one, but will be zero if the thread id is not found. public static int Interrupt(ulong pythonThreadID) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/runtime/pytype.cs b/src/runtime/pytype.cs index e9c80ebf3..afa957ecb 100644 --- a/src/runtime/pytype.cs +++ b/src/runtime/pytype.cs @@ -44,6 +44,15 @@ public string Name } } + /// Returns true when type is fully initialized + public bool IsReady => Flags.HasFlag(TypeFlags.Ready); + + internal TypeFlags Flags + { + get => (TypeFlags)Util.ReadCLong(Handle, TypeOffset.tp_flags); + set => Util.WriteCLong(Handle, TypeOffset.tp_flags, (long)value); + } + /// Checks if specified object is a Python type. public static bool IsType(PyObject value) { @@ -52,6 +61,39 @@ public static bool IsType(PyObject value) return Runtime.PyType_Check(value.obj); } + /// + /// Gets , which represents the specified CLR type. + /// Must be called after the CLR type was mapped to its Python type. + /// + internal static PyType Get(Type clrType) + { + if (clrType == null) + { + throw new ArgumentNullException(nameof(clrType)); + } + + ClassBase pyClass = ClassManager.GetClass(clrType); + return new PyType(pyClass.ObjectReference); + } + + internal BorrowedReference BaseReference + { + get + { + return new(Marshal.ReadIntPtr(Handle, TypeOffset.tp_base)); + } + set + { + var old = BaseReference.DangerousGetAddressOrNull(); + IntPtr @new = value.DangerousGetAddress(); + + Runtime.XIncref(@new); + Marshal.WriteIntPtr(Handle, TypeOffset.tp_base, @new); + + Runtime.XDecref(old); + } + } + internal IntPtr GetSlot(TypeSlotID slot) { IntPtr result = Runtime.PyType_GetSlot(this.Reference, slot); diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index e1bfe6aef..8d5600e4f 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -19,6 +19,7 @@ internal class TypeManager { internal static IntPtr subtype_traverse; internal static IntPtr subtype_clear; + internal static IPythonBaseTypeProvider pythonBaseTypeProvider; private const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static; private static Dictionary cache = new(); @@ -41,6 +42,7 @@ internal static void Initialize() subtype_traverse = Marshal.ReadIntPtr(type, TypeOffset.tp_traverse); subtype_clear = Marshal.ReadIntPtr(type, TypeOffset.tp_clear); Runtime.XDecref(type); + pythonBaseTypeProvider = PythonEngine.InteropConfiguration.pythonBaseTypeProviders; } internal static void RemoveTypes() @@ -115,18 +117,36 @@ internal static PyType GetType(Type type) internal static BorrowedReference GetTypeReference(Type type) => GetType(type).Reference; + /// + /// Get the fully initialized Python type that reflects the given CLR type. + /// The given ManagedType instance is a managed object that implements + /// the appropriate semantics in Python for the reflected managed type. + /// + internal static PyType GetOrInitializeClass(ClassBase obj, Type type) + { + var pyType = GetOrCreateClass(type); + if (!pyType.IsReady) + { + InitializeClass(pyType, obj, type); + _slotsImpls.Add(type, obj.GetType()); + } + return pyType; + } + /// /// Get the Python type that reflects the given CLR type. /// The given ManagedType instance is a managed object that implements /// the appropriate semantics in Python for the reflected managed type. /// - internal static PyType GetType(ClassBase obj, Type type) + /// + /// Returned might be partially initialized. + /// If you need fully initialized type, use + /// + internal static PyType GetOrCreateClass(Type type) { if (!cache.TryGetValue(type, out var pyType)) { - pyType = CreateType(obj, type); - cache[type] = pyType; - _slotsImpls.Add(type, obj.GetType()); + pyType = CreateClass(type); } return pyType; } @@ -143,11 +163,14 @@ internal static PyType GetType(ClassBase obj, Type type) internal static unsafe PyType CreateType(Type impl) { IntPtr type = AllocateTypeObject(impl.Name, metatype: Runtime.PyCLRMetaType); + // TODO: use PyType(TypeSpec) constructor + var pyType = new PyType(StolenReference.DangerousFromPointer(type)); + IntPtr base_ = impl == typeof(CLRModule) ? Runtime.PyModuleType : Runtime.PyBaseObjectType; - int newFieldOffset = InheritOrAllocateStandardFields(type, base_); + int newFieldOffset = InheritOrAllocateStandardFields(type, new BorrowedReference(base_)); int tp_clr_inst_offset = newFieldOffset; newFieldOffset += IntPtr.Size; @@ -161,18 +184,14 @@ internal static unsafe PyType CreateType(Type impl) SlotsHolder slotsHolder = CreateSolotsHolder(type); InitializeSlots(type, impl, slotsHolder); - var flags = TypeFlags.Default | TypeFlags.HasClrInstance | - TypeFlags.HeapType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); + pyType.Flags = TypeFlags.Default | TypeFlags.HasClrInstance | + TypeFlags.HeapType | TypeFlags.HaveGC; if (Runtime.PyType_Ready(type) != 0) { throw PythonException.ThrowLastAsClrException(); } - // TODO: use PyType(TypeSpec) constructor - var pyType = new PyType(new BorrowedReference(type)); - Runtime.XDecref(type); NewReference dict = Runtime.PyObject_GenericGetDict(pyType.Reference); var mod = NewReference.DangerousFromPointer(Runtime.PyString_FromString("CLR")); @@ -190,7 +209,7 @@ internal static unsafe PyType CreateType(Type impl) } - internal static PyType CreateType(ClassBase impl, Type clrType) + static PyType CreateClass(Type clrType) { // Cleanup the type name to get rid of funny nested type names. string name = $"clr.{clrType.FullName}"; @@ -205,28 +224,54 @@ internal static PyType CreateType(ClassBase impl, Type clrType) name = name.Substring(i + 1); } - IntPtr base_ = Runtime.PyBaseObjectType; - if (clrType == typeof(Exception)) + using var baseTuple = GetBaseTypeTuple(clrType); + + IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType); + var pyType = new PyType(StolenReference.DangerousFromPointer(type)); + pyType.Flags = TypeFlags.Default + | TypeFlags.HasClrInstance + | TypeFlags.HeapType + | TypeFlags.BaseType + | TypeFlags.HaveGC; + + cache.Add(clrType, pyType); + try { - base_ = Exceptions.Exception; + InitializeBases(pyType, baseTuple); + // core fields must be initialized in partically constructed classes, + // otherwise it would be impossible to manipulate GCHandle and check type size + InitializeCoreFields(pyType); } - else if (clrType.BaseType != null) + catch { - ClassBase bc = ClassManager.GetClass(clrType.BaseType); - if (bc.ObjectReference != null) - { - // there are cases when base class has not been fully initialized yet (nested types) - base_ = bc.pyHandle; - } + cache.Remove(clrType); + throw; } - IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType); + return pyType; + } - int newFieldOffset = InheritOrAllocateStandardFields(type, base_); + static BorrowedReference InitializeBases(PyType pyType, PyTuple baseTuple) + { + Debug.Assert(baseTuple.Length() > 0); + var primaryBase = baseTuple[0].Reference; + pyType.BaseReference = primaryBase; + + if (baseTuple.Length() > 1) + { + Marshal.WriteIntPtr(pyType.Handle, TypeOffset.tp_bases, baseTuple.NewReferenceOrNull().DangerousMoveToPointer()); + } + return primaryBase; + } + + static void InitializeCoreFields(PyType pyType) + { + IntPtr type = pyType.Handle; + int newFieldOffset = InheritOrAllocateStandardFields(type); - if (ManagedType.IsManagedType(new BorrowedReference(base_))) + if (ManagedType.IsManagedType(pyType.BaseReference)) { - int baseClrInstOffset = Marshal.ReadInt32(base_, ManagedType.Offsets.tp_clr_inst_offset); + int baseClrInstOffset = Marshal.ReadInt32(pyType.BaseReference.DangerousGetAddress(), ManagedType.Offsets.tp_clr_inst_offset); Marshal.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, baseClrInstOffset); } else @@ -239,6 +284,11 @@ internal static PyType CreateType(ClassBase impl, Type clrType) Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); + } + + static void InitializeClass(PyType pyType, ClassBase impl, Type clrType) + { + IntPtr type = pyType.Handle; // we want to do this after the slot stuff above in case the class itself implements a slot method SlotsHolder slotsHolder = CreateSolotsHolder(type); @@ -271,19 +321,6 @@ internal static PyType CreateType(ClassBase impl, Type clrType) } } - if (base_ != IntPtr.Zero) - { - Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_); - Runtime.XIncref(base_); - } - - const TypeFlags flags = TypeFlags.Default - | TypeFlags.HasClrInstance - | TypeFlags.HeapType - | TypeFlags.BaseType - | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); - OperatorMethod.FixupSlots(type, clrType); // Leverage followup initialization from the Python runtime. Note // that the type of the new type must PyType_Type at the time we @@ -311,19 +348,22 @@ internal static PyType CreateType(ClassBase impl, Type clrType) impl.pyHandle = type; //DebugUtil.DumpType(type); - var pyType = new PyType(new BorrowedReference(type)); - Runtime.XDecref(type); - return pyType; } - static int InheritOrAllocateStandardFields(IntPtr type, IntPtr @base) + static int InheritOrAllocateStandardFields(IntPtr type) { - int baseSize = Marshal.ReadInt32(@base, TypeOffset.tp_basicsize); + var @base = new BorrowedReference(Marshal.ReadIntPtr(type, TypeOffset.tp_base)); + return InheritOrAllocateStandardFields(type, @base); + } + static int InheritOrAllocateStandardFields(IntPtr type, BorrowedReference @base) + { + IntPtr baseAddress = @base.DangerousGetAddress(); + int baseSize = Marshal.ReadInt32(baseAddress, TypeOffset.tp_basicsize); int newFieldOffset = baseSize; void InheritOrAllocate(int typeField) { - int value = Marshal.ReadInt32(@base, typeField); + int value = Marshal.ReadInt32(baseAddress, typeField); if (value == 0) { Marshal.WriteIntPtr(type, typeField, new IntPtr(newFieldOffset)); @@ -341,6 +381,19 @@ void InheritOrAllocate(int typeField) return newFieldOffset; } + static PyTuple GetBaseTypeTuple(Type clrType) + { + var bases = pythonBaseTypeProvider + .GetBaseTypes(clrType, new PyType[0]) + ?.ToArray(); + if (bases is null || bases.Length == 0) + { + throw new InvalidOperationException("At least one base type must be specified"); + } + + return new PyTuple(bases); + } + internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, BorrowedReference dictRef) { // Utility to create a subtype of a managed type with the ability for the @@ -395,7 +448,7 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, Borrow // create the new ManagedType and python type ClassBase subClass = ClassManager.GetClass(subType); - IntPtr py_type = GetType(subClass, subType).Handle; + IntPtr py_type = GetOrInitializeClass(subClass, subType).Handle; // by default the class dict will have all the C# methods in it, but as this is a // derived class we want the python overrides in there instead if they exist.