Skip to content

Commit 2e6d12f

Browse files
committed
Remove deprecated implicit assembly loading
Legacy behavior: `import Company.Product.Namespace` would search for `Company.Product.Namespace.dll` .NET assmebly, load it, and import the namespace. New behavior: User must always explicitly add assembly reference using `clr.AddReference` to `Company.Product.Namespace` prior to attempting import
1 parent 6dd40dc commit 2e6d12f

8 files changed

+21
-132
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ details about the cause of the failure
3333
- Fixed a bug where indexers could not be used if they were inherited
3434
- Made it possible to use `__len__` also on `ICollection<>` interface objects
3535

36+
### Removed
37+
38+
- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import)
39+
3640
## [2.5.0][] - 2020-06-14
3741

3842
This version improves performance on benchmarks significantly compared to 2.3.

src/runtime/assemblymanager.cs

-69
Original file line numberDiff line numberDiff line change
@@ -252,75 +252,6 @@ public static Assembly FindLoadedAssembly(string name)
252252
return null;
253253
}
254254

255-
/// <summary>
256-
/// Given a qualified name of the form A.B.C.D, attempt to load
257-
/// an assembly named after each of A.B.C.D, A.B.C, A.B, A. This
258-
/// will only actually probe for the assembly once for each unique
259-
/// namespace. Returns true if any assemblies were loaded.
260-
/// </summary>
261-
/// <remarks>
262-
/// TODO item 3 "* Deprecate implicit loading of assemblies":
263-
/// Set the fromFile flag if the name of the loaded assembly matches
264-
/// the fully qualified name that was requested if the framework
265-
/// actually loads an assembly.
266-
/// Call ONLY for namespaces that HAVE NOT been cached yet.
267-
/// </remarks>
268-
public static bool LoadImplicit(string name, Action<Exception> assemblyLoadErrorHandler, bool warn = true)
269-
{
270-
string[] names = name.Split('.');
271-
var loaded = false;
272-
var s = "";
273-
Assembly lastAssembly = null;
274-
HashSet<Assembly> assembliesSet = null;
275-
for (var i = 0; i < names.Length; i++)
276-
{
277-
s = i == 0 ? names[0] : s + "." + names[i];
278-
if (!probed.ContainsKey(s))
279-
{
280-
if (assembliesSet == null)
281-
{
282-
assembliesSet = new HashSet<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
283-
}
284-
Assembly a = FindLoadedAssembly(s);
285-
try
286-
{
287-
if (a == null)
288-
{
289-
a = LoadAssemblyPath(s);
290-
}
291-
292-
if (a == null)
293-
{
294-
a = LoadAssembly(s);
295-
}
296-
}
297-
catch (FileLoadException e) { assemblyLoadErrorHandler(e); }
298-
catch (BadImageFormatException e) { assemblyLoadErrorHandler(e); }
299-
catch (System.Security.SecurityException e) { assemblyLoadErrorHandler(e); }
300-
catch (PathTooLongException e) { assemblyLoadErrorHandler(e); }
301-
302-
if (a != null && !assembliesSet.Contains(a))
303-
{
304-
loaded = true;
305-
lastAssembly = a;
306-
}
307-
probed[s] = 1;
308-
}
309-
}
310-
311-
// Deprecation warning
312-
if (warn && loaded)
313-
{
314-
string location = Path.GetFileNameWithoutExtension(lastAssembly.Location);
315-
string deprWarning = "The module was found, but not in a referenced namespace.\n" +
316-
$"Implicit loading is deprecated. Please use clr.AddReference('{location}').";
317-
Exceptions.deprecation(deprWarning);
318-
}
319-
320-
return loaded;
321-
}
322-
323-
324255
/// <summary>
325256
/// Scans an assembly for exported namespaces, adding them to the
326257
/// mapping of valid namespaces. Note that for a given namespace

src/runtime/importhook.cs

-32
Original file line numberDiff line numberDiff line change
@@ -310,38 +310,6 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw)
310310

311311
string[] names = realname.Split('.');
312312

313-
// Now we need to decide if the name refers to a CLR module,
314-
// and may have to do an implicit load (for b/w compatibility)
315-
// using the AssemblyManager. The assembly manager tries
316-
// really hard not to use Python objects or APIs, because
317-
// parts of it can run recursively and on strange threads.
318-
//
319-
// It does need an opportunity from time to time to check to
320-
// see if sys.path has changed, in a context that is safe. Here
321-
// we know we have the GIL, so we'll let it update if needed.
322-
323-
AssemblyManager.UpdatePath();
324-
if (!AssemblyManager.IsValidNamespace(realname))
325-
{
326-
var loadExceptions = new List<Exception>();
327-
if (!AssemblyManager.LoadImplicit(realname, assemblyLoadErrorHandler: loadExceptions.Add))
328-
{
329-
// May be called when a module being imported imports a module.
330-
// In particular, I've seen decimal import copy import org.python.core
331-
IntPtr importResult = Runtime.PyObject_Call(py_import, args, kw);
332-
// TODO: use ModuleNotFoundError in Python 3.6+
333-
if (importResult == IntPtr.Zero && loadExceptions.Count > 0
334-
&& Exceptions.ExceptionMatches(Exceptions.ImportError))
335-
{
336-
loadExceptions.Add(new PythonException());
337-
var importError = new PyObject(new BorrowedReference(Exceptions.ImportError));
338-
importError.SetAttr("__cause__", new AggregateException(loadExceptions).ToPython());
339-
Runtime.PyErr_SetObject(new BorrowedReference(Exceptions.ImportError), importError.Reference);
340-
}
341-
return importResult;
342-
}
343-
}
344-
345313
// See if sys.modules for this interpreter already has the
346314
// requested module. If so, just return the existing module.
347315
IntPtr modules = Runtime.PyImport_GetModuleDict();

src/runtime/moduleobject.cs

-24
Original file line numberDiff line numberDiff line change
@@ -118,30 +118,6 @@ public ManagedType GetAttribute(string name, bool guess)
118118
return c;
119119
}
120120

121-
// This is a little repetitive, but it ensures that the right
122-
// thing happens with implicit assembly loading at a reasonable
123-
// cost. Ask the AssemblyManager to do implicit loading for each
124-
// of the steps in the qualified name, then try it again.
125-
bool ignore = name.StartsWith("__");
126-
if (AssemblyManager.LoadImplicit(qname, assemblyLoadErrorHandler: ImportWarning, !ignore))
127-
{
128-
if (AssemblyManager.IsValidNamespace(qname))
129-
{
130-
m = new ModuleObject(qname);
131-
StoreAttribute(name, m);
132-
m.DecrRefCount();
133-
return m;
134-
}
135-
136-
type = AssemblyManager.LookupTypes(qname).FirstOrDefault(t => t.IsPublic);
137-
if (type != null)
138-
{
139-
c = ClassManager.GetClass(type);
140-
StoreAttribute(name, c);
141-
return c;
142-
}
143-
}
144-
145121
// We didn't find the name, so we may need to see if there is a
146122
// generic type with this base name. If so, we'll go ahead and
147123
// return it. Note that we store the mapping of the unmangled

src/tests/test_array.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
"""Test support for managed arrays."""
44

5+
import clr
56
import Python.Test as Test
67
import System
78
import pytest
@@ -1143,6 +1144,8 @@ def test_boxed_value_type_mutation_result():
11431144
# to accidentally write code like the following which is not really
11441145
# mutating value types in-place but changing boxed copies.
11451146

1147+
clr.AddReference('System.Drawing')
1148+
11461149
from System.Drawing import Point
11471150
from System import Array
11481151

src/tests/test_class.py

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""Test CLR class support."""
55

6+
import clr
67
import Python.Test as Test
78
import System
89
import pytest
@@ -124,6 +125,9 @@ def __init__(self, v):
124125

125126
def test_struct_construction():
126127
"""Test construction of structs."""
128+
129+
clr.AddReference('System.Drawing')
130+
127131
from System.Drawing import Point
128132

129133
p = Point()

src/tests/test_compat.py

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""Backward-compatibility tests for deprecated features."""
55

6+
import clr
67
import types
78

89
import pytest
@@ -137,6 +138,8 @@ def test_dotted_name_import_from_with_alias():
137138

138139
def test_from_module_import_star():
139140
"""Test from module import * behavior."""
141+
clr.AddReference('System.Management')
142+
140143
count = len(locals().keys())
141144
m = __import__('CLR.System.Management', globals(), locals(), ['*'])
142145
assert m.__name__ == 'System.Management'

src/tests/test_module.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ def test_dotted_name_import_from_with_alias():
192192

193193
def test_from_module_import_star():
194194
"""Test from module import * behavior."""
195+
clr.AddReference('System.Xml')
196+
195197
count = len(locals().keys())
196198
m = __import__('System.Xml', globals(), locals(), ['*'])
197199
assert m.__name__ == 'System.Xml'
@@ -201,16 +203,14 @@ def test_from_module_import_star():
201203

202204
def test_implicit_assembly_load():
203205
"""Test implicit assembly loading via import."""
204-
with warnings.catch_warnings(record=True) as w:
205-
warnings.simplefilter("always")
206+
with pytest.raises(ImportError):
207+
# MS.Build should not have been added as a reference yet
208+
# (and should exist for mono)
206209

207-
# should trigger a DeprecationWarning as Microsoft.Build hasn't
208-
# been added as a reference yet (and should exist for mono)
210+
# The implicit behavior has been disabled in 3.0
211+
# therefore this should fail
209212
import Microsoft.Build
210213

211-
assert len(w) == 1
212-
assert isinstance(w[0].message, DeprecationWarning)
213-
214214
with warnings.catch_warnings(record=True) as w:
215215
clr.AddReference("System.Windows.Forms")
216216
import System.Windows.Forms as Forms

0 commit comments

Comments
 (0)