X Tutup
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; 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()); } } }
X Tutup