Skip to content

Commit 73661fd

Browse files
committed
added new class PyType to wrap Python type objects and enable new type construction from PyType_Spec (TypeSpec class)
1 parent 16b4df7 commit 73661fd

File tree

6 files changed

+266
-0
lines changed

6 files changed

+266
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1313
- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]).
1414
- Add GetPythonThreadID and Interrupt methods in PythonEngine
1515
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
16+
- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`
1617

1718
### Changed
1819
- Drop support for Python 2, 3.4, and 3.5

src/embed_tests/TestPyType.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Text;
2+
3+
using NUnit.Framework;
4+
5+
using Python.Runtime;
6+
using Python.Runtime.Native;
7+
8+
namespace Python.EmbeddingTest
9+
{
10+
public class TestPyType
11+
{
12+
[OneTimeSetUp]
13+
public void SetUp()
14+
{
15+
PythonEngine.Initialize();
16+
}
17+
18+
[OneTimeTearDown]
19+
public void Dispose()
20+
{
21+
PythonEngine.Shutdown();
22+
}
23+
24+
[Test]
25+
public void CanCreateHeapType()
26+
{
27+
const string name = "nÁmæ";
28+
const string docStr = "dÁcæ";
29+
30+
using var doc = new StrPtr(docStr, Encoding.UTF8);
31+
var spec = new TypeSpec(
32+
name: name,
33+
basicSize: ObjectOffset.Size(Runtime.Runtime.PyTypeType),
34+
slots: new TypeSpec.Slot[] {
35+
new (TypeSlotID.tp_doc, doc.RawPointer),
36+
},
37+
TypeFlags.Default | TypeFlags.HeapType
38+
);
39+
40+
using var type = new PyType(spec);
41+
Assert.AreEqual(name, type.GetAttr("__name__").As<string>());
42+
Assert.AreEqual(docStr, type.GetAttr("__doc__").As<string>());
43+
}
44+
}
45+
}

src/runtime/TypeSpec.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Python.Runtime
8+
{
9+
public class TypeSpec
10+
{
11+
public TypeSpec(string name, int basicSize, IEnumerable<Slot> slots, TypeFlags flags, int itemSize = 0)
12+
{
13+
this.Name = name ?? throw new ArgumentNullException(nameof(name));
14+
this.BasicSize = basicSize;
15+
this.Slots = slots.ToArray();
16+
this.Flags = flags;
17+
this.ItemSize = itemSize;
18+
}
19+
public string Name { get; }
20+
public int BasicSize { get; }
21+
public int ItemSize { get; }
22+
public TypeFlags Flags { get; }
23+
public IReadOnlyList<Slot> Slots { get; }
24+
25+
[StructLayout(LayoutKind.Sequential)]
26+
public struct Slot
27+
{
28+
public Slot(TypeSlotID id, IntPtr value)
29+
{
30+
ID = id;
31+
Value = value;
32+
}
33+
34+
public TypeSlotID ID { get; }
35+
public IntPtr Value { get; }
36+
}
37+
}
38+
39+
public enum TypeSlotID : int
40+
{
41+
mp_ass_subscript = 3,
42+
mp_length = 4,
43+
mp_subscript = 5,
44+
nb_absolute = 6,
45+
nb_add = 7,
46+
nb_and = 8,
47+
nb_bool = 9,
48+
nb_divmod = 10,
49+
nb_float = 11,
50+
nb_floor_divide = 12,
51+
nb_index = 13,
52+
nb_inplace_add = 14,
53+
nb_inplace_and = 15,
54+
nb_inplace_floor_divide = 16,
55+
nb_inplace_lshift = 17,
56+
nb_inplace_multiply = 18,
57+
nb_inplace_or = 19,
58+
nb_inplace_power = 20,
59+
nb_inplace_remainder = 21,
60+
nb_inplace_rshift = 22,
61+
nb_inplace_subtract = 23,
62+
nb_inplace_true_divide = 24,
63+
nb_inplace_xor = 25,
64+
nb_int = 26,
65+
nb_invert = 27,
66+
nb_lshift = 28,
67+
nb_multiply = 29,
68+
nb_negative = 30,
69+
nb_or = 31,
70+
nb_positive = 32,
71+
nb_power = 33,
72+
nb_remainder = 34,
73+
nb_rshift = 35,
74+
nb_subtract = 36,
75+
nb_true_divide = 37,
76+
nb_xor = 38,
77+
sq_ass_item = 39,
78+
sq_concat = 40,
79+
sq_contains = 41,
80+
sq_inplace_concat = 42,
81+
sq_inplace_repeat = 43,
82+
sq_item = 44,
83+
sq_length = 45,
84+
sq_repeat = 46,
85+
tp_alloc = 47,
86+
tp_base = 48,
87+
tp_bases = 49,
88+
tp_call = 50,
89+
tp_clear = 51,
90+
tp_dealloc = 52,
91+
tp_del = 53,
92+
tp_descr_get = 54,
93+
tp_descr_set = 55,
94+
tp_doc = 56,
95+
tp_getattr = 57,
96+
tp_getattro = 58,
97+
tp_hash = 59,
98+
tp_init = 60,
99+
tp_is_gc = 61,
100+
tp_iter = 62,
101+
tp_iternext = 63,
102+
tp_methods = 64,
103+
tp_new = 65,
104+
tp_repr = 66,
105+
tp_richcompare = 67,
106+
tp_setattr = 68,
107+
tp_setattro = 69,
108+
tp_str = 70,
109+
tp_traverse = 71,
110+
tp_members = 72,
111+
tp_getset = 73,
112+
tp_free = 74,
113+
nb_matrix_multiply = 75,
114+
nb_inplace_matrix_multiply = 76,
115+
am_await = 77,
116+
am_aiter = 78,
117+
am_anext = 79,
118+
/// <remarks>New in 3.5</remarks>
119+
tp_finalize = 80,
120+
}
121+
}

src/runtime/native/NativeTypeSpec.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#nullable enable
2+
using System;
3+
using System.Runtime.InteropServices;
4+
using System.Text;
5+
6+
namespace Python.Runtime.Native
7+
{
8+
[StructLayout(LayoutKind.Sequential)]
9+
struct NativeTypeSpec : IDisposable
10+
{
11+
public readonly StrPtr Name;
12+
public readonly int BasicSize;
13+
public readonly int ItemSize;
14+
public readonly TypeFlags Flags;
15+
public IntPtr Slots;
16+
17+
public NativeTypeSpec(TypeSpec spec)
18+
{
19+
if (spec is null) throw new ArgumentNullException(nameof(spec));
20+
21+
this.Name = new StrPtr(spec.Name, Encoding.UTF8);
22+
this.BasicSize = spec.BasicSize;
23+
this.ItemSize = spec.ItemSize;
24+
this.Flags = spec.Flags;
25+
26+
unsafe
27+
{
28+
int slotsBytes = checked((spec.Slots.Count + 1) * Marshal.SizeOf<TypeSpec.Slot>());
29+
var slots = (TypeSpec.Slot*)Marshal.AllocHGlobal(slotsBytes);
30+
for (int slotIndex = 0; slotIndex < spec.Slots.Count; slotIndex++)
31+
slots[slotIndex] = spec.Slots[slotIndex];
32+
slots[spec.Slots.Count] = default;
33+
this.Slots = (IntPtr)slots;
34+
}
35+
}
36+
37+
public void Dispose()
38+
{
39+
// we have to leak the name
40+
// this.Name.Dispose();
41+
Marshal.FreeHGlobal(this.Slots);
42+
this.Slots = IntPtr.Zero;
43+
}
44+
}
45+
}

src/runtime/pytype.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#nullable enable
2+
using System;
3+
using System.Runtime.InteropServices;
4+
5+
using Python.Runtime.Native;
6+
7+
namespace Python.Runtime
8+
{
9+
public class PyType : PyObject
10+
{
11+
/// <summary>Creates heap type object from the <paramref name="spec"/>.</summary>
12+
public PyType(TypeSpec spec, PyTuple? bases = null) : base(FromSpec(spec, bases)) { }
13+
/// <summary>Wraps an existing type object.</summary>
14+
public PyType(PyObject o) : base(FromObject(o)) { }
15+
16+
/// <summary>Checks if specified object is a Python type.</summary>
17+
public static bool IsType(PyObject value)
18+
{
19+
if (value is null) throw new ArgumentNullException(nameof(value));
20+
21+
return Runtime.PyType_Check(value.obj);
22+
}
23+
24+
private static BorrowedReference FromObject(PyObject o)
25+
{
26+
if (o is null) throw new ArgumentNullException(nameof(o));
27+
if (!IsType(o)) throw new ArgumentException("object is not a type");
28+
29+
return o.Reference;
30+
}
31+
32+
private static IntPtr FromSpec(TypeSpec spec, PyTuple? bases = null)
33+
{
34+
if (spec is null) throw new ArgumentNullException(nameof(spec));
35+
36+
if ((spec.Flags & TypeFlags.HeapType) == 0)
37+
throw new NotSupportedException("Only heap types are supported");
38+
39+
var nativeSpec = new NativeTypeSpec(spec);
40+
var basesRef = bases is null ? default : bases.Reference;
41+
var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef);
42+
43+
PythonException.ThrowIfIsNull(result);
44+
45+
nativeSpec.Dispose();
46+
47+
return result.DangerousMoveToPointer();
48+
}
49+
}
50+
}

src/runtime/runtime.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,8 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n)
20062006

20072007

20082008
private static IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n) => Delegates.PyType_GenericAlloc(type, n);
2009+
2010+
internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases);
20092011

20102012
/// <summary>
20112013
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0 on success, or return -1 and sets an exception on error.
@@ -2510,6 +2512,7 @@ static Delegates()
25102512
PyException_SetCause = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void>)GetFunctionByName(nameof(PyException_SetCause), GetUnmanagedDll(_PythonDll));
25112513
PyThreadState_SetAsyncExcLLP64 = (delegate* unmanaged[Cdecl]<uint, IntPtr, int>)GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll));
25122514
PyThreadState_SetAsyncExcLP64 = (delegate* unmanaged[Cdecl]<ulong, IntPtr, int>)GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll));
2515+
PyType_FromSpecWithBases = (delegate* unmanaged[Cdecl]<in NativeTypeSpec, BorrowedReference, NewReference>)GetFunctionByName(nameof(PyType_FromSpecWithBases), GetUnmanagedDll(PythonDLL));
25132516
}
25142517

25152518
static global::System.IntPtr GetUnmanagedDll(string libraryName)
@@ -2775,6 +2778,7 @@ static Delegates()
27752778
internal static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void> PyException_SetCause { get; }
27762779
internal static delegate* unmanaged[Cdecl]<uint, IntPtr, int> PyThreadState_SetAsyncExcLLP64 { get; }
27772780
internal static delegate* unmanaged[Cdecl]<ulong, IntPtr, int> PyThreadState_SetAsyncExcLP64 { get; }
2781+
internal static delegate* unmanaged[Cdecl]<in NativeTypeSpec, BorrowedReference, NewReference> PyType_FromSpecWithBases { get; }
27782782
}
27792783
}
27802784

0 commit comments

Comments
 (0)