Environment:
- Python: 3.12.x
- pythonnet: 3.0.3, 3.0.5 (and other 3.0.x versions)
- clr-loader: 0.2.6 (also tested with 0.2.7.post0)
- .NET Runtimes Tested:
- .NET 8 (e.g., 8.0.16) - Fails
- .NET 9 (e.g., 9.0.5) - Works
- OS: Windows 10/11 (primarily tested on Windows)
Problem:
When using pythonnet 3.0.x with Python 3.12 to interact with .NET 8 assemblies via CoreCLR, pythonnet successfully loads the .NET 8 runtime and the target assemblies into the AppDomain. However, it fails to properly expose the .NET types to the Python environment. This results in AttributeError when trying to access types directly from an imported namespace, or ImportError when a Python module within a package attempts to import these .NET types.
The same operations succeed when the .NET assembly is compiled for .NET 9 and the .NET 9 runtime is used with the same pythonnet version.
Observed Behavior (.NET 8):
- .NET 8 CoreCLR is loaded (confirmed via
System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription and assemblies like System.Private.CoreLib, Version=8.0.0.0 in AppDomain).
- Target .NET 8 assembly is loaded into
System.AppDomain.CurrentDomain.GetAssemblies().
clr.AddReference("Path/To/Assembly.dll") completes without error.
- Importing the assembly's namespace (e.g.,
import MyNamespace) succeeds.
- Attempting to access a type (e.g.,
MyNamespace.MyClass) results in AttributeError: module 'MyNamespace' has no attribute 'MyClass'.
- Alternatively, if a package's Python module tries
from MyNamespace import MyClass (after clr.AddReference in __init__.py), it fails with ImportError: cannot import name 'MyClass' from 'MyNamespace'.
- The
clr module object consistently lacks the References attribute (i.e., hasattr(clr, "References") is False) after .NET CoreCLR initialization.
clr.FindAssembly("AssemblyName") returns a string path to the DLL, not an Assembly object.
Expected Behavior:
- Types from .NET 8 assemblies should be accessible in Python after
clr.AddReference() and importing the namespace.
- No
AttributeError or ImportError should occur when accessing correctly referenced .NET types.
- The behavior of
clr.References and clr.FindAssembly() with .NET CoreCLR should ideally be consistent with .NET Framework or clearly documented if different.
Steps to Reproduce (Minimal Case):
-
Create a simple .NET 8 Class Library (MinimalNet8Lib):
MinimalNet8Lib.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
</Project>
Class1.cs (or e.g., Greeter.cs):
namespace MinimalNet8Lib;
public class Greeter
{
public static string SayHello(string name)
{
return $"Hello, {name} from .NET 8!";
}
public string InstanceHello(string name)
{
return $"Instance hello to {name} from .NET 8!";
}
}
- Build the library:
dotnet build -c Release
-
Create a Python Test Script (test_minimal_net8.py):
import os
import sys
from pathlib import Path
print(f"Python version: {sys.version}")
print(f"Python executable: {sys.executable}")
# --- Crucial: Set PYTHONNET_RUNTIME to coreclr BEFORE pythonnet import ---
os.environ["PYTHONNET_RUNTIME"] = "coreclr"
try:
print("Attempting to import pythonnet and clr_loader...")
import pythonnet
import clr_loader
print(f"pythonnet version: {pythonnet.__version__}")
print(f"clr_loader version: {clr_loader.__version__}")
except Exception as e:
print(f"Error importing pythonnet/clr_loader: {e}")
sys.exit(1)
# --- Setup Paths ---
# Adjust this path to your MinimalNet8Lib.dll
# Assumes the script is run from a directory where MinimalNet8Lib/bin/Release/net8.0/MinimalNet8Lib.dll exists
script_dir = Path(__file__).parent.resolve()
dll_path = script_dir / "MinimalNet8Lib" / "bin" / "Release" / "net8.0" / "MinimalNet8Lib.dll"
runtime_config_path = script_dir / "MinimalNet8Lib" / "bin" / "Release" / "net8.0" / "MinimalNet8Lib.runtimeconfig.json"
# Optional: Specify DOTNET_ROOT if not discoverable
# dotnet_root = Path(os.environ.get("DOTNET_ROOT", "C:/Program Files/dotnet"))
print(f"Target DLL: {dll_path}")
print(f"Runtime Config: {runtime_config_path}")
if not dll_path.exists():
print(f"ERROR: DLL not found at {dll_path}")
sys.exit(1)
if not runtime_config_path.exists():
print(f"ERROR: Runtime config not found at {runtime_config_path}")
sys.exit(1)
try:
print("\n--- Initializing .NET CoreCLR Runtime ---")
# Using clr_loader to get the runtime
rt = clr_loader.get_coreclr(runtime_config=str(runtime_config_path)) #, dotnet_root=str(dotnet_root)
print(f"CoreCLR runtime object: {rt}")
print("Setting pythonnet runtime...")
pythonnet.set_runtime(rt)
print("Pythonnet runtime set.")
print("Attempting to import clr...")
import clr
print("Successfully imported clr.")
print(f"clr module: {clr}")
if hasattr(clr, "__version__"):
print(f"clr (pythonnet) version attribute: {clr.__version__}")
except Exception as e:
print(f"ERROR during runtime initialization or clr import: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
print("\n--- Runtime and Assembly Diagnostics ---")
try:
from System import Environment, AppDomain
from System.Runtime.InteropServices import RuntimeInformation
print(f"PythonEngine.Version: {pythonnet.get_version()}")
print(f"PythonEngine.IsInitialized: {pythonnet.is_initialized()}")
print(f"System.Environment.Version: {Environment.Version}")
print(f"RuntimeInformation.FrameworkDescription: {RuntimeInformation.FrameworkDescription}")
print(f"Adding reference to: {str(dll_path)}")
clr.AddReference(str(dll_path))
print("clr.AddReference completed.")
print("Assemblies in Current AppDomain:")
for assembly in AppDomain.CurrentDomain.GetAssemblies():
print(f" - {assembly.FullName}")
if "MinimalNet8Lib" in assembly.FullName:
print(f" FOUND MinimalNet8Lib: {assembly.FullName}, Location: {assembly.Location}")
print(f"clr.FindAssembly('MinimalNet8Lib'): {clr.FindAssembly('MinimalNet8Lib')}")
print(f"hasattr(clr, 'References'): {hasattr(clr, 'References')}")
if hasattr(clr, 'References') and clr.References is not None:
print(f"Number of clr.References: {len(clr.References)}")
else:
print("clr.References attribute is missing or None.")
except Exception as e:
print(f"Error during diagnostics: {e}")
import traceback
traceback.print_exc()
# Continue to type access attempt
print("\n--- Attempting to Access .NET Types ---")
try:
print("Attempting: import MinimalNet8Lib")
import MinimalNet8Lib
print("Successfully imported MinimalNet8Lib namespace.")
print("Attempting: greeter = MinimalNet8Lib.Greeter()")
greeter = MinimalNet8Lib.Greeter() # This is where AttributeError typically occurs
print(f"Successfully created Greeter instance: {greeter}")
print(f"Calling instance method: {greeter.InstanceHello('Python')}")
print(f"Calling static method: {MinimalNet8Lib.Greeter.SayHello('Python')}")
print("\nSUCCESS: .NET 8 types accessed successfully!")
except AttributeError as ae:
print(f"FAILURE: AttributeError: {ae}")
print("This indicates types from the .NET 8 assembly were not exposed correctly.")
import traceback
traceback.print_exc()
except Exception as e:
print(f"FAILURE: An unexpected error occurred: {e}")
import traceback
traceback.print_exc()
print("\nScript finished.")
-
Run the Python script:
python test_minimal_net8.py
This will show the AttributeError.
-
Test with .NET 9 (Works):
- Change
TargetFramework in MinimalNet8Lib.csproj to net9.0.
- Rebuild:
dotnet build -c Release.
- Update
dll_path and runtime_config_path in test_minimal_net8.py to point to the net9.0 outputs.
- Ensure .NET 9 SDK/Runtime is installed and discoverable.
- Run
test_minimal_net8.py. The script should complete successfully, accessing the .NET types.
Additional Notes:
- The issue has been observed with
pythonnet versions 3.0.3 and 3.0.5.
- The problem is reproducible with both a minimal class library and more complex, real-world libraries (e.g.,
Tableau.Migration.dll v5.0.1, which targets only net8.0 and uses pythonnet.load("coreclr") in its __init__.py, leading to an ImportError when its Python submodules try to import the .NET types).
- Setting
PYTHONNET_RUNTIME=coreclr and using clr_loader.get_coreclr() followed by pythonnet.set_runtime() before import clr is a reliable way to initialize .NET CoreCLR. Simpler methods like just pythonnet.load("coreclr") or import clr can sometimes lead to .NET Framework being loaded if not carefully managed, but the type exposure issue persists even with explicit and correct .NET 8 CoreCLR initialization.
We believe this is a significant issue for users attempting to leverage .NET 8 libraries with Python via pythonnet. The fact that it works seamlessly with .NET 9 suggests a specific interaction problem with .NET 8.
Environment:
Problem:
When using
pythonnet3.0.x with Python 3.12 to interact with .NET 8 assemblies via CoreCLR,pythonnetsuccessfully loads the .NET 8 runtime and the target assemblies into the AppDomain. However, it fails to properly expose the .NET types to the Python environment. This results inAttributeErrorwhen trying to access types directly from an imported namespace, orImportErrorwhen a Python module within a package attempts to import these .NET types.The same operations succeed when the .NET assembly is compiled for .NET 9 and the .NET 9 runtime is used with the same
pythonnetversion.Observed Behavior (.NET 8):
System.Runtime.InteropServices.RuntimeInformation.FrameworkDescriptionand assemblies likeSystem.Private.CoreLib, Version=8.0.0.0in AppDomain).System.AppDomain.CurrentDomain.GetAssemblies().clr.AddReference("Path/To/Assembly.dll")completes without error.import MyNamespace) succeeds.MyNamespace.MyClass) results inAttributeError: module 'MyNamespace' has no attribute 'MyClass'.from MyNamespace import MyClass(afterclr.AddReferencein__init__.py), it fails withImportError: cannot import name 'MyClass' from 'MyNamespace'.clrmodule object consistently lacks theReferencesattribute (i.e.,hasattr(clr, "References")isFalse) after .NET CoreCLR initialization.clr.FindAssembly("AssemblyName")returns a string path to the DLL, not anAssemblyobject.Expected Behavior:
clr.AddReference()and importing the namespace.AttributeErrororImportErrorshould occur when accessing correctly referenced .NET types.clr.Referencesandclr.FindAssembly()with .NET CoreCLR should ideally be consistent with .NET Framework or clearly documented if different.Steps to Reproduce (Minimal Case):
Create a simple .NET 8 Class Library (
MinimalNet8Lib):MinimalNet8Lib.csproj:Class1.cs(or e.g.,Greeter.cs):dotnet build -c ReleaseCreate a Python Test Script (
test_minimal_net8.py):Run the Python script:
python test_minimal_net8.pyThis will show the
AttributeError.Test with .NET 9 (Works):
TargetFrameworkinMinimalNet8Lib.csprojtonet9.0.dotnet build -c Release.dll_pathandruntime_config_pathintest_minimal_net8.pyto point to thenet9.0outputs.test_minimal_net8.py. The script should complete successfully, accessing the .NET types.Additional Notes:
pythonnetversions 3.0.3 and 3.0.5.Tableau.Migration.dllv5.0.1, which targets onlynet8.0and usespythonnet.load("coreclr")in its__init__.py, leading to anImportErrorwhen its Python submodules try to import the .NET types).PYTHONNET_RUNTIME=coreclrand usingclr_loader.get_coreclr()followed bypythonnet.set_runtime()beforeimport clris a reliable way to initialize .NET CoreCLR. Simpler methods like justpythonnet.load("coreclr")orimport clrcan sometimes lead to .NET Framework being loaded if not carefully managed, but the type exposure issue persists even with explicit and correct .NET 8 CoreCLR initialization.We believe this is a significant issue for users attempting to leverage .NET 8 libraries with Python via
pythonnet. The fact that it works seamlessly with .NET 9 suggests a specific interaction problem with .NET 8.