Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Prev Previous commit
Next Next commit
Add unit test
  • Loading branch information
joaompneves committed Jul 18, 2024
commit 37c4d049a3cf81cfa5c14a0bcf19b363a14e3519
4 changes: 2 additions & 2 deletions src/embed_tests/CodecGroups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public void Encodes()
encoder2,
};

var uri = group.TryEncode(new Uri("data:"), typeof(Uri));
var uri = group.TryEncode(new Uri("data:"));
var clrObject = (CLRObject)ManagedType.GetManagedObject(uri);
Assert.AreSame(encoder1, clrObject.inst);
Assert.AreNotSame(encoder2, clrObject.inst);

var tuple = group.TryEncode(Tuple.Create(1), typeof(Tuple<int>));
var tuple = group.TryEncode(Tuple.Create(1));
clrObject = (CLRObject)ManagedType.GetManagedObject(tuple);
Assert.AreSame(encoder0, clrObject.inst);
}
Expand Down
40 changes: 36 additions & 4 deletions src/embed_tests/Codecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void TupleRoundtripObject()
static void TupleRoundtripObject<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object());
using var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple, typeof(T));
using var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
Expand All @@ -85,7 +85,7 @@ public void TupleRoundtripGeneric()
static void TupleRoundtripGeneric<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
using var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple, typeof(T));
using var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
Expand Down Expand Up @@ -339,6 +339,26 @@ public void ExceptionDecoded()
Assert.AreEqual(TestExceptionMessage, error.Message);
}

[Test]
public void ExplicitObjectInterfaceEncoded()
{
var obj = new ExplicitInterfaceObject();

// explicitly pass an interface (but not a generic type argument) to simulate a scenario where the type is not know at build time
// var encoder = new InterfaceEncoder(typeof(IObjectInterface));
// PyObjectConversions.RegisterEncoder(encoder);

using var scope = Py.CreateScope();
scope.Exec(@"
def call(obj):
return dir(obj)
");
var callFunc = scope.Get("call");
var members = callFunc.Invoke(obj.ToPythonAs(typeof(IObjectInterface))).As<string[]>();
CollectionAssert.Contains(members, nameof(IObjectInterface.MemberFromInterface));
CollectionAssert.DoesNotContain(members, nameof(ExplicitInterfaceObject.MemberFromObject));
}

[Test]
public void DateTimeDecoded()
{
Expand Down Expand Up @@ -438,7 +458,7 @@ public bool TryDecode<T>(PyObject pyObj, out T value)
return true;
}

public PyObject TryEncode(object value, Type type)
public PyObject TryEncode(object value)
{
var error = (ValueErrorWrapper)value;
return PythonEngine.Eval("ValueError").Invoke(error.Message.ToPython());
Expand Down Expand Up @@ -478,7 +498,7 @@ public bool TryDecode<T>(PyObject pyObj, out T value)
class ObjectToEncoderInstanceEncoder<T> : IPyObjectEncoder
{
public bool CanEncode(Type type) => type == typeof(T);
public PyObject TryEncode(object value, Type type) => PyObject.FromManagedObject(this);
public PyObject TryEncode(object value) => PyObject.FromManagedObject(this);
}

/// <summary>
Expand Down Expand Up @@ -533,4 +553,16 @@ public bool TryDecode<T>(PyObject pyObj, out T value)
return true;
}
}

interface IObjectInterface
{
int MemberFromInterface { get; }
}

class ExplicitInterfaceObject : IObjectInterface
{
int IObjectInterface.MemberFromInterface { get; }

public int MemberFromObject { get; }
}
}
1 change: 1 addition & 0 deletions src/embed_tests/Python.EmbeddingTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<RollForward>LatestMajor</RollForward>
Copy link
Author

Choose a reason for hiding this comment

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

This allows anyone that has recent versions of .net to run these tests. I can revert but I think its usefull, otherwise one needs to install .net6 which is already deprecated

Copy link
Member

Choose a reason for hiding this comment

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

I think this should be conditioned on not running in CI

Copy link
Author

Choose a reason for hiding this comment

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

Is there any config/env var which I can check?

<AssemblyOriginatorKeyFile>..\pythonnet.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Codecs/EncoderGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ public void Add(IPyObjectEncoder item)
/// <inheritdoc />
public bool CanEncode(Type type) => this.encoders.Any(encoder => encoder.CanEncode(type));
/// <inheritdoc />
public PyObject? TryEncode(object value, Type type)
public PyObject? TryEncode(object value)
{
if (value is null) throw new ArgumentNullException(nameof(value));

foreach (var encoder in this.GetEncoders(value.GetType()))
{
var result = encoder.TryEncode(value, type);
var result = encoder.TryEncode(value);
if (result != null)
{
return result;
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/Codecs/EnumPyIntCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public bool TryDecode<T>(PyObject pyObj, out T? value)
return false;
}

public PyObject? TryEncode(object value, Type type)
public PyObject? TryEncode(object value)
{
if (value is null) return null;
if (!type.IsEnum) return null;

if (!value.GetType().IsEnum) return null;

try
{
Expand Down
3 changes: 1 addition & 2 deletions src/runtime/Codecs/IPyObjectEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public interface IPyObjectEncoder
bool CanEncode(Type type);
/// <summary>
/// Attempts to encode CLR object <paramref name="value"/> into Python object
/// using the specified <paramref name="type"/>
/// </summary>
PyObject? TryEncode(object value, Type type);
PyObject? TryEncode(object value);
}
2 changes: 1 addition & 1 deletion src/runtime/Codecs/PyObjectConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static void RegisterDecoder(IPyObjectDecoder decoder)

foreach (var encoder in clrToPython.GetOrAdd(type, GetEncoders))
{
var result = encoder.TryEncode(obj, type);
var result = encoder.TryEncode(obj);
if (result != null) return result;
}

Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Codecs/RawProxyEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Python.Runtime.Codecs
/// </summary>
public class RawProxyEncoder: IPyObjectEncoder
{
public PyObject TryEncode(object value, Type type)
public PyObject TryEncode(object value)
{
if (value is null) throw new ArgumentNullException(nameof(value));

return PyObject.FromManagedObject(value);
return PyObject.FromManagedObject(value, value.GetType());
}

public virtual bool CanEncode(Type type) => false;
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/Codecs/TupleCodecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ public bool CanEncode(Type type)
&& type.Name.StartsWith(typeof(TTuple).Name + '`');
}

public PyObject? TryEncode(object value, Type type)
public PyObject? TryEncode(object value)
{
if (value == null) return null;

var type = value.GetType();
if (type == typeof(object)) return null;
if (!this.CanEncode(type)) return null;
if (type == typeof(TTuple)) return new PyTuple();
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 @@ -136,14 +136,14 @@ public IntPtr Handle
/// Given an arbitrary managed object, return a Python instance that
/// reflects the managed object.
/// </remarks>
public static PyObject FromManagedObject(object ob)
public static PyObject FromManagedObject(object ob, Type? type = null)
{
Copy link
Member

Choose a reason for hiding this comment

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

We need a use case and a test for this change as well.

Also, you have to add a separate overload because AFAIK adding default arguments breaks binary compatibility (e.g. libs compiled prior to this change will fail with MissingMethodException when they'll try to call the original method)

// Special case: if ob is null, we return None.
if (ob == null)
{
return new PyObject(Runtime.PyNone);
}
return CLRObject.GetReference(ob).MoveToPyObject();
return CLRObject.GetReference(ob, type ?? ob.GetType()).MoveToPyObject();
}

/// <summary>
Expand Down Expand Up @@ -235,7 +235,7 @@ public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);

}

internal StolenReference Steal()
Expand Down