X Tutup
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.IO; using System.Reflection; namespace Python.Runtime { /// /// Implements a Python type that provides access to CLR namespaces. The /// type behaves like a Python module, and can contain other sub-modules. /// [Serializable] internal class ModuleObject : ExtensionType { private readonly Dictionary cache = new(); internal string moduleName; internal PyDict dict; protected string _namespace; private readonly PyList __all__ = new (); // Attributes to be set on the module according to PEP302 and 451 // by the import machinery. static readonly HashSet settableAttributes = new () {"__spec__", "__file__", "__name__", "__path__", "__loader__", "__package__"}; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. /// is initialized in protected ModuleObject(string name) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { if (name == string.Empty) { throw new ArgumentException("Name must not be empty!"); } moduleName = name; _namespace = name; } internal static NewReference Create(string name) => new ModuleObject(name).Alloc(); public override NewReference Alloc() { var py = base.Alloc(); if (dict is null) { // Use the filename from any of the assemblies just so there's something for // anything that expects __file__ to be set. var filename = "unknown"; var docstring = "Namespace containing types from the following assemblies:\n\n"; foreach (Assembly a in AssemblyManager.GetAssemblies(moduleName)) { if (!a.IsDynamic && a.Location != null) { filename = a.Location; } docstring += "- " + a.FullName + "\n"; } using var dictRef = Runtime.PyObject_GenericGetDict(py.Borrow()); dict = new PyDict(dictRef.StealOrThrow()); using var pyname = Runtime.PyString_FromString(moduleName); using var pyfilename = Runtime.PyString_FromString(filename); using var pydocstring = Runtime.PyString_FromString(docstring); BorrowedReference pycls = TypeManager.GetTypeReference(GetType()); Runtime.PyDict_SetItem(dict, PyIdentifier.__name__, pyname.Borrow()); Runtime.PyDict_SetItem(dict, PyIdentifier.__file__, pyfilename.Borrow()); Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, pydocstring.Borrow()); Runtime.PyDict_SetItem(dict, PyIdentifier.__class__, pycls); } else { SetObjectDict(py.Borrow(), new NewReference(dict).Steal()); } InitializeModuleMembers(); return py; } /// /// Returns a ClassBase object representing a type that appears in /// this module's namespace or a ModuleObject representing a child /// namespace (or null if the name is not found). This method does /// not increment the Python refcount of the returned object. /// public NewReference GetAttribute(string name, bool guess) { cache.TryGetValue(name, out var cached); if (cached != null) { return new NewReference(cached); } Type type; //if (AssemblyManager.IsValidNamespace(name)) //{ // IntPtr py_mod_name = Runtime.PyString_FromString(name); // IntPtr modules = Runtime.PyImport_GetModuleDict(); // IntPtr module = Runtime.PyDict_GetItem(modules, py_mod_name); // if (module != IntPtr.Zero) // return (ManagedType)this; // return null; //} string qname = _namespace == string.Empty ? name : _namespace + "." + name; // If the fully-qualified name of the requested attribute is // a namespace exported by a currently loaded assembly, return // a new ModuleObject representing that namespace. if (AssemblyManager.IsValidNamespace(qname)) { var m = ModuleObject.Create(qname); this.StoreAttribute(name, m.Borrow()); return m; } // Look for a type in the current namespace. Note that this // includes types, delegates, enums, interfaces and structs. // Only public namespace members are exposed to Python. type = AssemblyManager.LookupTypes(qname).FirstOrDefault(t => t.IsPublic); if (type != null) { var c = ClassManager.GetClass(type); StoreAttribute(name, c); return new NewReference(c); } // We didn't find the name, so we may need to see if there is a // generic type with this base name. If so, we'll go ahead and // return it. Note that we store the mapping of the unmangled // name to generic type - it is technically possible that some // future assembly load could contribute a non-generic type to // the current namespace with the given basename, but unlikely // enough to complicate the implementation for now. if (guess) { string? gname = GenericUtil.GenericNameForBaseName(this._namespace, name); if (gname != null) { var o = this.GetAttribute(gname, false); if (!o.IsNull()) { this.StoreAttribute(name, o.Borrow()); return o; } } } return default; } /// /// Stores an attribute in the instance dict for future lookups. /// private void StoreAttribute(string name, BorrowedReference ob) { if (Runtime.PyDict_SetItemString(dict, name, ob) != 0) { throw PythonException.ThrowLastAsClrException(); } cache[name] = new PyObject(ob); } /// /// Preloads all currently-known names for the module namespace. This /// can be called multiple times, to add names from assemblies that /// may have been loaded since the last call to the method. /// public void LoadNames() { foreach (string name in AssemblyManager.GetNames(_namespace)) { cache.TryGetValue(name, out var m); if (m != null) { continue; } BorrowedReference attr = Runtime.PyDict_GetItemString(dict, name); // If __dict__ has already set a custom property, skip it. if (!attr.IsNull) { continue; } using var attrVal = GetAttribute(name, true); if (!attrVal.IsNull()) { // if it's a valid attribute, add it to __all__ using var pyname = Runtime.PyString_FromString(name); if (Runtime.PyList_Append(__all__, pyname.Borrow()) != 0) { throw PythonException.ThrowLastAsClrException(); } } } } const BindingFlags ModuleMethodFlags = BindingFlags.Public | BindingFlags.Static; /// /// Initialize module level functions and attributes /// internal void InitializeModuleMembers() { Type funcmarker = typeof(ModuleFunctionAttribute); Type propmarker = typeof(ModulePropertyAttribute); Type ftmarker = typeof(ForbidPythonThreadsAttribute); Type type = GetType(); while (type != null) { MethodInfo[] methods = type.GetMethods(ModuleMethodFlags); foreach (MethodInfo method in methods) { object[] attrs = method.GetCustomAttributes(funcmarker, false); object[] forbid = method.GetCustomAttributes(ftmarker, false); bool allow_threads = forbid.Length == 0; if (attrs.Length > 0) { string name = method.Name; var mi = new MethodInfo[1]; mi[0] = method; using var m = new ModuleFunctionObject(type, name, mi, allow_threads).Alloc(); StoreAttribute(name, m.Borrow()); } } PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo property in properties) { object[] attrs = property.GetCustomAttributes(propmarker, false); if (attrs.Length > 0) { string name = property.Name; using var p = new ModulePropertyObject(property).Alloc(); StoreAttribute(name, p.Borrow()); } } type = type.BaseType; } } internal void ResetModuleMembers() { Type type = GetType(); var methods = type.GetMethods(ModuleMethodFlags) .Where(m => m.GetCustomAttribute() is not null) .OfType(); var properties = type.GetProperties().Where(p => p.GetCustomAttribute() is not null); foreach (string memberName in methods.Concat(properties).Select(m => m.Name)) { if (Runtime.PyDict_DelItemString(dict, memberName) != 0) { if (!PythonException.CurrentMatches(Exceptions.KeyError)) { throw PythonException.ThrowLastAsClrException(); } Runtime.PyErr_Clear(); } cache.Remove(memberName); } } /// /// ModuleObject __getattribute__ implementation. Module attributes /// are always either classes or sub-modules representing subordinate /// namespaces. CLR modules implement a lazy pattern - the sub-modules /// and classes are created when accessed and cached for future use. /// public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key) { var self = (ModuleObject)GetManagedObject(ob)!; if (!Runtime.PyString_Check(key)) { Exceptions.SetError(Exceptions.TypeError, "string expected"); return default; } Debug.Assert(!self.dict.IsDisposed); BorrowedReference op = Runtime.PyDict_GetItem(self.dict, key); if (op != null) { return new NewReference(op); } string? name = InternString.GetManagedString(key); if (name == "__dict__") { return new NewReference(self.dict); } if (name == "__all__") { self.LoadNames(); return new NewReference(self.__all__); } NewReference attr; try { if (name is null) throw new ArgumentNullException(); attr = self.GetAttribute(name, true); } catch (Exception e) { Exceptions.SetError(e); return default; } if (attr.IsNull()) { Exceptions.SetError(Exceptions.AttributeError, name); return default; } return attr; } /// /// ModuleObject __repr__ implementation. /// public static NewReference tp_repr(BorrowedReference ob) { var self = (ModuleObject)GetManagedObject(ob)!; return Runtime.PyString_FromString($""); } public static int tp_traverse(BorrowedReference ob, IntPtr visit, IntPtr arg) { var self = (ModuleObject?)GetManagedObject(ob); if (self is null) return 0; Debug.Assert(self.dict == GetObjectDict(ob)); int res = PyVisit(self.dict, visit, arg); if (res != 0) return res; foreach (var attr in self.cache.Values) { res = PyVisit(attr, visit, arg); if (res != 0) return res; } return 0; } /// /// Override the setattr implementation. /// This is needed because the import mechanics need /// to set a few attributes /// [ForbidPythonThreads] public new static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val) { var managedKey = Runtime.GetManagedString(key); if ((settableAttributes.Contains(managedKey)) || (ManagedType.GetManagedObject(val) is ModuleObject) ) { var self = (ModuleObject)ManagedType.GetManagedObject(ob)!; return Runtime.PyDict_SetItem(self.dict, key, val); } return ExtensionType.tp_setattro(ob, key, val); } protected override Dictionary? OnSave(BorrowedReference ob) { var context = base.OnSave(ob); System.Diagnostics.Debug.Assert(dict == GetObjectDict(ob)); // destroy the cache(s) foreach (var pair in cache) { if ((Runtime.PyDict_DelItemString(dict, pair.Key) == -1) && (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(); } else if (Exceptions.ErrorOccurred()) { throw PythonException.ThrowLastAsClrException(); } } cache.Clear(); return context; } protected override void OnLoad(BorrowedReference ob, Dictionary? context) { base.OnLoad(ob, context); SetObjectDict(ob, new NewReference(dict).Steal()); } } /// /// The CLR module is the root handler used by the magic import hook /// to import assemblies. It has a fixed module name "clr" and doesn't /// provide a namespace. /// [Serializable] internal class CLRModule : ModuleObject { protected static bool interactive_preload = true; internal static bool preload; // XXX Test performance of new features // internal static bool _SuppressDocs = false; internal static bool _SuppressOverloads = false; static CLRModule() { Reset(); } private CLRModule() : base("clr") { _namespace = string.Empty; } internal static NewReference Create(out CLRModule module) { module = new CLRModule(); return module.Alloc(); } public static void Reset() { interactive_preload = true; preload = false; // XXX Test performance of new features // _SuppressDocs = false; _SuppressOverloads = false; } /// /// The initializing of the preload hook has to happen as late as /// possible since sys.ps1 is created after the CLR module is /// created. /// internal void InitializePreload() { if (interactive_preload) { interactive_preload = false; if (!Runtime.PySys_GetObject("ps1").IsNull) { preload = true; } else { Exceptions.Clear(); preload = false; } } } [ModuleFunction] public static bool getPreload() { return preload; } [ModuleFunction] public static void setPreload(bool preloadFlag) { preload = preloadFlag; } //[ModuleProperty] public static bool SuppressDocs { get { return _SuppressDocs; } set { _SuppressDocs = value; } } //[ModuleProperty] public static bool SuppressOverloads { get { return _SuppressOverloads; } set { _SuppressOverloads = value; } } [ModuleFunction] [ForbidPythonThreads] public static Assembly AddReference(string name) { AssemblyManager.UpdatePath(); var origNs = AssemblyManager.GetNamespaces(); Assembly? assembly = null; assembly = AssemblyManager.FindLoadedAssembly(name); if (assembly == null) { assembly = AssemblyManager.LoadAssemblyPath(name); } if (assembly == null && AssemblyManager.TryParseAssemblyName(name) is { } parsedName) { assembly = AssemblyManager.LoadAssembly(parsedName); } if (assembly == null) { assembly = AssemblyManager.LoadAssemblyFullPath(name); } if (assembly == null) { throw new FileNotFoundException($"Unable to find assembly '{name}'."); } // Classes that are not in a namespace needs an extra nudge to be found. ImportHook.UpdateCLRModuleDict(); // A bit heavyhanded, but we can't use the AssemblyManager's AssemblyLoadHandler // method because it may be called from other threads, leading to deadlocks // if it is called while Python code is executing. var currNs = AssemblyManager.GetNamespaces().Except(origNs); foreach(var ns in currNs) { ImportHook.AddNamespaceWithGIL(ns); } return assembly; } /// /// Get a Type instance for a class object. /// clr.GetClrType(IComparable) gives you the Type for IComparable, /// that you can e.g. perform reflection on. Similar to typeof(IComparable) in C# /// or clr.GetClrType(IComparable) in IronPython. /// /// /// /// The Type object [ModuleFunction] [ForbidPythonThreads] public static Type GetClrType(Type type) { return type; } [ModuleFunction] [ForbidPythonThreads] public static string FindAssembly(string name) { AssemblyManager.UpdatePath(); return AssemblyManager.FindAssembly(name); } [ModuleFunction] public static string[] ListAssemblies(bool verbose) { AssemblyName[] assnames = AssemblyManager.ListAssemblies(); var names = new string[assnames.Length]; for (var i = 0; i < assnames.Length; i++) { if (verbose) { names[i] = assnames[i].FullName; } else { names[i] = assnames[i].Name; } } return names; } /// /// Note: This should *not* be called directly. /// The function that get/import a CLR assembly as a python module. /// This function should only be called by the import machinery as seen /// in importhook.cs /// /// A ModuleSpec Python object /// A new reference to the imported module, as a PyObject. [ModuleFunction] [ForbidPythonThreads] public static PyObject _load_clr_module(PyObject spec) { using var modname = spec.GetAttr("name"); string name = modname.As() ?? throw new ArgumentException("name must not be None"); var mod = ImportHook.Import(name); return mod; } [ModuleFunction] [ForbidPythonThreads] public static int _add_pending_namespaces() => ImportHook.AddPendingNamespaces(); } }
X Tutup