Skip to content

Commit 59b2f29

Browse files
authored
Merge branch 'master' into losttech-perf-interop
2 parents 0564832 + 72fae73 commit 59b2f29

33 files changed

+635
-160
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
- Luke Stratman ([@lstratman](https://github.com/lstratman))
4646
- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy))
4747
- Matthias Dittrich ([@matthid](https://github.com/matthid))
48+
- Meinrad Recheis ([@henon](https://github.com/henon))
4849
- Mohamed Koubaa ([@koubaa](https://github.com/koubaa))
4950
- Patrick Stewart ([@patstew](https://github.com/patstew))
5051
- Raphael Nestler ([@rnestler](https://github.com/rnestler))

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1313
- Added function that sets Py_NoSiteFlag to 1.
1414
- Added support for Jetson Nano.
1515
- Added support for __len__ for .NET classes that implement ICollection
16-
- Added `object.GetRawPythonProxy() -> PyObject` extension method, that bypasses any conversions
16+
- Added PythonException.Format method to format exceptions the same as traceback.format_exception
17+
- Added Runtime.None to be able to pass None as parameter into Python from .NET
18+
- Added PyObject.IsNone() to check if a Python object is None in .NET.
1719

1820
### Changed
1921

@@ -25,6 +27,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2527
- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer<TDelegate>(IntPtr)
2628
- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881])
2729
- Added support for kwarg parameters when calling .NET methods from Python
30+
- Changed method for finding MSBuild using vswhere
31+
- Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created.
2832

2933
### Fixed
3034

setup.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,13 @@ def _build_monoclr(self):
398398
return
399399
raise
400400
mono_cflags = _check_output("pkg-config --cflags mono-2", shell=True)
401-
glib_libs = _check_output("pkg-config --libs glib-2.0", shell=True)
402-
glib_cflags = _check_output("pkg-config --cflags glib-2.0", shell=True)
403-
cflags = mono_cflags.strip() + " " + glib_cflags.strip()
404-
libs = mono_libs.strip() + " " + glib_libs.strip()
405-
401+
cflags = mono_cflags.strip()
402+
libs = mono_libs.strip()
403+
406404
# build the clr python module
407405
clr_ext = Extension(
408406
"clr",
407+
language="c++",
409408
sources=["src/monoclr/pynetinit.c", "src/monoclr/clrmod.c"],
410409
extra_compile_args=cflags.split(" "),
411410
extra_link_args=libs.split(" "),
@@ -457,26 +456,20 @@ def _find_msbuild_tool(self, tool="msbuild.exe", use_windows_sdk=False):
457456
# trying to search path with help of vswhere when MSBuild 15.0 and higher installed.
458457
if tool == "msbuild.exe" and use_windows_sdk == False:
459458
try:
460-
basePathes = subprocess.check_output(
459+
basePaths = subprocess.check_output(
461460
[
462461
"tools\\vswhere\\vswhere.exe",
463462
"-latest",
464463
"-version",
465-
"[15.0, 16.0)",
464+
"[15.0,)",
466465
"-requires",
467466
"Microsoft.Component.MSBuild",
468-
"-property",
469-
"InstallationPath",
467+
"-find",
468+
"MSBuild\**\Bin\MSBuild.exe",
470469
]
471470
).splitlines()
472-
if len(basePathes):
473-
return os.path.join(
474-
basePathes[0].decode(sys.stdout.encoding or "utf-8"),
475-
"MSBuild",
476-
"15.0",
477-
"Bin",
478-
"MSBuild.exe",
479-
)
471+
if len(basePaths):
472+
return basePaths[0].decode(sys.stdout.encoding or "utf-8")
480473
except:
481474
pass # keep trying to search by old method.
482475

@@ -528,26 +521,20 @@ def _find_msbuild_tool(self, tool="msbuild.exe", use_windows_sdk=False):
528521
def _find_msbuild_tool_15(self):
529522
"""Return full path to one of the Microsoft build tools"""
530523
try:
531-
basePathes = subprocess.check_output(
524+
basePaths = subprocess.check_output(
532525
[
533526
"tools\\vswhere\\vswhere.exe",
534527
"-latest",
535528
"-version",
536-
"[15.0, 16.0)",
529+
"[15.0,)",
537530
"-requires",
538531
"Microsoft.Component.MSBuild",
539-
"-property",
540-
"InstallationPath",
532+
"-find",
533+
"MSBuild\**\Bin\MSBuild.exe",
541534
]
542535
).splitlines()
543-
if len(basePathes):
544-
return os.path.join(
545-
basePathes[0].decode(sys.stdout.encoding or "utf-8"),
546-
"MSBuild",
547-
"15.0",
548-
"Bin",
549-
"MSBuild.exe",
550-
)
536+
if len(basePaths):
537+
return basePaths[0].decode(sys.stdout.encoding or "utf-8")
551538
else:
552539
raise RuntimeError("MSBuild >=15.0 could not be found.")
553540
except subprocess.CalledProcessError as e:

src/embed_tests/CodecGroups.cs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
namespace Python.EmbeddingTest
2+
{
3+
using System;
4+
using System.Linq;
5+
using NUnit.Framework;
6+
using Python.Runtime;
7+
using Python.Runtime.Codecs;
8+
9+
public class CodecGroups
10+
{
11+
[Test]
12+
public void GetEncodersByType()
13+
{
14+
var encoder1 = new ObjectToEncoderInstanceEncoder<Uri>();
15+
var encoder2 = new ObjectToEncoderInstanceEncoder<Uri>();
16+
var group = new EncoderGroup {
17+
new ObjectToEncoderInstanceEncoder<Tuple<int>>(),
18+
encoder1,
19+
encoder2,
20+
};
21+
22+
var got = group.GetEncoders(typeof(Uri)).ToArray();
23+
CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got);
24+
}
25+
26+
[Test]
27+
public void CanEncode()
28+
{
29+
var group = new EncoderGroup {
30+
new ObjectToEncoderInstanceEncoder<Tuple<int>>(),
31+
new ObjectToEncoderInstanceEncoder<Uri>(),
32+
};
33+
34+
Assert.IsTrue(group.CanEncode(typeof(Tuple<int>)));
35+
Assert.IsTrue(group.CanEncode(typeof(Uri)));
36+
Assert.IsFalse(group.CanEncode(typeof(string)));
37+
}
38+
39+
[Test]
40+
public void Encodes()
41+
{
42+
var encoder0 = new ObjectToEncoderInstanceEncoder<Tuple<int>>();
43+
var encoder1 = new ObjectToEncoderInstanceEncoder<Uri>();
44+
var encoder2 = new ObjectToEncoderInstanceEncoder<Uri>();
45+
var group = new EncoderGroup {
46+
encoder0,
47+
encoder1,
48+
encoder2,
49+
};
50+
51+
var uri = group.TryEncode(new Uri("data:"));
52+
var clrObject = (CLRObject)ManagedType.GetManagedObject(uri.Handle);
53+
Assert.AreSame(encoder1, clrObject.inst);
54+
Assert.AreNotSame(encoder2, clrObject.inst);
55+
56+
var tuple = group.TryEncode(Tuple.Create(1));
57+
clrObject = (CLRObject)ManagedType.GetManagedObject(tuple.Handle);
58+
Assert.AreSame(encoder0, clrObject.inst);
59+
}
60+
61+
[Test]
62+
public void GetDecodersByTypes()
63+
{
64+
var pyint = new PyInt(10).GetPythonType();
65+
var pyfloat = new PyFloat(10).GetPythonType();
66+
var pystr = new PyString("world").GetPythonType();
67+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
68+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
69+
var group = new DecoderGroup {
70+
decoder1,
71+
decoder2,
72+
};
73+
74+
var decoder = group.GetDecoder(pyfloat, typeof(string));
75+
Assert.AreSame(decoder2, decoder);
76+
decoder = group.GetDecoder(pystr, typeof(string));
77+
Assert.IsNull(decoder);
78+
decoder = group.GetDecoder(pyint, typeof(long));
79+
Assert.AreSame(decoder1, decoder);
80+
}
81+
[Test]
82+
public void CanDecode()
83+
{
84+
var pyint = new PyInt(10).GetPythonType();
85+
var pyfloat = new PyFloat(10).GetPythonType();
86+
var pystr = new PyString("world").GetPythonType();
87+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
88+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
89+
var group = new DecoderGroup {
90+
decoder1,
91+
decoder2,
92+
};
93+
94+
Assert.IsTrue(group.CanDecode(pyint, typeof(long)));
95+
Assert.IsFalse(group.CanDecode(pyint, typeof(int)));
96+
Assert.IsTrue(group.CanDecode(pyfloat, typeof(string)));
97+
Assert.IsFalse(group.CanDecode(pystr, typeof(string)));
98+
}
99+
100+
[Test]
101+
public void Decodes()
102+
{
103+
var pyint = new PyInt(10).GetPythonType();
104+
var pyfloat = new PyFloat(10).GetPythonType();
105+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
106+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
107+
var group = new DecoderGroup {
108+
decoder1,
109+
decoder2,
110+
};
111+
112+
Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult));
113+
Assert.AreEqual(42, longResult);
114+
Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult));
115+
Assert.AreSame("atad:", strResult);
116+
117+
Assert.IsFalse(group.TryDecode(new PyInt(10), out int _));
118+
}
119+
120+
[SetUp]
121+
public void SetUp()
122+
{
123+
PythonEngine.Initialize();
124+
}
125+
126+
[TearDown]
127+
public void Dispose()
128+
{
129+
PythonEngine.Shutdown();
130+
}
131+
}
132+
}

src/embed_tests/Codecs.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,41 @@ static void TupleRoundtripGeneric<T, TTuple>() {
8383
}
8484
}
8585
}
86+
87+
/// <summary>
88+
/// "Decodes" only objects of exact type <typeparamref name="T"/>.
89+
/// Result is just the raw proxy to the encoder instance itself.
90+
/// </summary>
91+
class ObjectToEncoderInstanceEncoder<T> : IPyObjectEncoder
92+
{
93+
public bool CanEncode(Type type) => type == typeof(T);
94+
public PyObject TryEncode(object value) => PyObject.FromManagedObject(this);
95+
}
96+
97+
/// <summary>
98+
/// Decodes object of specified Python type to the predefined value <see cref="DecodeResult"/>
99+
/// </summary>
100+
/// <typeparam name="TTarget">Type of the <see cref="DecodeResult"/></typeparam>
101+
class DecoderReturningPredefinedValue<TTarget> : IPyObjectDecoder
102+
{
103+
public PyObject TheOnlySupportedSourceType { get; }
104+
public TTarget DecodeResult { get; }
105+
106+
public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult)
107+
{
108+
this.TheOnlySupportedSourceType = objectType;
109+
this.DecodeResult = decodeResult;
110+
}
111+
112+
public bool CanDecode(PyObject objectType, Type targetType)
113+
=> objectType.Handle == TheOnlySupportedSourceType.Handle
114+
&& targetType == typeof(TTarget);
115+
public bool TryDecode<T>(PyObject pyObj, out T value)
116+
{
117+
if (typeof(T) != typeof(TTarget))
118+
throw new ArgumentException(nameof(T));
119+
value = (T)(object)DecodeResult;
120+
return true;
121+
}
122+
}
86123
}

src/embed_tests/Python.EmbeddingTest.15.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777

7878

7979
<ItemGroup>
80+
<PackageReference Include="NonCopyableAnalyzer" Version="0.6.0" />
8081
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
8182
<PackageReference Include="NUnit" Version="3.12.0" />
8283
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" />

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<None Include="packages.config" />
8484
</ItemGroup>
8585
<ItemGroup>
86+
<Compile Include="CodecGroups.cs" />
8687
<Compile Include="Codecs.cs" />
8788
<Compile Include="dynamic.cs" />
8889
<Compile Include="pyimport.cs" />

src/embed_tests/References.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,20 @@ public void MoveToPyObject_SetsNull()
3636
reference.Dispose();
3737
}
3838
}
39+
40+
[Test]
41+
public void CanBorrowFromNewReference()
42+
{
43+
var dict = new PyDict();
44+
NewReference reference = Runtime.PyDict_Items(dict.Handle);
45+
try
46+
{
47+
PythonException.ThrowIfIsNotZero(Runtime.PyList_Reverse(reference));
48+
}
49+
finally
50+
{
51+
reference.Dispose();
52+
}
53+
}
3954
}
4055
}

src/embed_tests/TestConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void TestConvertDoubleToManaged(
5151
public void RawListProxy()
5252
{
5353
var list = new List<string> {"hello", "world"};
54-
var listProxy = list.GetRawPythonProxy();
54+
var listProxy = PyObject.FromManagedObject(list);
5555
var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy.Handle);
5656
Assert.AreSame(list, clrObject.inst);
5757
}
@@ -60,7 +60,7 @@ public void RawListProxy()
6060
public void RawPyObjectProxy()
6161
{
6262
var pyObject = "hello world!".ToPython();
63-
var pyObjectProxy = pyObject.GetRawPythonProxy();
63+
var pyObjectProxy = PyObject.FromManagedObject(pyObject);
6464
var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy.Handle);
6565
Assert.AreSame(pyObject, clrObject.inst);
6666

src/embed_tests/TestFinalizer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void CollectBasicObject()
7777
}
7878
try
7979
{
80-
Finalizer.Instance.Collect(forceDispose: false);
80+
Finalizer.Instance.Collect();
8181
}
8282
finally
8383
{

src/embed_tests/TestGILState.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,17 @@ public void CanDisposeMultipleTimes()
1717
gilState.Dispose();
1818
}
1919
}
20+
21+
[OneTimeSetUp]
22+
public void SetUp()
23+
{
24+
PythonEngine.Initialize();
25+
}
26+
27+
[OneTimeTearDown]
28+
public void Dispose()
29+
{
30+
PythonEngine.Shutdown();
31+
}
2032
}
2133
}

0 commit comments

Comments
 (0)