Skip to content

Commit 467d1fd

Browse files
committed
allow excluding public .NET types from being exposed to Python
Introduced PyExportAttribute and handling for it in AssemblyManager
1 parent 72fae73 commit 467d1fd

File tree

9 files changed

+76
-17
lines changed

9 files changed

+76
-17
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.

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ def build_extension(self, ext):
306306
_config = "{0}Win".format(CONFIG)
307307
_solution_file = "pythonnet.sln"
308308
_custom_define_constants = False
309+
defines.append("NET40")
309310
elif DEVTOOLS == "MsDev15":
310311
_xbuild = '"{0}"'.format(self._find_msbuild_tool_15())
311312
_config = "{0}Win".format(CONFIG)
@@ -316,6 +317,7 @@ def build_extension(self, ext):
316317
_config = "{0}Mono".format(CONFIG)
317318
_solution_file = "pythonnet.sln"
318319
_custom_define_constants = False
320+
defines.append("NET40")
319321
elif DEVTOOLS == "dotnet":
320322
_xbuild = "dotnet msbuild"
321323
_config = "{0}Mono".format(CONFIG)

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: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,46 +29,46 @@
2929
<PlatformTarget>x64</PlatformTarget>
3030
</PropertyGroup>-->
3131
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseMono'">
32-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON2;PYTHON27;UCS4</DefineConstants>
32+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON2;PYTHON27;UCS4</DefineConstants>
3333
<Optimize>true</Optimize>
3434
<DebugType>pdbonly</DebugType>
3535
</PropertyGroup>
3636
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseMonoPY3'">
37-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON3;PYTHON38;UCS4</DefineConstants>
37+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON3;PYTHON38;UCS4</DefineConstants>
3838
<Optimize>true</Optimize>
3939
<DebugType>pdbonly</DebugType>
4040
</PropertyGroup>
4141
<PropertyGroup Condition=" '$(Configuration)' == 'DebugMono'">
4242
<DebugSymbols>true</DebugSymbols>
43-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON2;PYTHON27;UCS4;TRACE;DEBUG</DefineConstants>
43+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON2;PYTHON27;UCS4;TRACE;DEBUG</DefineConstants>
4444
<Optimize>false</Optimize>
4545
<DebugType>full</DebugType>
4646
</PropertyGroup>
4747
<PropertyGroup Condition=" '$(Configuration)' == 'DebugMonoPY3'">
4848
<DebugSymbols>true</DebugSymbols>
49-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON3;PYTHON38;UCS4;TRACE;DEBUG</DefineConstants>
49+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON3;PYTHON38;UCS4;TRACE;DEBUG</DefineConstants>
5050
<Optimize>false</Optimize>
5151
<DebugType>full</DebugType>
5252
</PropertyGroup>
5353
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseWin'">
54-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON2;PYTHON27;UCS2</DefineConstants>
54+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON2;PYTHON27;UCS2</DefineConstants>
5555
<Optimize>true</Optimize>
5656
<DebugType>pdbonly</DebugType>
5757
</PropertyGroup>
5858
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseWinPY3'">
59-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON3;PYTHON38;UCS2</DefineConstants>
59+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON3;PYTHON38;UCS2</DefineConstants>
6060
<Optimize>true</Optimize>
6161
<DebugType>pdbonly</DebugType>
6262
</PropertyGroup>
6363
<PropertyGroup Condition=" '$(Configuration)' == 'DebugWin'">
6464
<DebugSymbols>true</DebugSymbols>
65-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON2;PYTHON27;UCS2;TRACE;DEBUG</DefineConstants>
65+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON2;PYTHON27;UCS2;TRACE;DEBUG</DefineConstants>
6666
<Optimize>false</Optimize>
6767
<DebugType>full</DebugType>
6868
</PropertyGroup>
6969
<PropertyGroup Condition=" '$(Configuration)' == 'DebugWinPY3'">
7070
<DebugSymbols>true</DebugSymbols>
71-
<DefineConstants Condition="'$(DefineConstants)' == ''">PYTHON3;PYTHON38;UCS2;TRACE;DEBUG</DefineConstants>
71+
<DefineConstants Condition="'$(DefineConstants)' == ''">NET40;PYTHON3;PYTHON38;UCS2;TRACE;DEBUG</DefineConstants>
7272
<Optimize>false</Optimize>
7373
<DebugType>full</DebugType>
7474
</PropertyGroup>
@@ -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
}
Lines changed: 19 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,21 @@ public static Type CreateType(this TypeBuilder typeBuilder)
1618
{
1719
return typeBuilder.GetTypeInfo().GetType();
1820
}
19-
}
2021
#endif
22+
#if NET40
23+
public static T GetCustomAttribute<T>(this Type type) where T: Attribute
24+
{
25+
return type.GetCustomAttributes(typeof(T), inherit: false)
26+
.Cast<T>()
27+
.SingleOrDefault();
28+
}
29+
30+
public static T GetCustomAttribute<T>(this Assembly assembly) where T: Attribute
31+
{
32+
return assembly.GetCustomAttributes(typeof(T), inherit: false)
33+
.Cast<T>()
34+
.SingleOrDefault();
35+
}
36+
#endif
37+
}
2138
}

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy