8000 enabled an ability to override Python to .NET argument coversion usin… · losttech/pythonnet@8d069cc · GitHub
[go: up one dir, main page]

Skip to content

Commit 8d069cc

Browse files
committed
enabled an ability to override Python to .NET argument coversion using PyArgConverter attribute
1 parent 08e3356 commit 8d069cc

File tree

3 files changed

+124
-4
lines changed

3 files changed

+124
-4
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using NUnit.Framework;
3+
using Python.Runtime;
4+
5+
namespace Python.EmbeddingTest
6+
{
7+
class TestCustomArgMarshal
8+
{
9+
[OneTimeSetUp]
10+
public void SetUp()
11+
{
12+
PythonEngine.Initialize();
13+
}
14+
15+
[OneTimeTearDown]
16+
public void Dispose()
17+
{
18+
PythonEngine.Shutdown();
19+
}
20+
21+
[Test]
22+
public void CustomArgMarshaller()
23+
{
24+
var obj = new CustomArgMarshaling();
25+
using (Py.GIL()) {
26+
dynamic callWithInt = PythonEngine.Eval("lambda o: o.CallWithInt('42')");
27+
callWithInt(obj.ToPython());
28+
}
29+
Assert.AreEqual(expected: 42, actual: obj.LastArgument);
30+
}
31+
}
32+
33+
[PyArgConverter(typeof(CustomArgConverter))]
34+
class CustomArgMarshaling {
35+
public object LastArgument { get; private set; }
36+
public void CallWithInt(int value) => this.LastArgument = value;
37+
}
38+
39+
class CustomArgConverter : DefaultPyArgumentConverter {
40+
public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution,
41+
out object arg, out bool isOut) {
42+
if (parameterType != typeof(int))
43+
return base.TryConvertArgument(pyarg, parameterType, needsResolution, out arg, out isOut);
44+
bool isString = base.TryConvertArgument(pyarg, typeof(string), needsResolution,
45+
out arg, out isOut);
46+
if (!isString) return false;
47+
int number;
48+
if (!int.TryParse((string)arg, out number)) return false;
49+
arg = number;
50+
return true;
51+
}
52+
}
53+
}

src/runtime/methodbinder.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections;
3+
using System.Diagnostics;
34
using System.Reflection;
45
using System.Text;
56

@@ -17,16 +18,16 @@ internal class MethodBinder
1718
public MethodBase[] methods;
1819
public bool init = false;
1920
public bool allow_threads = true;
20-
readonly IPyArgumentConverter pyArgumentConverter = DefaultPyArgumentConverter.Instance;
21+
IPyArgumentConverter pyArgumentConverter;
2122

2223
internal MethodBinder()
2324
{
2425
list = new ArrayList();
2526
}
2627

27-
internal MethodBinder(MethodInfo mi)
28+
internal MethodBinder(MethodInfo mi): this()
2829
{
29-
list = new ArrayList { mi };
30+
this.AddMethod(mi);
3031
}
3132

3233
public int Count
@@ -36,6 +37,7 @@ public int Count
3637

3738
internal void AddMethod(MethodBase m)
3839
{
40+
Debug.Assert(!init);
3941
list.Add(m);
4042
}
4143

@@ -163,11 +165,34 @@ internal MethodBase[] GetMethods()
163165
// I'm sure this could be made more efficient.
164166
list.Sort(new MethodSorter());
165167
methods = (MethodBase[])list.ToArray(typeof(MethodBase));
168+
pyArgumentConverter = this.GetArgumentConverter();
166169
init = true;
167170
}
168171
return methods;
169172
}
170173

174+
IPyArgumentConverter GetArgumentConverter() {
175+
IPyArgumentConverter converter = null;
176+
Type converterType = null;
177+
foreach (MethodBase method in this.methods)
178+
{
179+
var attribute = method.DeclaringType?.GetCustomAttribute<PyArgConverterAttribute>()
180+
?? method.DeclaringType?.Assembly.GetCustomAttribute<PyArgConverterAttribute>();
181+
if (converterType == null)
182+
{
183+
if (attribute == null) continue;
184+
185+
converterType = attribute.ConverterType;
186+
converter = attribute.Converter;
187+
} else if (converterType != attribute?.ConverterType)
188+
{
189+
throw new NotSupportedException("All methods must have the same IPyArgumentConverter");
190+
}
191+
}
192+
193+
return converter ?? DefaultPyArgumentConverter.Instance;
194+
}
195+
171196
/// <summary>
172197
/// Precedence algorithm largely lifted from Jython - the concerns are
173198
/// generally the same so we'll start with this and tweak as necessary.

src/runtime/pyargconverter.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ bool TryConvertArgument(IntPtr pyarg, Type parameterType,
2121
bool needsResolution, out object arg, out bool isOut);
2222
}
2323

24+
/// <summary>
25+
/// The implementation of <see cref="IPyArgumentConverter"/> used by default
26+
/// </summary>
2427
public class DefaultPyArgumentConverter: IPyArgumentConverter {
25-
public static DefaultPyArgumentConverter Instance { get; }= new DefaultPyArgumentConverter();
28+
/// <summary>
29+
/// Gets the singleton instance.
30+
/// </summary>
31+
public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter();
2632

2733
/// <summary>
2834
/// Attempts to convert an argument passed by Python to the specified parameter type.
@@ -44,4 +50,40 @@ public virtual bool TryConvertArgument(
4450
out arg, out isOut);
4551
}
4652
}
53+
54+
/// <summary>
55+
/// Specifies an argument converter to be used, when methods in this class/assembly are called from Python.
56+
/// </summary>
57+
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct)]
58+
public class PyArgConverterAttribute : Attribute
59+
{
60+
static readonly Type[] EmptyArgTypeList = new Type[0];
61+
static readonly object[] EmptyArgList = new object[0];
62+
63+
/// <summary>
64+
/// Gets the instance of the converter, that will be used when calling methods
65+
/// of this class/assembly from Python
66+
/// </summary>
67+
public IPyArgumentConverter Converter { get; }
68+
/// <summary>
69+
/// Gets the type of the converter, that will be used when calling methods
70+
/// of this class/assembly from Python
71+
/// </summary>
72+
public Type ConverterType { get; }
73+
74+
/// <summary>
75+
/// Specifies an argument converter to be used, when methods
76+
/// in this class/assembly are called from Python.
77+
/// </summary>
78+
/// <param name="converterType">Type of the converter to use.
79+
/// Must implement <see cref="IPyArgumentConverter"/>.</param>
80+
public PyArgConverterAttribute(Type converterType)
81+
{
82+
if (converterType == null) throw new ArgumentNullException(nameof(converterType));
83+
var ctor = converterType.GetConstructor(EmptyArgTypeList);
84+
if (ctor == null) throw new ArgumentException("Specified converter must have public parameterless constructor");
85+
this.Converter = (IPyArgumentConverter)ctor.Invoke(EmptyArgList);
86+
this.ConverterType = converterType;
87+
}
88+
}
4789
}

0 commit comments

Comments
 (0)
0