Skip to content

Commit 3c9f7b3

Browse files
thesn10filmor
authored andcommitted
Add Python buffer api support
1 parent 49a230b commit 3c9f7b3

File tree

9 files changed

+475
-1
lines changed

9 files changed

+475
-1
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
- ([@OneBlue](https://github.com/OneBlue))
7070
- ([@rico-chet](https://github.com/rico-chet))
7171
- ([@rmadsen-ks](https://github.com/rmadsen-ks))
72+
- ([@SnGmng](https://github.com/SnGmng))
7273
- ([@stonebig](https://github.com/stonebig))
7374
- ([@testrunner123](https://github.com/testrunner123))
7475

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1818
- Added Runtime.None to be able to pass None as parameter into Python from .NET
1919
- Added PyObject.IsNone() to check if a Python object is None in .NET.
2020
- Support for Python 3.8
21+
- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980])
2122

2223
### Changed
2324

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<Compile Include="TestFinalizer.cs" />
9898
<Compile Include="TestInstanceWrapping.cs" />
9999
<Compile Include="TestPyAnsiString.cs" />
100+
<Compile Include="TestPyBuffer.cs" />
100101
<Compile Include="TestPyFloat.cs" />
101102
<Compile Include="TestPyInt.cs" />
102103
<Compile Include="TestPyList.cs" />

src/embed_tests/TestPyBuffer.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.Text;
2+
using NUnit.Framework;
3+
using Python.Runtime;
4+
5+
namespace Python.EmbeddingTest {
6+
class TestPyBuffer
7+
{
8+
[OneTimeSetUp]
9+
public void SetUp()
10+
{
11+
PythonEngine.Initialize();
12+
}
13+
14+
[OneTimeTearDown]
15+
public void Dispose()
16+
{
17+
PythonEngine.Shutdown();
18+
}
19+
20+
[Test]
21+
public void TestBufferWrite()
22+
{
23+
if (Runtime.Runtime.pyversionnumber < 35) return;
24+
25+
string bufferTestString = "hello world! !$%&/()=?";
26+
27+
using (Py.GIL())
28+
{
29+
using (var scope = Py.CreateScope())
30+
{
31+
scope.Exec($"arr = bytearray({bufferTestString.Length})");
32+
PyObject pythonArray = scope.Get("arr");
33+
byte[] managedArray = new UTF8Encoding().GetBytes(bufferTestString);
34+
35+
using (PyBuffer buf = pythonArray.GetBuffer())
36+
{
37+
buf.Write(managedArray, 0, managedArray.Length);
38+
}
39+
40+
string result = scope.Eval("arr.decode('utf-8')").ToString();
41+
Assert.IsTrue(result == bufferTestString);
42+
}
43+
}
44+
}
45+
46+
[Test]
47+
public void TestBufferRead()
48+
{
49+
if (Runtime.Runtime.pyversionnumber < 35) return;
50+
51+
string bufferTestString = "hello world! !$%&/()=?";
52+
53+
using (Py.GIL())
54+
{
55+
using (var scope = Py.CreateScope())
56+
{
57+
scope.Exec($"arr = b'{bufferTestString}'");
58+
PyObject pythonArray = scope.Get("arr");
59+
byte[] managedArray = new byte[bufferTestString.Length];
60+
61+
using (PyBuffer buf = pythonArray.GetBuffer())
62+
{
63+
buf.Read(managedArray, 0, managedArray.Length);
64+
}
65+
66+
string result = new UTF8Encoding().GetString(managedArray);
67+
Assert.IsTrue(result == bufferTestString);
68+
}
69+
}
70+
}
71+
}
72+
}

src/runtime/Python.Runtime.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<Compile Include="arrayobject.cs" />
9090
<Compile Include="assemblymanager.cs" />
9191
<Compile Include="BorrowedReference.cs" />
92+
<Compile Include="bufferinterface.cs" />
9293
<Compile Include="classderived.cs" />
9394
<Compile Include="classbase.cs" />
9495
<Compile Include="classmanager.cs" />
@@ -130,6 +131,7 @@
130131
<Compile Include="overload.cs" />
131132
<Compile Include="propertyobject.cs" />
132133
<Compile Include="pyansistring.cs" />
134+
<Compile Include="pybuffer.cs" />
133135
<Compile Include="pydict.cs" />
134136
<Compile Include="PyExportAttribute.cs" />
135137
<Compile Include="pyfloat.cs" />
@@ -183,4 +185,4 @@
183185
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
184186
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
185187
</Target>
186-
</Project>
188+
</Project>

src/runtime/bufferinterface.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Python.Runtime
5+
{
6+
/* buffer interface */
7+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
8+
internal struct Py_buffer {
9+
public IntPtr buf;
10+
public IntPtr obj; /* owned reference */
11+
[MarshalAs(UnmanagedType.SysInt)]
12+
public IntPtr len;
13+
[MarshalAs(UnmanagedType.SysInt)]
14+
public IntPtr itemsize; /* This is Py_ssize_t so it can be
15+
pointed to by strides in simple case.*/
16+
[MarshalAs(UnmanagedType.Bool)]
17+
public bool _readonly;
18+
public int ndim;
19+
[MarshalAs(UnmanagedType.LPStr)]
20+
public string format;
21+
public IntPtr shape;
22+
public IntPtr strides;
23+
public IntPtr suboffsets;
24+
public IntPtr _internal;
25+
}
26+
27+
public enum BufferOrderStyle
28+
{
29+
C,
30+
Fortran,
31+
EitherOne,
32+
}
33+
34+
/* Flags for getting buffers */
35+
public enum PyBUF
36+
{
37+
/// <summary>
38+
/// Simple buffer without shape strides and suboffsets
39+
/// </summary>
40+
SIMPLE = 0,
41+
/// <summary>
42+
/// Controls the <see cref="PyBuffer.ReadOnly"/> field. If set, the exporter MUST provide a writable buffer or else report failure. Otherwise, the exporter MAY provide either a read-only or writable buffer, but the choice MUST be consistent for all consumers.
43+
/// </summary>
44+
WRITABLE = 0x0001,
45+
/// <summary>
46+
/// Controls the <see cref="PyBuffer.Format"/> field. If set, this field MUST be filled in correctly. Otherwise, this field MUST be NULL.
47+
/// </summary>
48+
FORMATS = 0x0004,
49+
/// <summary>
50+
/// N-Dimensional buffer with shape
51+
/// </summary>
52+
ND = 0x0008,
53+
/// <summary>
54+
/// Buffer with strides and shape
55+
/// </summary>
56+
STRIDES = (0x0010 | ND),
57+
/// <summary>
58+
/// C-Contigous buffer with strides and shape
59+
/// </summary>
60+
C_CONTIGUOUS = (0x0020 | STRIDES),
61+
/// <summary>
62+
/// F-Contigous buffer with strides and shape
63+
/// </summary>
64+
F_CONTIGUOUS = (0x0040 | STRIDES),
65+
/// <summary>
66+
/// C or Fortran contigous buffer with strides and shape
67+
/// </summary>
68+
ANY_CONTIGUOUS = (0x0080 | STRIDES),
69+
/// <summary>
70+
/// Buffer with suboffsets (if needed)
71+
/// </summary>
72+
INDIRECT = (0x0100 | STRIDES),
73+
/// <summary>
74+
/// Writable C-Contigous buffer with shape
75+
/// </summary>
76+
CONTIG = (ND | WRITABLE),
77+
/// <summary>
78+
/// Readonly C-Contigous buffer with shape
79+
/// </summary>
80+
CONTIG_RO = (ND),
81+
/// <summary>
82+
/// Writable buffer with shape and strides
83+
/// </summary>
84+
STRIDED = (STRIDES | WRITABLE),
85+
/// <summary>
86+
/// Readonly buffer with shape and strides
87+
/// </summary>
88+
STRIDED_RO = (STRIDES),
89+
/// <summary>
90+
/// Writable buffer with shape, strides and format
91+
/// </summary>
92+
RECORDS = (STRIDES | WRITABLE | FORMATS),
93+
/// <summary>
94+
/// Readonly buffer with shape, strides and format
95+
/// </summary>
96+
RECORDS_RO = (STRIDES | FORMATS),
97+
/// <summary>
98+
/// Writable indirect buffer with shape, strides, format and suboffsets (if needed)
99+
/// </summary>
100+
FULL = (INDIRECT | WRITABLE | FORMATS),
101+
/// <summary>
102+
/// Readonly indirect buffer with shape, strides, format and suboffsets (if needed)
103+
/// </summary>
104+
FULL_RO = (INDIRECT | FORMATS),
105+
}
106+
}

0 commit comments

Comments
 (0)