Skip to content

Commit c5f9793

Browse files
koubaaMohamed Koubaa
andauthored
Binding options (#23)
Fixes #22 Gives the user an option to opt-into exposing explicit interface implementations. This is done by adding a place to register customizations for python bindings by type or assembly. Usage (to customize binding for all types in an assembly): ``` m = clr.AddReference('TestInheritance') from Python.Runtime import BindingManager, BindingOptions binding_options = BindingOptions() binding_options.AllowExplicitInterfaceImplementation = True BindingManager.SetBindingOptions(m, binding_options) ``` --------- Co-authored-by: Mohamed Koubaa <[email protected]>
1 parent a988c32 commit c5f9793

File tree

9 files changed

+162
-32
lines changed

9 files changed

+162
-32
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ We will try to keep this up-to-date with pythonnet and upstream changes that mig
55
Changes relative to pythonnet:
66

77
* Revert of `#1240 <https://github.com/pythonnet/pythonnet/pull/1240>`_.
8-
* Enum REPR `#2239 <https://github.com/pythonnet/pythonnet/pull/2239>`_ is included in this release of version 3.0.2, but is unreleased in pythonnet
98
* Opt-into explicit interface wrapping, `#19 <https://github.com/ansys/ansys-pythonnet/pull/19>`_. This opts into the behavior that became the default in #1240 if ToPythonAs<T> is explicitly used
9+
* Option to bind explicit interface implementations, `#23 <https://github.com/ansys/ansys-pythonnet/pull/23>`_. This provides a runtime option to expose C# explicit interface implementations to Python.

src/runtime/BindingOptions.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace Python.Runtime
6+
{
7+
public class BindingOptions
8+
{
9+
private bool _SuppressDocs = false;
10+
private bool _SuppressOverloads = false;
11+
private bool _AllowExplicitInterfaceImplementation = false;
12+
13+
//[ModuleProperty]
14+
public bool SuppressDocs
15+
{
16+
get { return _SuppressDocs; }
17+
set { _SuppressDocs = value; }
18+
}
19+
20+
//[ModuleProperty]
21+
public bool SuppressOverloads
22+
{
23+
get { return _SuppressOverloads; }
24+
set { _SuppressOverloads = value; }
25+
}
26+
27+
public bool AllowExplicitInterfaceImplementation
28+
{
29+
get { return _AllowExplicitInterfaceImplementation; }
30+
set { _AllowExplicitInterfaceImplementation = value; }
31+
}
32+
}
33+
34+
public class BindingManager
35+
{
36+
static IDictionary<Type, BindingOptions> _typeOverrides = new Dictionary<Type, BindingOptions>();
37+
static IDictionary<Assembly, BindingOptions> _assemblyOverrides = new Dictionary<Assembly, BindingOptions>();
38+
static BindingOptions _defaultBindingOptions = new BindingOptions();
39+
40+
public static BindingOptions GetBindingOptions(Type type)
41+
{
42+
if (_typeOverrides.ContainsKey(type))
43+
{
44+
return _typeOverrides[type];
45+
}
46+
47+
if (_assemblyOverrides.ContainsKey(type.Assembly))
48+
{
49+
return _assemblyOverrides[type.Assembly];
50+
}
51+
return _defaultBindingOptions;
52+
}
53+
54+
public static BindingOptions DefaultBindingOptions => _defaultBindingOptions;
55+
56+
public static void SetBindingOptions(Type type, BindingOptions options)
57+
{
58+
_typeOverrides[type] = options;
59+
}
60+
61+
public static void SetBindingOptions(Assembly assembly, BindingOptions options)
62+
{
63+
_assemblyOverrides[assembly] = options;
64+
}
65+
66+
public static void Clear()
67+
{
68+
_typeOverrides.Clear();
69+
_assemblyOverrides.Clear();
70+
}
71+
}
72+
}

src/runtime/ClassManager.cs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
210210
// information, including generating the member descriptors
211211
// that we'll be putting in the Python class __dict__.
212212

213-
ClassInfo info = GetClassInfo(type, impl);
213+
var bindingOptions = BindingManager.GetBindingOptions(type);
214+
215+
ClassInfo info = GetClassInfo(type, impl, bindingOptions);
214216

215217
impl.indexer = info.indexer;
216218
impl.richcompare.Clear();
@@ -254,7 +256,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
254256
if (co.NumCtors > 0 && !co.HasCustomNew())
255257
{
256258
// Implement Overloads on the class object
257-
if (!CLRModule._SuppressOverloads)
259+
if (!bindingOptions.SuppressOverloads)
258260
{
259261
// HACK: __init__ points to instance constructors.
260262
// When unbound they fully instantiate object, so we get overloads for free from MethodBinding.
@@ -265,7 +267,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
265267
}
266268

267269
// don't generate the docstring if one was already set from a DocStringAttribute.
268-
if (!CLRModule._SuppressDocs && doc.IsNull())
270+
if (!bindingOptions.SuppressDocs && doc.IsNull())
269271
{
270272
doc = co.GetDocString();
271273
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow());
@@ -288,10 +290,20 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
288290
Runtime.PyType_Modified(pyType.Reference);
289291
}
290292

291-
internal static bool ShouldBindMethod(MethodBase mb)
293+
internal static bool ShouldBindMethod(MethodBase mb, BindingOptions bindingOptions)
292294
{
293295
if (mb is null) throw new ArgumentNullException(nameof(mb));
294-
return (mb.IsPublic || mb.IsFamily || mb.IsFamilyOrAssembly);
296+
297+
bool rejectByVisibility = false;
298+
if (!mb.IsPublic)
299+
{
300+
if (bindingOptions.AllowExplicitInterfaceImplementation)
301+
// detect explicit interface implementation
302+
rejectByVisibility = !mb.IsVirtual;
303+
else
304+
rejectByVisibility = true;
305+
}
306+
return (!rejectByVisibility || mb.IsFamily || mb.IsFamilyOrAssembly);
295307
}
296308

297309
internal static bool ShouldBindField(FieldInfo fi)
@@ -300,7 +312,7 @@ internal static bool ShouldBindField(FieldInfo fi)
300312
return (fi.IsPublic || fi.IsFamily || fi.IsFamilyOrAssembly);
301313
}
302314

303-
internal static bool ShouldBindProperty(PropertyInfo pi)
315+
internal static bool ShouldBindProperty(PropertyInfo pi, BindingOptions bindingOptions)
304316
{
305317
MethodInfo? mm;
306318
try
@@ -323,16 +335,27 @@ internal static bool ShouldBindProperty(PropertyInfo pi)
323335
return false;
324336
}
325337

326-
return ShouldBindMethod(mm);
338+
return ShouldBindMethod(mm, bindingOptions);
327339
}
328340

329-
internal static bool ShouldBindEvent(EventInfo ei)
341+
internal static bool ShouldBindEvent(EventInfo ei, BindingOptions bindingOptions)
330342
{
331-
return ei.GetAddMethod(true) is { } add && ShouldBindMethod(add);
343+
return ei.GetAddMethod(true) is { } add && ShouldBindMethod(add, bindingOptions);
332344
}
333345

334-
private static ClassInfo GetClassInfo(Type type, ClassBase impl)
346+
private static string SanitizeName(string name)
335347
{
348+
// For explicit interface implementation, the name of the attribute will be:
349+
// `Class.Interface.Attribute`
350+
// In that case, only use the last token for the real attribute name to bind.
351+
if (name.Contains("."))
352+
name = name.Substring(name.LastIndexOf(".")+1);
353+
return name;
354+
}
355+
356+
private static ClassInfo GetClassInfo(Type type, ClassBase impl, BindingOptions bindingOptions)
357+
{
358+
var typeName = type.Name;
336359
var ci = new ClassInfo();
337360
var methods = new Dictionary<string, List<MethodBase>>();
338361
MethodInfo meth;
@@ -436,11 +459,12 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
436459
{
437460
case MemberTypes.Method:
438461
meth = (MethodInfo)mi;
439-
if (!ShouldBindMethod(meth))
462+
if (!ShouldBindMethod(meth, bindingOptions))
440463
{
441464
continue;
442465
}
443-
name = meth.Name;
466+
467+
name = SanitizeName(meth.Name);
444468

445469
//TODO mangle?
446470
if (name == "__init__" && !impl.HasCustomNew())
@@ -471,7 +495,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
471495
case MemberTypes.Property:
472496
var pi = (PropertyInfo)mi;
473497

474-
if(!ShouldBindProperty(pi))
498+
if(!ShouldBindProperty(pi, bindingOptions))
475499
{
476500
continue;
477501
}
@@ -490,8 +514,9 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
490514
continue;
491515
}
492516

517+
var propertyName = SanitizeName(pi.Name);
493518
ob = new PropertyObject(pi);
494-
ci.members[pi.Name] = ob.AllocObject();
519+
ci.members[propertyName] = ob.AllocObject();
495520
continue;
496521

497522
case MemberTypes.Field:
@@ -506,7 +531,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
506531

507532
case MemberTypes.Event:
508533
var ei = (EventInfo)mi;
509-
if (!ShouldBindEvent(ei))
534+
if (!ShouldBindEvent(ei, bindingOptions))
510535
{
511536
continue;
512537
}

src/runtime/StateSerialization/MaybeMemberInfo.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ internal MaybeMemberInfo(SerializationInfo serializationInfo, StreamingContext c
6464
var tp = Type.GetType(serializationInfo.GetString(SerializationType));
6565
if (tp != null)
6666
{
67+
var bindingOptions = BindingManager.GetBindingOptions(tp);
6768
var memberName = serializationInfo.GetString(SerializationMemberName);
6869
MemberInfo? mi = Get(tp, memberName, ClassManager.BindingFlags);
69-
if (mi != null && ShouldBindMember(mi))
70+
if (mi != null && ShouldBindMember(mi, bindingOptions))
7071
{
7172
info = mi;
7273
}
@@ -92,19 +93,19 @@ internal MaybeMemberInfo(SerializationInfo serializationInfo, StreamingContext c
9293
// based on it's setter/getter (which is a method
9394
// info) visibility and events based on their
9495
// AddMethod visibility.
95-
static bool ShouldBindMember(MemberInfo mi)
96+
static bool ShouldBindMember(MemberInfo mi, BindingOptions bindingOptions)
9697
{
9798
if (mi is PropertyInfo pi)
9899
{
99-
return ClassManager.ShouldBindProperty(pi);
100+
return ClassManager.ShouldBindProperty(pi, bindingOptions);
100101
}
101102
else if (mi is FieldInfo fi)
102103
{
103104
return ClassManager.ShouldBindField(fi);
104105
}
105106
else if (mi is EventInfo ei)
106107
{
107-
return ClassManager.ShouldBindEvent(ei);
108+
return ClassManager.ShouldBindEvent(ei, bindingOptions);
108109
}
109110

110111
return false;

src/runtime/StateSerialization/MaybeMethodBase.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,11 @@ static MethodBase ScanForMethod(Type declaringType, string name, int genericCoun
117117

118118
var visibility = flags & MaybeMethodFlags.Visibility;
119119

120+
var bindingOptions = BindingManager.GetBindingOptions(declaringType);
121+
120122
var result = alternatives.Cast<MethodBase>().FirstOrDefault(m
121123
=> MatchesGenericCount(m, genericCount) && MatchesSignature(m, parameters)
122-
&& (Visibility(m) == visibility || ClassManager.ShouldBindMethod(m)));
124+
&& (Visibility(m) == visibility || ClassManager.ShouldBindMethod(m, bindingOptions)));
123125

124126
if (result is null)
125127
throw new MissingMethodException($"Matching overload not found for {declaringType}.{name}");

src/runtime/Types/ClrModule.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ internal class CLRModule : ModuleObject
1616
protected static bool interactive_preload = true;
1717
internal static bool preload;
1818
// XXX Test performance of new features //
19-
internal static bool _SuppressDocs = false;
2019
internal static bool _SuppressOverloads = false;
2120

2221
static CLRModule()
@@ -39,10 +38,6 @@ public static void Reset()
3938
{
4039
interactive_preload = true;
4140
preload = false;
42-
43-
// XXX Test performance of new features //
44-
_SuppressDocs = false;
45-
_SuppressOverloads = false;
4641
}
4742

4843
/// <summary>
@@ -82,15 +77,15 @@ public static void setPreload(bool preloadFlag)
8277
//[ModuleProperty]
8378
public static bool SuppressDocs
8479
{
85-
get { return _SuppressDocs; }
86-
set { _SuppressDocs = value; }
80+
get { return BindingManager.DefaultBindingOptions.SuppressDocs; }
81+
set { BindingManager.DefaultBindingOptions.SuppressDocs = value; }
8782
}
8883

8984
//[ModuleProperty]
9085
public static bool SuppressOverloads
9186
{
92-
get { return _SuppressOverloads; }
93-
set { _SuppressOverloads = value; }
87+
get { return BindingManager.DefaultBindingOptions.SuppressOverloads; }
88+
set { BindingManager.DefaultBindingOptions.SuppressOverloads = value; }
9489
}
9590

9691
[ModuleFunction]

src/testing/propertytest.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace Python.Test
22
{
3+
public interface IInherited {
4+
int InheritedProperty { get; }
5+
}
6+
37
/// <summary>
48
/// Supports units tests for property access.
59
/// </summary>
@@ -80,5 +84,17 @@ public ShortEnum EnumProperty
8084
get { return _enum_property; }
8185
set { _enum_property = value; }
8286
}
87+
88+
public int InheritedProperty => 1;
89+
}
90+
91+
public class PropertyTest2 : IInherited
92+
{
93+
int IInherited.InheritedProperty => 2;
94+
}
95+
96+
public class PropertyTest3 : IInherited
97+
{
98+
int IInherited.InheritedProperty => 3;
8399
}
84100
}

tests/conftest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,17 @@ def pytest_configure(config):
8787
import clr
8888

8989
sys.path.append(str(bin_path))
90-
clr.AddReference("Python.Test")
90+
python_test_module = clr.AddReference("Python.Test")
91+
configure_custom_binding_options(python_test_module)
92+
93+
94+
def configure_custom_binding_options(python_test_module):
95+
from Python.Runtime import BindingManager, BindingOptions
96+
binding_options = BindingOptions()
97+
binding_options.AllowExplicitInterfaceImplementation = True
98+
prop_test_3_type = [t for t in python_test_module.GetTypes() if "PropertyTest3" == t.Name][0]
99+
100+
BindingManager.SetBindingOptions(prop_test_3_type, binding_options)
91101

92102

93103
def pytest_unconfigure(config):

tests/test_property.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"""Test CLR property support."""
44

55
import pytest
6-
from Python.Test import PropertyTest
76

7+
from Python.Test import PropertyTest, PropertyTest2, PropertyTest3
88

99
def test_public_instance_property():
1010
"""Test public instance properties."""
@@ -93,6 +93,15 @@ def test_private_property():
9393
with pytest.raises(AttributeError):
9494
_ = PropertyTest.PrivateStaticProperty
9595

96+
def test_inherited_property():
97+
"""Test inherited properties."""
98+
99+
assert PropertyTest().InheritedProperty == 1
100+
with pytest.raises(AttributeError):
101+
_ = PropertyTest2().InheritedProperty
102+
103+
assert PropertyTest3().InheritedProperty == 3
104+
96105

97106
def test_property_descriptor_get_set():
98107
"""Test property descriptor get / set."""

0 commit comments

Comments
 (0)