Skip to content
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

Fix type instance conversion #92

Merged
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
25 changes: 25 additions & 0 deletions src/embed_tests/TestConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,27 @@ public void PrimitiveIntConversion()
var testInt = pyValue.As<int>();
Assert.AreEqual(testInt , 10);
}

[TestCase(typeof(Type), true)]
[TestCase(typeof(string), false)]
[TestCase(typeof(TestCSharpModel), false)]
public void NoErrorSetWhenFailingToConvertClassType(Type type, bool shouldConvert)
{
using var _ = Py.GIL();

var module = PyModule.FromString("CallsCorrectOverloadWithoutErrors", @"
from clr import AddReference
AddReference(""System"")
AddReference(""Python.EmbeddingTest"")
from Python.EmbeddingTest import *

class TestPythonModel(TestCSharpModel):
pass
");
var testPythonModelClass = module.GetAttr("TestPythonModel");
Assert.AreEqual(shouldConvert, Converter.ToManaged(testPythonModelClass, type, out var result, setError: false));
Assert.IsFalse(Exceptions.ErrorOccurred());
}
}

public interface IGetList
Expand All @@ -461,4 +482,8 @@ public class GetListImpl : IGetList
{
public List<string> GetList() => new() { "testing" };
}

public class TestCSharpModel
{
}
}
158 changes: 158 additions & 0 deletions src/embed_tests/TestMethodBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,164 @@ from Python.EmbeddingTest import *
"));
}

public class OverloadsTestClass
{

public string Method1(string positionalArg, decimal namedArg1 = 1.2m, int namedArg2 = 123)
{
Console.WriteLine("1");
return "Method1 Overload 1";
}

public string Method1(decimal namedArg1 = 1.2m, int namedArg2 = 123)
{
Console.WriteLine("2");
return "Method1 Overload 2";
}

// ----

public string Method2(string arg1, int arg2, decimal arg3, decimal kwarg1 = 1.1m, bool kwarg2 = false, string kwarg3 = "")
{
return "Method2 Overload 1";
}

public string Method2(string arg1, int arg2, decimal kwarg1 = 1.1m, bool kwarg2 = false, string kwarg3 = "")
{
return "Method2 Overload 2";
}

// ----

public string Method3(string arg1, int arg2, float arg3, float kwarg1 = 1.1f, bool kwarg2 = false, string kwarg3 = "")
{
return "Method3 Overload 1";
}

public string Method3(string arg1, int arg2, float kwarg1 = 1.1f, bool kwarg2 = false, string kwarg3 = "")
{
return "Method3 Overload 2";
}

// ----

public string ImplicitConversionSameArgumentCount(string symbol, int quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount 1";
}

public string ImplicitConversionSameArgumentCount(string symbol, decimal quantity, decimal trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount 2";
}

public string ImplicitConversionSameArgumentCount2(string symbol, int quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 1";
}

public string ImplicitConversionSameArgumentCount2(string symbol, float quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 2";
}

public string ImplicitConversionSameArgumentCount2(string symbol, decimal quantity, float trailingAmount, bool trailingAsPercentage, string tag = "")
{
return "ImplicitConversionSameArgumentCount2 2";
}
}

[TestCase("Method1('abc', namedArg1=10, namedArg2=321)", "Method1 Overload 1")]
[TestCase("Method1('abc', namedArg1=12.34, namedArg2=321)", "Method1 Overload 1")]
[TestCase("Method2(\"SPY\", 10, 123, kwarg1=1, kwarg2=True)", "Method2 Overload 1")]
[TestCase("Method2(\"SPY\", 10, 123.34, kwarg1=1.23, kwarg2=True)", "Method2 Overload 1")]
[TestCase("Method3(\"SPY\", 10, 123.34, kwarg1=1.23, kwarg2=True)", "Method3 Overload 1")]
public void SelectsRightOverloadWithNamedParameters(string methodCallCode, string expectedResult)
{
using var _ = Py.GIL();

dynamic module = PyModule.FromString("SelectsRightOverloadWithNamedParameters", @$"

def call_method(instance):
return instance.{methodCallCode}
");

var instance = new OverloadsTestClass();
var result = module.call_method(instance).As<string>();

Assert.AreEqual(expectedResult, result);
}

[TestCase("ImplicitConversionSameArgumentCount", "10", "ImplicitConversionSameArgumentCount 1")]
[TestCase("ImplicitConversionSameArgumentCount", "10.1", "ImplicitConversionSameArgumentCount 2")]
[TestCase("ImplicitConversionSameArgumentCount2", "10", "ImplicitConversionSameArgumentCount2 1")]
[TestCase("ImplicitConversionSameArgumentCount2", "10.1", "ImplicitConversionSameArgumentCount2 2")]
public void DisambiguatesOverloadWithSameArgumentCountAndImplicitConversion(string methodName, string quantity, string expectedResult)
{
using var _ = Py.GIL();

dynamic module = PyModule.FromString("DisambiguatesOverloadWithSameArgumentCountAndImplicitConversion", @$"
def call_method(instance):
return instance.{methodName}(""SPY"", {quantity}, 123.4, trailingAsPercentage=True)
");

var instance = new OverloadsTestClass();
var result = module.call_method(instance).As<string>();

Assert.AreEqual(expectedResult, result);
}

public class CSharpClass
{
public string CalledMethodMessage { get; private set; }

public void Method()
{
CalledMethodMessage = "Overload 1";
}

public void Method(string stringArgument, decimal decimalArgument = 1.2m)
{
CalledMethodMessage = "Overload 2";
}

public void Method(PyObject typeArgument, decimal decimalArgument = 1.2m)
{
CalledMethodMessage = "Overload 3";
}
}

[Test]
public void CallsCorrectOverloadWithoutErrors()
{
using var _ = Py.GIL();

var module = PyModule.FromString("CallsCorrectOverloadWithoutErrors", @"
from clr import AddReference
AddReference(""System"")
AddReference(""Python.EmbeddingTest"")
from Python.EmbeddingTest import *

class PythonModel(TestMethodBinder.CSharpModel):
pass

def call_method(instance):
instance.Method(PythonModel, decimalArgument=1.234)
");

var instance = new CSharpClass();
using var pyInstance = instance.ToPython();

Assert.DoesNotThrow(() =>
{
module.GetAttr("call_method").Invoke(pyInstance);
});

Assert.AreEqual("Overload 3", instance.CalledMethodMessage);

Assert.IsFalse(Exceptions.ErrorOccurred());
}


// Used to test that we match this function with Py DateTime & Date Objects
public static int GetMonth(DateTime test)
Expand Down
4 changes: 2 additions & 2 deletions src/perf_tests/Python.PerformanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="quantconnect.pythonnet" Version="2.0.36" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.37" GeneratePathProperty="true">
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup>
Expand All @@ -25,7 +25,7 @@
</Target>

<Target Name="CopyBaseline" AfterTargets="Build">
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.36\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.37\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
23 changes: 21 additions & 2 deletions src/runtime/Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,13 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
}
if (mt is ClassBase cb)
{
if (!cb.type.Valid || !obType.IsInstanceOfType(cb.type.Value))
// The value being converted is a class type, so it will only succeed if it's being converted into a Type
if (obType != typeof(Type))
{
return false;
}

if (!cb.type.Valid)
{
Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage);
return false;
Expand Down Expand Up @@ -845,8 +851,21 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
}

case TypeCode.Boolean:
result = Runtime.PyObject_IsTrue(value) != 0;
if (value == Runtime.PyTrue)
{
result = true;
return true;
}
if (value == Runtime.PyFalse)
{
result = false;
return true;
}
if (setError)
{
goto type_error;
}
return false;

case TypeCode.Byte:
{
Expand Down
Loading
Loading