8000 Object identity, Interfaces and Attributes by rmadsen-ks · Pull Request #2019 · pythonnet/pythonnet · GitHub
[go: up one dir, main page]

Skip to content

Object identity, Interfaces and Attributes #2019

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/python_tests_runner/PythonTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class PythonTestRunner
[OneTimeSetUp]
public void SetUp()
{
Python.Runtime.Runtime.PythonDLL =
"C:\\Python37.2\\python37.dll";
Comment on lines +20 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up the history

PythonEngine.Initialize();
}

Expand All @@ -35,6 +37,13 @@ static IEnumerable<string[]> PythonTestCases()
// Add the test that you want to debug here.
yield return new[] { "test_indexer", "test_boolean_indexer" };
yield return new[] { "test_delegate", "test_bool_delegate" };
yield return new[] { "test_subclass", "test_virtual_generic_method" };
yield return new[] { "test_subclass", "test_interface_and_class_impl2" };
yield return new[] { "test_subclass", "test_class_with_attributes" };
yield return new[] { "test_subclass", "test_class_with_advanced_attribute" };
yield return new[] { "test_subclass", "test_more_subclasses" };
yield return new[] { "test_subclass", "test_more_subclasses2" };
yield return new[] { "test_subclass", "abstract_test" };
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/PythonTypes/PyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public partial class PyObject : DynamicObject, IDisposable, ISerializable
/// Trace stack for PyObject's construction
/// </summary>
public StackTrace Traceback { get; } = new StackTrace(1);
#endif
#endif

protected IntPtr rawPtr = IntPtr.Zero;
internal readonly int run = Runtime.GetRun();
Expand Down Expand Up @@ -165,7 +165,7 @@ public static PyObject FromManagedObject(object ob)
{
if (!Converter.ToManaged(obj, t, out var result, true))
{
throw new InvalidCastException("cannot convert object to target type",
throw new InvalidCastException($"Cannot convert object to target type '{t}'.",
PythonException.FetchCurrentOrNull(out _));
}
return result;
Expand Down Expand Up @@ -235,7 +235,7 @@ public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);

}

internal StolenReference Steal()
Expand Down Expand Up @@ -1325,7 +1325,7 @@ private bool TryCompare(PyObject arg, int op, out object @out)
}
return true;
}

public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
{
using var _ = Py.GIL();
Expand Down
27 changes: 27 additions & 0 deletions src/runtime/PythonTypes/PythonTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace Python.Runtime;

/// <summary>
/// Marks a property type with a specific python type. Normally, properties has .NET types, but if the property has a python type,
/// that cannot be represented in the propert type info, so this attribute is used to mark the property with the corresponding python type.
/// </summary>
public class PythonTypeAttribute : Attribute
{
/// <summary> Type name. </summary> E864
public string TypeName { get; }

/// <summary> Importable module name. </summary>
public string Module { get; }

/// <summary>
/// Creates a new instance of PythonTypeAttribute.
/// </summary>
/// <param name="pyTypeModule"></param>
/// <param name="pyTypeName"></param>
Comment on lines +17 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This XML doc does not have any useful information.

public PythonTypeAttribute(string pyTypeModule, string pyTypeName)
{
TypeName = pyTypeName;
Module = pyTypeModule;
}
}
127 changes: 121 additions & 6 deletions src/runtime/Resources/clr.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ def test(self):
string z = x.test; // calls into python and returns "x"
"""

def __init__(self, type_, fget=None, fset=None):
def __init__(self, type_, fget=None, fset=None, attributes = []):
self.__name__ = getattr(fget, "__name__", None)
self._clr_property_type_ = type_
self.fget = fget
self.fset = fset

self._clr_attributes_ = attributes
def __call__(self, fget):
return self.__class__(self._clr_property_type_,
self.__class__(self._clr_property_type_,
fget=fget,
fset=self.fset)
fset=self.fset,
attributes = self._clr_attributes_)


def setter(self, fset):
self.fset = fset
Expand All @@ -48,7 +50,69 @@ def __set__(self, instance, value):
raise AttributeError("%s is read-only" % self.__name__)
return self.fset.__get__(instance, None)(value)

# TODO: I am not sure this add_attribute is actually necessary.
def add_attribute(self, *args, **kwargs):
"""Adds an attribute to this class.
If the first argument is a tuple we assume it is a tuple containing everything to initialize the attribute.
Otherwise, the first arg should be a .net type implementing Attribute."""
lst = []
if len(args) > 0:
if isinstance(args[0], tuple):
lst = args
else:
lst = [(args[0], args[1:], kwargs)]
self._clr_attributes_.extend(lst)
return self

class property(object):
"""
property constructor for creating properties with implicit get/set.

It can be used as such:
e.g.::

class X(object):
A = clr.property(Double, 3.14)\
.add_attribute(Browsable(False))

"""
def __init__(self, type, default = None):
import weakref
self._clr_property_type_ = type
self.default = default
self.values = weakref.WeakKeyDictionary()
self._clr_attributes_ = []
self.fget = 1
self.fset = 1
def __get__(self, instance, owner):
if self.fget != 1:
return self.fget(instance)
v = self.values.get(instance, self.default)
return v
def __set__(self, instance, value):
if self.fset != 1:
self.fset(instance,value)
return
self.values[instance] = value

def add_attribute(self, *args, **kwargs):
"""Adds an attribute to this class.
If the first argument is a tuple we assume it is a tuple containing everything to initialize the attribute.
Otherwise, the first arg should be a .net type implementing Attribute."""
lst = []
if len(args) > 0:
if isinstance(args[0], tuple):
lst = args
else:
lst = [(args[0], args[1:], kwargs)]
self._clr_attributes_.extend(lst)
return self

def __call__(self, func):
self2 = self.__class__(self._clr_property_type_, None)
self2.fget = func
self2._clr_attributes_ = self._clr_attributes_
return self2
class clrmethod(object):
"""
Method decorator for exposing python methods to .NET.
Expand All @@ -67,18 +131,69 @@ def test(self, x):
int z = x.test("hello"); // calls into python and returns len("hello")
"""

def __init__(self, return_type, arg_types, clrname=None, func=None):
def __init__(self, return_type = None, arg_types = [], clrname=None, func=None, **kwargs):
if return_type == None:
import System
return_type = System.Void
self.__name__ = getattr(func, "__name__", None)
self._clr_return_type_ = return_type
self._clr_arg_types_ = arg_types
self._clr_method_name_ = clrname or self.__name__
self.__func = func
if 'attributes' in kwargs:
self._clr_attributes_ = kwargs["attributes"]
else:
self._clr_attributes_ = []

def __call__(self, func):
return self.__class__(self._clr_return_type_,
self2 = self.__class__(self._clr_return_type_,
self._clr_arg_types_,
clrname=self._clr_method_name_,
func=func)
self2._clr_attributes_ = self._clr_attributes_
return self2

def __get__(self, instance, owner):
return self.__func.__get__(instance, owner)

def add_attribute(self, *args, **kwargs):
"""Adds an attribute to this class.
If the first argument is a tuple we assume it is a tuple containing everything to initialize the attribute.
Otherwise, the first arg should be a .net type implementing Attribute."""
lst = []
if len(args) > 0:
if isinstance(args[0], tuple):
lst = args
else:
lst = [(args[0], args[1:], kwargs)]
self._clr_attributes_.extend(lst)
return self

class attribute(object):
"""
Class decorator for adding attributes to .net python classes.

e.g.::
@attribute(DisplayName("X Class"))
class X(object):
pass
"""
def __init__(self, *args, **kwargs):
lst = []
if len(args) > 0:
if isinstance(args[0], tuple):
lst = args
else:
lst = [(args[0], args[1:], kwargs)]
import Python.Runtime
#todo: ensure that attributes only are pushed when @ is used.
self.attr = lst
for item in lst:
Python.Runtime.PythonDerivedType.PushAttribute(item)

def __call__(self, x):
import Python.Runtime
for item in self.attr:
if Python.Runtime.PythonDerivedType.AssocAttribute(item, x):
pass
return x
6 changes: 6 additions & 0 deletions src/runtime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,12 @@ internal static void SetNoSiteFlag()
return *Delegates.Py_NoSiteFlag;
});
}

internal static uint PyTuple_GetSize(BorrowedReference tuple)
{
IntPtr r = Delegates.PyTuple_Size(tuple);
return (uint)r.ToInt32();
}
}

internal class BadPythonDllException : MissingMethodException
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/StateSerialization/MaybeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ internal struct MaybeType : ISerializable
const string SerializationName = "n";
readonly string name;
readonly Type type;

public string DeletedMessage
{
get
Expand All @@ -38,6 +37,7 @@ public Type Value

public string Name => name;
public bool Valid => type != null;
public Type ValueOrNull => type;

public override string ToString()
{
Expand All @@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext
serializationInfo.AddValue(SerializationName, name);
}
}
}
}
19 changes: 7 additions & 12 deletions src/runtime/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType)
return new PyTuple(bases);
}

internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef)
internal static NewReference CreateSubType(BorrowedReference py_name, IEnumerable<ClassBase> py_base_type, IEnumerable<Type> interfaces, BorrowedReference dictRef)
{
// Utility to create a subtype of a managed type with the ability for the
// a python subtype able to override the managed implementation
Expand Down Expand Up @@ -415,17 +415,12 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe
}

// create the new managed type subclassing the base managed type
if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass)
{
return ReflectedClrType.CreateSubclass(baseClass, name,
ns: (string?)namespaceStr,
assembly: (string?)assembly,
dict: dictRef);
}
else
{
return Exceptions.RaiseTypeError("invalid base class, expected CLR class type");
}
var baseClass = py_base_type.FirstOrDefault();

return ReflectedClrType.CreateSubclass(baseClass, interfaces, name,
ns: (string?)namespaceStr,
assembly: (string?)assembly,
dict: dictRef);
}

internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc)
Expand Down
Loading
0