Skip to content

Commit 2f16997

Browse files
authored
Merge branch 'master' into losttech-perf-interop
2 parents 59b2f29 + d8f5ab0 commit 2f16997

11 files changed

+111
-34
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
- 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 `PyExport` attribute to hide .NET types from Python
1617
- Added PythonException.Format method to format exceptions the same as traceback.format_exception
1718
- Added Runtime.None to be able to pass None as parameter into Python from .NET
1819
- Added PyObject.IsNone() to check if a Python object is None in .NET.

ci/appveyor_build_recipe.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ if ($env:PLATFORM -eq "x86"){
1313
$env:CONDA_BLD = "$env:CONDA_BLD" + "-x64"
1414
}
1515

16-
if ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $env:APPVEYOR_REPO_TAG_NAME -or $env:FORCE_CONDA_BUILD -eq "True") {
16+
if ($env:APPVEYOR_REPO_TAG_NAME -or $env:FORCE_CONDA_BUILD -eq "True") {
1717
# Update PATH, and keep a copy to restore at end of this PowerShell script
1818
$old_path = $env:path
1919
$env:path = "$env:CONDA_BLD;$env:CONDA_BLD\Scripts;" + $env:path

src/runtime/PyExportAttribute.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Python.Runtime {
2+
using System;
3+
4+
/// <summary>
5+
/// Controls visibility to Python for public .NET type or an entire assembly
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Enum
8+
| AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Assembly,
9+
AllowMultiple = false,
10+
Inherited = false)]
11+
public class PyExportAttribute : Attribute
12+
{
13+
internal readonly bool Export;
14+
public PyExportAttribute(bool export) { this.Export = export; }
15+
}
16+
}

src/runtime/Python.Runtime.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
<Compile Include="propertyobject.cs" />
132132
<Compile Include="pyansistring.cs" />
133133
<Compile Include="pydict.cs" />
134+
<Compile Include="PyExportAttribute.cs" />
134135
<Compile Include="pyfloat.cs" />
135136
<Compile Include="pyint.cs" />
136137
<Compile Include="pyiter.cs" />
@@ -151,6 +152,7 @@
151152
<Compile Include="Util.cs" />
152153
<Compile Include="platform\Types.cs" />
153154
<Compile Include="platform\LibraryLoader.cs" />
155+
<Compile Include="polyfill\ReflectionPolifills.cs" />
154156
<Compile Include="slots\mp_length.cs" />
155157
</ItemGroup>
156158
<ItemGroup Condition=" '$(PythonInteropFile)' != '' ">

src/runtime/assemblymanager.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ public static bool LoadImplicit(string name, bool warn = true)
346346
/// </summary>
347347
internal static void ScanAssembly(Assembly assembly)
348348
{
349+
if (assembly.GetCustomAttribute<PyExportAttribute>()?.Export == false)
350+
{
351+
return;
352+
}
349353
// A couple of things we want to do here: first, we want to
350354
// gather a list of all of the namespaces contributed to by
351355
// the assembly.
@@ -458,7 +462,7 @@ public static Type LookupType(string qname)
458462
foreach (Assembly assembly in assemblies)
459463
{
460464
Type type = assembly.GetType(qname);
461-
if (type != null)
465+
if (type != null && IsExported(type))
462466
{
463467
return type;
464468
}
@@ -472,33 +476,35 @@ public static Type LookupType(string qname)
472476
/// type.
473477
/// </summary>
474478
public static IEnumerable<Type> LookupTypes(string qualifiedName)
475-
=> assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null);
479+
=> assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null && IsExported(type));
476480

477481
internal static Type[] GetTypes(Assembly a)
478482
{
479483
if (a.IsDynamic)
480484
{
481485
try
482486
{
483-
return a.GetTypes();
487+
return a.GetTypes().Where(IsExported).ToArray();
484488
}
485489
catch (ReflectionTypeLoadException exc)
486490
{
487491
// Return all types that were successfully loaded
488-
return exc.Types.Where(x => x != null).ToArray();
492+
return exc.Types.Where(x => x != null && IsExported(x)).ToArray();
489493
}
490494
}
491495
else
492496
{
493497
try
494498
{
495-
return a.GetExportedTypes();
499+
return a.GetExportedTypes().Where(IsExported).ToArray();
496500
}
497501
catch (FileNotFoundException)
498502
{
499503
return new Type[0];
500504
}
501505
}
502506
}
507+
508+
static bool IsExported(Type type) => type.GetCustomAttribute<PyExportAttribute>()?.Export != false;
503509
}
504510
}

src/runtime/constructorbinder.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Reflection;
3+
using System.Text;
34

45
namespace Python.Runtime
56
{
@@ -93,7 +94,15 @@ internal object InvokeRaw(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
9394

9495
if (binding == null)
9596
{
96-
Exceptions.SetError(Exceptions.TypeError, "no constructor matches given arguments");
97+
var errorMessage = new StringBuilder("No constructor matches given arguments");
98+
if (info != null && info.IsConstructor && info.DeclaringType != null)
99+
{
100+
errorMessage.Append(" for ").Append(info.DeclaringType.Name);
101+
}
102+
103+
errorMessage.Append(": ");
104+
AppendArgumentTypes(to: errorMessage, args);
105+
Exceptions.SetError(Exceptions.TypeError, errorMessage.ToString());
97106
return null;
98107
}
99108
}

src/runtime/methodbinder.cs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,40 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
599599
return Invoke(inst, args, kw, info, null);
600600
}
601601

602+
protected static void AppendArgumentTypes(StringBuilder to, IntPtr args)
603+
{
604+
long argCount = Runtime.PyTuple_Size(args);
605+
to.Append("(");
606+
for (long argIndex = 0; argIndex < argCount; argIndex++)
607+
{
608+
var arg = Runtime.PyTuple_GetItem(args, argIndex);
609+
if (arg != IntPtr.Zero)
610+
{
611+
var type = Runtime.PyObject_Type(arg);
612+
if (type != IntPtr.Zero)
613+
{
614+
try
615+
{
616+
var description = Runtime.PyObject_Unicode(type);
617+
if (description != IntPtr.Zero)
618+
{
619+
to.Append(Runtime.GetManagedString(description));
620+
Runtime.XDecref(description);
621+
}
622+
}
623+
finally
624+
{
625+
Runtime.XDecref(type);
626+
}
627+
}
628+
}
629+
630+
if (argIndex + 1 < argCount)
631+
to.Append(", ");
632+
}
633+
to.Append(')');
634+
}
635+
602636
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo)
603637
{
604638
Binding binding = Bind(inst, args, kw, info, methodinfo);
@@ -613,29 +647,8 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
613647
value.Append($" for {methodinfo[0].Name}");
614648
}
615649

616-
long argCount = Runtime.PyTuple_Size(args);
617-
value.Append(": (");
618-
for(long argIndex = 0; argIndex < argCount; argIndex++) {
619-
var arg = Runtime.PyTuple_GetItem(args, argIndex);
620-
if (arg != IntPtr.Zero) {
621-
var type = Runtime.PyObject_Type(arg);
622-
if (type != IntPtr.Zero) {
623-
try {
624-
var description = Runtime.PyObject_Unicode(type);
625-
if (description != IntPtr.Zero) {
626-
value.Append(Runtime.GetManagedString(description));
627-
Runtime.XDecref(description);
628-
}
629-
} finally {
630-
Runtime.XDecref(type);
631-
}
632-
}
633-
}
634-
635-
if (argIndex + 1 < argCount)
636-
value.Append(", ");
637-
}
638-
value.Append(')');
650+
value.Append(": ");
651+
AppendArgumentTypes(to: value, args);
639652
Exceptions.SetError(Exceptions.TypeError, value.ToString());
640653
return IntPtr.Zero;
641654
}
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
using System;
2+
using System.Linq;
23
using System.Reflection;
34
using System.Reflection.Emit;
45

56
namespace Python.Runtime
67
{
7-
#if NETSTANDARD
8+
[Obsolete("This API is for internal use only")]
89
public static class ReflectionPolifills
910
{
11+
#if NETSTANDARD
1012
public static AssemblyBuilder DefineDynamicAssembly(this AppDomain appDomain, AssemblyName assemblyName, AssemblyBuilderAccess assemblyBuilderAccess)
1113
{
1214
return AssemblyBuilder.DefineDynamicAssembly(assemblyName, assemblyBuilderAccess);
@@ -16,6 +18,19 @@ public static Type CreateType(this TypeBuilder typeBuilder)
1618
{
1719
return typeBuilder.GetTypeInfo().GetType();
1820
}
19-
}
2021
#endif
22+
public static T GetCustomAttribute<T>(this Type type) where T: Attribute
23+
{
24+
return type.GetCustomAttributes(typeof(T), inherit: false)
25+
.Cast<T>()
26+
.SingleOrDefault();
27+
}
28+
29+
public static T GetCustomAttribute<T>(this Assembly assembly) where T: Attribute
30+
{
31+
return assembly.GetCustomAttributes(typeof(T), inherit: false)
32+
.Cast<T>()
33+
.SingleOrDefault();
34+
}
35+
}
2136
}

src/testing/Python.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<Compile Include="doctest.cs" />
9393
<Compile Include="subclasstest.cs" />
9494
<Compile Include="ReprTest.cs" />
95+
<Compile Include="nonexportable.cs" />
9596
<Compile Include="mp_lengthtest.cs" />
9697
</ItemGroup>
9798
<ItemGroup>

src/testing/nonexportable.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Python.Test
2+
{
3+
using Python.Runtime;
4+
5+
// this class should not be visible to Python
6+
[PyExport(false)]
7+
public class NonExportable { }
8+
}

src/tests/test_class.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def test_basic_reference_type():
1414
"""Test usage of CLR defined reference types."""
1515
assert System.String.Empty == ""
1616

17-
1817
def test_basic_value_type():
1918
"""Test usage of CLR defined value types."""
2019
assert System.Int32.MaxValue == 2147483647
@@ -29,7 +28,6 @@ def test_class_standard_attrs():
2928
assert isinstance(ClassTest.__dict__, DictProxyType)
3029
assert len(ClassTest.__doc__) > 0
3130

32-
3331
def test_class_docstrings():
3432
"""Test standard class docstring generation"""
3533
from Python.Test import ClassTest
@@ -58,6 +56,14 @@ def test_non_public_class():
5856
with pytest.raises(AttributeError):
5957
_ = Test.InternalClass
6058

59+
def test_non_exported():
60+
"""Test [PyExport(false)]"""
61+
with pytest.raises(ImportError):
62+
from Python.Test import NonExportable
63+
64+
with pytest.raises(AttributeError):
65+
_ = Test.NonExportable
66+
6167

6268
def test_basic_subclass():
6369
"""Test basic subclass of a managed class."""

0 commit comments

Comments
 (0)