-
Notifications
You must be signed in to change notification settings - Fork 772
Modernize import hook #1369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modernize import hook #1369
Changes from 1 commit
a321daa
279b535
f92e95b
afffc18
d821c0f
e469a8a
685b972
be81364
73958ed
e71a0ef
2af066d
bb490bf
31ea876
c02d5c6
970a189
059605b
ff170e9
63de923
624f7e3
bd7e745
46a85fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -19,7 +19,7 @@ import sys | |||
| class DotNetLoader(importlib.abc.Loader): | ||||
|
|
||||
| def __init__(self): | ||||
| super(DotNetLoader, self).__init__() | ||||
| super().__init__() | ||||
|
|
||||
| @classmethod | ||||
| def exec_module(klass, mod): | ||||
|
|
@@ -29,21 +29,19 @@ def exec_module(klass, mod): | |||
| @classmethod | ||||
| def create_module(klass, spec): | ||||
| import clr | ||||
| return clr._LoadClrModule(spec) | ||||
| return clr._load_clr_module(spec) | ||||
|
|
||||
| class DotNetFinder(importlib.abc.MetaPathFinder): | ||||
|
|
||||
| def __init__(self): | ||||
| super(DotNetFinder, self).__init__() | ||||
| super().__init__() | ||||
|
|
||||
| @classmethod | ||||
| def find_spec(klass, fullname, paths=None, target=None): | ||||
| import clr | ||||
| if (hasattr(clr, '_availableNamespaces') and fullname in clr._availableNamespaces): | ||||
| if clr._availableNamespaces and fullname in clr._availableNamespaces: | ||||
| return importlib.machinery.ModuleSpec(fullname, DotNetLoader(), is_package=True) | ||||
| return None | ||||
|
|
||||
| sys.meta_path.append(DotNetFinder()) | ||||
| "; | ||||
| const string availableNsKey = "_availableNamespaces"; | ||||
|
|
||||
|
|
@@ -69,7 +67,7 @@ internal static unsafe void Initialize() | |||
|
|
||||
| // Add/create the MetaPathLoader | ||||
| SetupNamespaceTracking(); | ||||
| PythonEngine.Exec(LoaderCode); | ||||
| SetupImportHook(); | ||||
| } | ||||
|
|
||||
|
|
||||
|
|
@@ -114,13 +112,66 @@ internal static void RestoreRuntimeData(RuntimeDataStorage storage) | |||
| SetupNamespaceTracking(); | ||||
| } | ||||
|
|
||||
| static void SetupImportHook() | ||||
| { | ||||
| // Create the import hook module | ||||
| var import_hook_module_def = ModuleDefOffset.AllocModuleDef("clr.loader"); | ||||
| var import_hook_module = Runtime.PyModule_Create2(import_hook_module_def, 3); | ||||
|
|
||||
| // Run the python code to create the module's classes. | ||||
| var mod_dict = Runtime.PyModule_GetDict(new BorrowedReference(import_hook_module)); | ||||
| var builtins = Runtime.PyEval_GetBuiltins(); | ||||
| var exec = Runtime.PyDict_GetItemString(builtins, "exec"); | ||||
| using var args = NewReference.DangerousFromPointer(Runtime.PyTuple_New(2)); | ||||
|
|
||||
| var codeStr = Runtime.PyString_FromString(LoaderCode); | ||||
| Runtime.PyTuple_SetItem(args.DangerousGetAddress(), 0, codeStr); | ||||
| // PyTuple_SetItem steals a reference, mod_dict is borrowed. | ||||
| Runtime.XIncref(mod_dict.DangerousGetAddress()); | ||||
| Runtime.PyTuple_SetItem(args.DangerousGetAddress(), 1, mod_dict.DangerousGetAddress()); | ||||
| Runtime.PyObject_Call(exec.DangerousGetAddress(), args.DangerousGetAddress(), IntPtr.Zero); | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is better to make an overload with the new reference types when possible without rewriting existing code, than to call pythonnet/src/runtime/runtime.cs Line 996 in 7d8f754
One can also be made for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This applies to a few Runtime methods across the PR. Basically anything where you can add a new overload. |
||||
|
|
||||
| var loader = Runtime.PyDict_GetItemString(mod_dict, "DotNetLoader").DangerousGetAddressOrNull(); | ||||
| Runtime.XIncref(loader); | ||||
|
|
||||
| // Add the classes to the module | ||||
| // PyModule_AddObject steals a reference only on success | ||||
| if (Runtime.PyModule_AddObject(import_hook_module, "DotNetLoader", loader) != 0) | ||||
| { | ||||
| Runtime.XDecref(loader); | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
||||
| var finder = Runtime.PyDict_GetItemString(mod_dict, "DotNetFinder").DangerousGetAddressOrNull(); | ||||
| Runtime.XIncref(finder); | ||||
| if (Runtime.PyModule_AddObject(import_hook_module, "DotNetFinder", finder) != 0) | ||||
| { | ||||
| Runtime.XDecref(finder); | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
||||
| // Set as a sub-module of clr. | ||||
| Runtime.XIncref(import_hook_module); | ||||
| if(Runtime.PyModule_AddObject(py_clr_module, "loader", import_hook_module) != 0) | ||||
| { | ||||
| Runtime.XDecref(import_hook_module); | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
||||
| // Finally, add the hook to the meta path | ||||
| var finder_inst = Runtime.PyDict_GetItemString(mod_dict, "finder_inst").DangerousGetAddressOrNull(); | ||||
| Runtime.XIncref(finder); | ||||
| var metapath = Runtime.PySys_GetObject("meta_path"); | ||||
| Runtime.PyList_Append(metapath, finder); | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These four lines look fishy: you don't use finder_inst and you are doing an incref on finder for no obvious reason.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh.. that should be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, it makes sense to add an overload for Same for This is a NIT though. Should come up with a better plan to switch everything. |
||||
| } | ||||
|
|
||||
| /// <summary> | ||||
| /// Sets up the tracking of loaded namespaces. This makes available to | ||||
| /// Python, as a Python object, the loaded namespaces. The set of loaded | ||||
| /// namespaces is used during the import to verify if we can import a | ||||
| /// CLR assembly as a module or not. The set is stored on the clr module. | ||||
| /// </summary> | ||||
| static void SetupNamespaceTracking () | ||||
| static void SetupNamespaceTracking() | ||||
| { | ||||
| var newset = Runtime.PySet_New(new BorrowedReference(IntPtr.Zero)); | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You Dispose in a finally; should this be a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: I'd replace |
||||
| try | ||||
|
|
@@ -130,7 +181,7 @@ static void SetupNamespaceTracking () | |||
| var pyNs = Runtime.PyString_FromString(ns); | ||||
| try | ||||
| { | ||||
| if(Runtime.PySet_Add(newset, new BorrowedReference(pyNs)) != 0) | ||||
| if (Runtime.PySet_Add(newset, new BorrowedReference(pyNs)) != 0) | ||||
| { | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
@@ -141,7 +192,7 @@ static void SetupNamespaceTracking () | |||
| } | ||||
| } | ||||
|
|
||||
| if(Runtime.PyDict_SetItemString(root.dict, availableNsKey, newset.DangerousGetAddress()) != 0) | ||||
| if (Runtime.PyDict_SetItemString(root.dict, availableNsKey, newset.DangerousGetAddress()) != 0) | ||||
| { | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
@@ -152,7 +203,7 @@ static void SetupNamespaceTracking () | |||
| } | ||||
|
|
||||
| AssemblyManager.namespaceAdded += OnNamespaceAdded; | ||||
| PythonEngine.AddShutdownHandler(()=>AssemblyManager.namespaceAdded -= OnNamespaceAdded); | ||||
| PythonEngine.AddShutdownHandler(() => AssemblyManager.namespaceAdded -= OnNamespaceAdded); | ||||
| } | ||||
|
|
||||
| /// <summary> | ||||
|
|
@@ -162,27 +213,21 @@ static void SetupNamespaceTracking () | |||
| static void TeardownNameSpaceTracking() | ||||
| { | ||||
| AssemblyManager.namespaceAdded -= OnNamespaceAdded; | ||||
| // If the C# runtime isn't loaded, then there is no namespaces available | ||||
| if ((Runtime.PyDict_DelItemString(new BorrowedReference(root.dict), availableNsKey) != 0) && | ||||
| (Exceptions.ExceptionMatches(Exceptions.KeyError))) | ||||
| { | ||||
| // Trying to remove a key that's not in the dictionary | ||||
| // raises an error. We don't care about it. | ||||
| Runtime.PyErr_Clear(); | ||||
| } | ||||
| // If the C# runtime isn't loaded, then there are no namespaces available | ||||
| Runtime.PyDict_SetItemString(root.dict, availableNsKey, Runtime.PyNone); | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not remove the key entirely? You would not need to compare it to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The DotNetFinder relies on that key being present on the object. If I remove it in AddNamespace, I need to add a check over there instead. |
||||
| } | ||||
|
|
||||
| static void OnNamespaceAdded (string name) | ||||
| static void OnNamespaceAdded(string name) | ||||
| { | ||||
| using(Py.GIL()) | ||||
| using (Py.GIL()) | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there scenarios when this method will be called without GIL acquired?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It used to be possible. The method calling it now has the attribute |
||||
| { | ||||
| var pyNs = Runtime.PyString_FromString(name); | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: |
||||
| try | ||||
| { | ||||
| var nsSet = Runtime.PyDict_GetItemString(new BorrowedReference(root.dict), availableNsKey); | ||||
| if (!nsSet.IsNull) | ||||
| if (!nsSet.IsNull || nsSet.DangerousGetAddress() != Runtime.PyNone) | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should BorrowedReference have an IsNullOrNone property? It seems likely to be a common idiom.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd keep separate |
||||
| { | ||||
| if(Runtime.PySet_Add(nsSet, new BorrowedReference(pyNs)) != 0) | ||||
| if (Runtime.PySet_Add(nsSet, new BorrowedReference(pyNs)) != 0) | ||||
| { | ||||
| throw new PythonException(); | ||||
| } | ||||
|
|
@@ -225,7 +270,7 @@ public static unsafe NewReference GetCLRModule() | |||
| /// <summary> | ||||
| /// The hook to import a CLR module into Python | ||||
| /// </summary> | ||||
| public static ModuleObject __import__(string modname) | ||||
| public static ModuleObject Import(string modname) | ||||
| { | ||||
| // Traverse the qualified module name to get the named module. | ||||
| // Note that if | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.