| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,7 @@ public static InteropConfiguration MakeDefault() | |||
| 22 | 22 | { | |
| 23 | 23 | DefaultBaseTypeProvider.Instance, | |
| 24 | 24 | new CollectionMixinsProvider(new Lazy<PyObject>(() => Py.Import("clr._extras.collections"))), | |
| 25 | + new DynamicObjectMixinsProvider(new Lazy<PyObject>(() => Py.Import("clr._extras.dlr"))), | ||
| 25 | 26 | }, | |
| 26 | 27 | }; | |
| 27 | 28 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,47 @@ | |||
| 1 | + using System; | ||
| 2 | + using System.Collections.Generic; | ||
| 3 | + using System.Dynamic; | ||
| 4 | + | ||
| 5 | + namespace Python.Runtime.Mixins; | ||
| 6 | + | ||
| 7 | + class DynamicObjectMixinsProvider : IPythonBaseTypeProvider, IDisposable | ||
| 8 | + { | ||
| 9 | + readonly Lazy<PyObject> mixinsModule; | ||
| 10 | + | ||
| 11 | + public DynamicObjectMixinsProvider(Lazy<PyObject> mixinsModule) => | ||
| 12 | + this.mixinsModule = mixinsModule ?? throw new ArgumentNullException(nameof(mixinsModule)); | ||
| 13 | + | ||
| 14 | + public PyObject Mixins => mixinsModule.Value; | ||
| 15 | + | ||
| 16 | + public IEnumerable<PyType> GetBaseTypes(Type type, IList<PyType> existingBases) | ||
| 17 | + { | ||
| 18 | + if (type is null) | ||
| 19 | + throw new ArgumentNullException(nameof(type)); | ||
| 20 | + | ||
| 21 | + if (existingBases is null) | ||
| 22 | + throw new ArgumentNullException(nameof(existingBases)); | ||
| 23 | + | ||
| 24 | + if (!typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type)) | ||
| 25 | + return existingBases; | ||
| 26 | + | ||
| 27 | + var newBases = new List<PyType>(existingBases) | ||
| 28 | + { | ||
| 29 | + new(Mixins.GetAttr("DynamicMetaObjectProviderMixin")) | ||
| 30 | + }; | ||
| 31 | + | ||
| 32 | + if (type.IsInterface && type.BaseType is null) | ||
| 33 | + { | ||
| 34 | + newBases.RemoveAll(@base => PythonReferenceComparer.Instance.Equals(@base, Runtime.PyBaseObjectType)); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + return newBases; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public void Dispose() | ||
| 41 | + { | ||
| 42 | + if (this.mixinsModule.IsValueCreated) | ||
| 43 | + { | ||
| 44 | + this.mixinsModule.Value.Dispose(); | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,18 @@ | |||
| 1 | + """ | ||
| 2 | + Implements helpers for Dynamic Language Runtime (DLR) types. | ||
| 3 | + """ | ||
| 4 | + | ||
| 5 | + class DynamicMetaObjectProviderMixin: | ||
| 6 | + def __dir__(self): | ||
| 7 | + names = set(super().__dir__()) | ||
| 8 | + | ||
| 9 | + get_dynamic_member_names = getattr(self, "GetDynamicMemberNames", None) | ||
| 10 | + if callable(get_dynamic_member_names): | ||
| 11 | + try: | ||
| 12 | + for name in get_dynamic_member_names(): | ||
| 13 | + if isinstance(name, str): | ||
| 14 | + names.add(name) | ||
| 15 | + except Exception: | ||
| 16 | + pass | ||
| 17 | + | ||
| 18 | + return list(sorted(names)) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -299,7 +299,7 @@ static void LoadSubmodule(BorrowedReference targetModuleDict, string fullName, s | |||
| 299 | 299 | ||
| 300 | 300 | static void LoadMixins(BorrowedReference targetModuleDict) | |
| 301 | 301 | { | |
| 302 | - foreach (string nested in new[] { "collections" }) | ||
| 302 | + foreach (string nested in new[] { "collections", "dlr" }) | ||
| 303 | 303 | { | |
| 304 | 304 | LoadSubmodule(targetModuleDict, | |
| 305 | 305 | fullName: "clr._extras." + nested, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,11 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.Collections.Generic; | |
| 3 | + using System.Dynamic; | ||
| 3 | 4 | using System.Linq; | |
| 4 | 5 | using System.Reflection; | |
| 5 | 6 | using System.Runtime.InteropServices; | |
| 6 | 7 | using System.Diagnostics; | |
| 8 | + | ||
| 7 | 9 | using Python.Runtime.Native; | |
| 8 | 10 | using Python.Runtime.StateSerialization; | |
| 9 | 11 | ||
@@ -37,10 +39,164 @@ internal class TypeManager | |||
| 37 | 39 | "tp_clear", | |
| 38 | 40 | }; | |
| 39 | 41 | ||
| 42 | + static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new(); | ||
| 43 | + | ||
| 44 | + static bool HasClrMember(object instance, string memberName) => | ||
| 45 | + instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance).Length > 0; | ||
| 46 | + | ||
| 47 | + static bool IsPythonSpecialAttributeName(string memberName) => | ||
| 48 | + memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__"); | ||
| 49 | + | ||
| 50 | + static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject) | ||
| 51 | + { | ||
| 52 | + if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic) | ||
| 53 | + { | ||
| 54 | + instance = co.inst; | ||
| 55 | + dynamicObject = coDynamic; | ||
| 56 | + return true; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + if (Converter.ToManaged(ob, typeof(IDynamicMetaObjectProvider), out object? managedDynamic, false) | ||
| 60 | + && managedDynamic is IDynamicMetaObjectProvider convertedDynamic) | ||
| 61 | + { | ||
| 62 | + instance = managedDynamic; | ||
| 63 | + dynamicObject = convertedDynamic; | ||
| 64 | + return true; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + if (Converter.ToManaged(ob, typeof(object), out object? managedInstance, false) | ||
| 68 | + && managedInstance is IDynamicMetaObjectProvider boxedDynamic) | ||
| 69 | + { | ||
| 70 | + instance = managedInstance; | ||
| 71 | + dynamicObject = boxedDynamic; | ||
| 72 | + return true; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + instance = null!; | ||
| 76 | + dynamicObject = null!; | ||
| 77 | + return false; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedReference key) | ||
| 81 | + { | ||
| 82 | + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); | ||
| 83 | + | ||
| 84 | + // The whole DLR machinery only makes sense with string keys and dynamic objects | ||
| 85 | + if (!isDynamic || !Runtime.PyString_Check(key)) | ||
| 86 | + { | ||
| 87 | + return Runtime.PyObject_GenericGetAttr(ob, key); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + string memberName = Runtime.GetManagedString(key)!; | ||
| 91 | + | ||
| 92 | + // Forward requests to GetDynamicMemberNames to the mixin implementation | ||
| 93 | + if (memberName == nameof(DynamicObjectMemberAccessor.GetDynamicMemberNames) | ||
| 94 | + && !HasClrMember(instance, memberName)) | ||
| 95 | + { | ||
| 96 | + using var pyMemberNames = new Func<IReadOnlyCollection<string>>( | ||
| 97 | + () => dynamicMemberAccessor.GetDynamicMemberNames(dynamicObject) | ||
| 98 | + ).ToPython(); | ||
| 99 | + return pyMemberNames.NewReferenceOrNull(); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + // Now, first try to access the Python attribute | ||
| 103 | + var attr = Runtime.PyObject_GenericGetAttr(ob, key); | ||
| 104 | + if (!attr.IsNull()) | ||
| 105 | + return attr; | ||
| 106 | + | ||
| 107 | + // attr is null, so an exception must be set. If that exception is not an AttributeError, | ||
| 108 | + // we return from this function immediately without clearing. All later returns until the | ||
| 109 | + // very end will lead to the AttributeError getting raised. | ||
| 110 | + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) | ||
| 111 | + { | ||
| 112 | + return default; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + if (HasClrMember(instance, memberName) || IsPythonSpecialAttributeName(memberName)) | ||
| 116 | + { | ||
| 117 | + return default; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + bool resolved = false; | ||
| 121 | + object? value = null; | ||
| 122 | + try | ||
| 123 | + { | ||
| 124 | + resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value); | ||
| 125 | + } | ||
| 126 | + catch | ||
| 127 | + { | ||
| 128 | + return default; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + if (!resolved) | ||
| 132 | + { | ||
| 133 | + return default; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + Runtime.PyErr_Clear(); | ||
| 137 | + | ||
| 138 | + using var pyValue = value.ToPython(); | ||
| 139 | + return pyValue.NewReferenceOrNull(); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference key, BorrowedReference val) | ||
| 143 | + { | ||
| 144 | + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); | ||
| 145 | + | ||
| 146 | + // The whole DLR machinery only makes sense with string keys and dynamic objects | ||
| 147 | + if (!isDynamic || !Runtime.PyString_Check(key)) | ||
| 148 | + { | ||
| 149 | + return Runtime.PyObject_GenericSetAttr(ob, key, val); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + string memberName = Runtime.GetManagedString(key)!; | ||
| 153 | + | ||
| 154 | + // For Python-derived types (IPythonDerivedType), the Python descriptor protocol | ||
| 155 | + // (e.g. @property setters) takes priority over DLR member storage. | ||
| 156 | + if (instance is IPythonDerivedType) | ||
| 157 | + { | ||
| 158 | + int pyResult = Runtime.PyObject_GenericSetAttr(ob, key, val); | ||
| 159 | + if (pyResult == 0) | ||
| 160 | + return 0; | ||
| 161 | + | ||
| 162 | + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) | ||
| 163 | + return pyResult; | ||
| 164 | + | ||
| 165 | + Runtime.PyErr_Clear(); | ||
| 166 | + // Fall through to DLR fallback below | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + if (!HasClrMember(instance, memberName) && !IsPythonSpecialAttributeName(memberName)) | ||
| 170 | + { | ||
| 171 | + // Try DLR member storage first | ||
| 172 | + bool handled = false; | ||
| 173 | + | ||
| 174 | + if (val == null) | ||
| 175 | + { | ||
| 176 | + handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); | ||
| 177 | + } | ||
| 178 | + else | ||
| 179 | + { | ||
| 180 | + object? managedValue = null; | ||
| 181 | + if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true)) | ||
| 182 | + return -1; | ||
| 183 | + | ||
| 184 | + handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue); | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + if (handled) | ||
| 188 | + return 0; | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + // Fall back to Python attribute setting | ||
| 192 | + return Runtime.PyObject_GenericSetAttr(ob, key, val); | ||
| 193 | + } | ||
| 194 | + | ||
| 40 | 195 | internal static void Initialize() | |
| 41 | 196 | { | |
| 42 | 197 | Debug.Assert(cache.Count == 0, "Cache should be empty", | |
| 43 | 198 | "Some errors may occurred on last shutdown"); | |
| 199 | + dynamicMemberAccessor.Clear(); | ||
| 44 | 200 | using (var plainType = SlotHelper.CreateObjectType()) | |
| 45 | 201 | { | |
| 46 | 202 | subtype_traverse = Util.ReadIntPtr(plainType.Borrow(), TypeOffset.tp_traverse); | |
@@ -64,6 +220,8 @@ internal static void RemoveTypes() | |||
| 64 | 220 | } | |
| 65 | 221 | } | |
| 66 | 222 | ||
| 223 | + dynamicMemberAccessor.Clear(); | ||
| 224 | + | ||
| 67 | 225 | foreach (var type in cache.Values) | |
| 68 | 226 | { | |
| 69 | 227 | type.Dispose(); | |
@@ -313,6 +471,13 @@ internal static void InitializeClass(PyType type, ClassBase impl, Type clrType) | |||
| 313 | 471 | throw PythonException.ThrowLastAsClrException(); | |
| 314 | 472 | } | |
| 315 | 473 | ||
| 474 | + if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType)) | ||
| 475 | + { | ||
| 476 | + InitializeSlot(type, TypeOffset.tp_getattro, new Interop.BB_N(tp_getattro_dlr_proxy), slotsHolder); | ||
| 477 | + InitializeSlot(type, TypeOffset.tp_setattro, new Interop.BBB_I32(tp_setattro_dlr_proxy), slotsHolder); | ||
| 478 | + Runtime.PyType_Modified(type.Reference); | ||
| 479 | + } | ||
| 480 | + | ||
| 316 | 481 | var dict = Util.ReadRef(type, TypeOffset.tp_dict); | |
| 317 | 482 | string mn = clrType.Namespace ?? ""; | |
| 318 | 483 | using (var mod = Runtime.PyString_FromString(mn)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.Collections.Generic; | |
| 3 | 3 | using System.ComponentModel; | |
| 4 | + using System.Dynamic; | ||
| 4 | 5 | using System.Diagnostics; | |
| 5 | 6 | using System.Linq; | |
| 6 | 7 | using System.Reflection; | |
@@ -232,6 +233,13 @@ internal static Type CreateDerivedType(string name, | |||
| 232 | 233 | continue; | |
| 233 | 234 | } | |
| 234 | 235 | ||
| 236 | + // Avoid re-entrant DLR binder recursion when Python derives from | ||
| 237 | + // DynamicObject-based types (including overrides in intermediate bases). | ||
| 238 | + if (IsDynamicObjectHookMethod(method)) | ||
| 239 | + { | ||
| 240 | + continue; | ||
| 241 | + } | ||
| 242 | + | ||
| 235 | 243 | // skip if this property has already been overridden | |
| 236 | 244 | if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) | |
| 237 | 245 | && pyProperties.Contains(method.Name.Substring(4))) | |
@@ -300,6 +308,35 @@ internal static Type CreateDerivedType(string name, | |||
| 300 | 308 | return type; | |
| 301 | 309 | } | |
| 302 | 310 | ||
| 311 | + static bool IsDynamicObjectHookMethod(MethodInfo method) | ||
| 312 | + { | ||
| 313 | + MethodInfo origin = method.GetBaseDefinition(); | ||
| 314 | + Type? originType = origin.DeclaringType; | ||
| 315 | + if (originType == typeof(DynamicObject)) | ||
| 316 | + { | ||
| 317 | + return origin.Name switch | ||
| 318 | + { | ||
| 319 | + nameof(DynamicObject.TryGetMember) | ||
| 320 | + or nameof(DynamicObject.TrySetMember) | ||
| 321 | + or nameof(DynamicObject.TryDeleteMember) | ||
| 322 | + or nameof(DynamicObject.TryInvokeMember) | ||
| 323 | + or nameof(DynamicObject.TryConvert) | ||
| 324 | + or nameof(DynamicObject.TryGetIndex) | ||
| 325 | + or nameof(DynamicObject.TrySetIndex) | ||
| 326 | + or nameof(DynamicObject.GetDynamicMemberNames) | ||
| 327 | + or nameof(IDynamicMetaObjectProvider.GetMetaObject) => true, | ||
| 328 | + _ => false, | ||
| 329 | + }; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + if (originType == typeof(IDynamicMetaObjectProvider)) | ||
| 333 | + { | ||
| 334 | + return origin.Name == nameof(IDynamicMetaObjectProvider.GetMetaObject); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + return false; | ||
| 338 | + } | ||
| 339 | + | ||
| 303 | 340 | /// <summary> | |
| 304 | 341 | /// Add a constructor override that calls the python ctor after calling the base type constructor. | |
| 305 | 342 | /// </summary> | |
| Back | FazBrowse Home | New Git URL |
0 commit comments