Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/core/IronPython/Hosting/PythonCommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ protected override void Initialize() {
// the first entry in sys.path. If running a script file, however, the CWD should not be added;
// instead, the script's containg folder should be added.

string fullPath = "."; // this is a valid path resolving to current working dir. Pinky-swear.
string fullPath = ""; // the current working directory, importlib resolves it with os.getcwd()

if (Options.Command == null && Options.FileName != null) {
if (Options.FileName == "-") {
Expand Down
77 changes: 74 additions & 3 deletions src/core/IronPython/Runtime/Importer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,9 @@ private static object ImportTopAbsolute(CodeContext/*!*/ context, string/*!*/ na
}

if (TryLoadMetaPathModule(context, name, null, out ret)) {
// importlib's BuiltinImporter loads clr before ImportBuiltin gets a chance to
if (ret == context.LanguageContext.ClrModule) {
// importlib's BuiltinImporter loads clr before ImportBuiltin gets a chance to,
// and ClrImporter loads .NET namespaces and types
if (ret is MemberTracker || ret is PythonType { IsSystemType: true } || ret == context.LanguageContext.ClrModule) {
context.ShowCls = true;
}
return ret;
Expand Down Expand Up @@ -842,7 +843,8 @@ private static object LoadFromDisk(CodeContext context, string name, string full
// default behavior
string pathname;
try {
pathname = context.LanguageContext.DomainManager.Platform.CombinePaths(str, name);
// an empty path entry is the current working directory
pathname = context.LanguageContext.DomainManager.Platform.CombinePaths(str.Length == 0 ? "." : str, name);
} catch (ArgumentException) {
return null; // ignore invalid paths
}
Expand Down Expand Up @@ -934,5 +936,74 @@ internal static PythonModule LoadPackageFromSource(CodeContext/*!*/ context, str
Assert.NotNull(sourceCode, name, path);
return context.LanguageContext.CompileModule(path, name, sourceCode, ModuleOptions.Initialize | ModuleOptions.Optimized);
}

#nullable enable

/// <summary>
/// sys.meta_path finder for .NET namespaces and types. It is added after importlib's PathFinder
/// so Python modules take precedence over .NET namespaces and types.
/// </summary>
[PythonType]
public sealed class ClrImporter {
private readonly object _moduleSpec;

internal ClrImporter(object moduleSpec) {
_moduleSpec = moduleSpec;
}

public object? find_spec(CodeContext/*!*/ context, [NotNone] string fullname, object? path = null, object? target = null) {
object? member = FindMember(context, fullname);
if (member is null) return null;

// we return "this" as the loader for the module, which uses the legacy load_module
// rather than exec_module since types and namespaces don't support setting
// the module attributes (__name__, etc.)
return PythonCalls.CallWithKeywordArgs(context, _moduleSpec, [fullname, this, member is NamespaceTracker], ["is_package"]);
}

public object? find_module(CodeContext/*!*/ context, [NotNone] string fullname, object? path = null) {
return FindMember(context, fullname) is null ? null : this;
}

public object load_module(CodeContext/*!*/ context, [NotNone] string fullname) {
var modules = context.LanguageContext.SystemStateModules;
if (modules.TryGetValue(fullname, out object? existing) && existing is not null) {
return existing;
}

object member = FindMember(context, fullname) ?? throw PythonOps.ImportError("No module named {0}", fullname);
modules[fullname] = member;
return member;
}

/// <summary>
/// Looks up a top-level namespace or type, or a child of a namespace that has been imported.
/// Returns the same object as attribute access on the namespace.
/// </summary>
private static object? FindMember(CodeContext/*!*/ context, string fullname) {
PythonContext pc = context.LanguageContext;
NamespaceTracker? parent;
string name;

int lastDot = fullname.LastIndexOf('.');
if (lastDot == -1) {
// names in the runtime's globals (e.g. COM type libraries) take precedence over namespaces
// and are left to the legacy importer
if (PythonOps.ScopeTryGetMember(context, pc.DomainManager.Globals, fullname, out _)) return null;
parent = pc.TopNamespace;
name = fullname;
} else {
parent = pc.SystemStateModules.TryGetValue(fullname.Substring(0, lastDot), out object? parentModule) ? parentModule as NamespaceTracker : null;
name = fullname.Substring(lastDot + 1);
}

if (parent is null) return null;

object member = NamespaceTrackerOps.GetCustomMember(context, parent, name);
return member == OperationFailed.Value ? null : member;
}
}

#nullable restore
}
}
2 changes: 2 additions & 0 deletions src/core/IronPython/Runtime/PythonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ void BootstrapImportLib() {
#if PYTHON_37_OR_GREATER
PythonOps.Invoke(SharedClsContext, _frozen_importlib, "_install_external_importers");
#endif
var moduleSpec = PythonOps.GetBoundAttr(SharedClsContext, _frozen_importlib, "ModuleSpec");
(GetSystemStateValue("meta_path") as PythonList)?.append(new Importer.ClrImporter(moduleSpec));
} catch { }

PythonModule LoadModuleFromResource(string name, string resourceName) {
Expand Down
72 changes: 71 additions & 1 deletion tests/suite/test_imp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,13 +1064,83 @@ def test_import_clr_show_cls(self):
clr = sys.modules.pop('clr')
try:
g = {}
exec("r1 = hasattr('abc', 'Split')\nimport clr\nr2 = hasattr('abc', 'Split')", g)
exec(compile("r1 = hasattr('abc', 'Split')\nimport clr\nr2 = hasattr('abc', 'Split')", '<string>', 'exec', dont_inherit=True), g)
self.assertFalse(g['r1'])
self.assertTrue(g['r2'])
self.assertIs(g['clr'], clr)
finally:
sys.modules['clr'] = clr

@skipUnlessIronPython()
def test_clr_importer(self):
import importlib
import System
import System.Collections.Generic

names = [type(finder).__name__ if not isinstance(finder, type) else finder.__name__ for finder in sys.meta_path]
self.assertLess(names.index('PathFinder'), names.index('ClrImporter'))

# namespaces and types are imported as the same objects as attribute access gives
self.assertIs(importlib.import_module('System.Collections.Generic'), System.Collections.Generic)
self.assertIs(importlib.import_module('System.Math'), System.Math)
self.assertIs(importlib.import_module('.Generic', 'System.Collections'), System.Collections.Generic)
self.assertRaises(ImportError, importlib.import_module, 'System.Nonexistent')

# the first import of a namespace goes through ClrImporter
index = names.index('ClrImporter')
finder = sys.meta_path[index]
found = []
class Spy:
def find_module(self, fullname, path=None):
found.append(fullname)
return finder.find_module(fullname, path)

system = sys.modules.pop('System')
sys.meta_path[index] = Spy()
try:
g = {}
exec(compile("r1 = hasattr('abc', 'Split')\nimport System\nr2 = hasattr('abc', 'Split')", '<string>', 'exec', dont_inherit=True), g)
self.assertEqual(found, ['System'])
self.assertFalse(g['r1'])
self.assertTrue(g['r2'])
self.assertIs(g['System'], system)
finally:
sys.meta_path[index] = finder
sys.modules['System'] = system

@skipUnlessIronPython()
def test_clr_importer_global_type(self):
# the first import of a type in the global namespace enables CLS members
import clr
from System.Reflection import AssemblyName, TypeAttributes
from System.Reflection.Emit import AssemblyBuilder, AssemblyBuilderAccess
ab = AssemblyBuilder.DefineDynamicAssembly(AssemblyName('ClrImporterGlobalType'), AssemblyBuilderAccess.Run)
ab.DefineDynamicModule('ClrImporterGlobalType').DefineType('ClrImporterGlobalType', TypeAttributes.Public).CreateType()
clr.AddReference(ab)

g = {}
exec(compile("r1 = hasattr('abc', 'Split')\nimport ClrImporterGlobalType\nr2 = hasattr('abc', 'Split')", '<string>', 'exec', dont_inherit=True), g)
self.assertFalse(g['r1'])
self.assertTrue(g['r2'])
self.assertEqual(g['ClrImporterGlobalType'].__name__, 'ClrImporterGlobalType')

@skipUnlessIronPython()
def test_clr_importer_globals(self):
# the runtime's globals take precedence over namespaces
import clr
import System
runtime_globals = clr.GetCurrentRuntime().Globals
value = object()
system = sys.modules.pop('System')
runtime_globals.System = value
try:
g = {}
exec("import System", g)
self.assertIs(g['System'], value)
finally:
del runtime_globals.System
sys.modules['System'] = system

def test_get_frozen_object(self):
# frozen objects not supported, this always fails
self.assertRaises(ImportError, imp.get_frozen_object, 'foo')
Expand Down
Loading