Skip to content
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
230 changes: 230 additions & 0 deletions src/embed_tests/ClassManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,236 @@ def contains(dictionary, key):
Assert.IsFalse(result);
}

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

var module = PyModule.FromString("SupportsLenOperatorForIEnumerableWithCountProperty", $@"
from clr import AddReference
AddReference(""Python.EmbeddingTest"")

from Python.EmbeddingTest import *

def length(enumerable):
return len(enumerable)
");

using var length = module.GetAttr("length");

Assert.Multiple(() =>
{
var enumerableWithCount = new EnumerableWithCount();
using var pyEnumerableWithCount = enumerableWithCount.ToPython();
var count = length.Invoke(pyEnumerableWithCount).As<int>();
Assert.AreEqual(enumerableWithCount.Count, count);

var genericEnumerableWithCount = new GenericEnumerableWithCount();
using var pyGenericEnumerableWithCount = genericEnumerableWithCount.ToPython();
count = length.Invoke(pyGenericEnumerableWithCount).As<int>();
Assert.AreEqual(genericEnumerableWithCount.Count, count);

var derivedEnumerableWithCount = new DerivedEnumerableWithCount();
using var pyDerivedEnumerableWithCount = derivedEnumerableWithCount.ToPython();
count = length.Invoke(pyDerivedEnumerableWithCount).As<int>();
Assert.AreEqual(derivedEnumerableWithCount.Count, count);
});
}

private class EnumerableWithCount : IEnumerable
{
public int Count => 123;
public IEnumerator GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return i;
}
}
}

private class GenericEnumerableWithCount : IEnumerable<int>
{
public int Count => 123;

public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return i;
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

private class DerivedEnumerableWithCount : GenericEnumerableWithCount
{
}

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

var module = PyModule.FromString("SupportsLenOperatorForICollection", $@"
from clr import AddReference
AddReference(""Python.EmbeddingTest"")

from Python.EmbeddingTest import *

def length(enumerable):
return len(enumerable)
");

using var length = module.GetAttr("length");

Assert.Multiple(() =>
{
var collection = new BasicCollection();
using var pyCollection = collection.ToPython();
var count = length.Invoke(pyCollection).As<int>();
Assert.AreEqual(collection.Count, count);

var genericCollection = new GenericCollection();
using var pyGenericCollection = genericCollection.ToPython();
count = length.Invoke(pyGenericCollection).As<int>();
Assert.AreEqual(genericCollection.Count, count);

var collectionWithExplicitInterfaceImplementation = new CollectionWithExplicitInterfaceImplementation();
using var pyCollectionWithExplicitInterfaceImplementation = collectionWithExplicitInterfaceImplementation.ToPython();
count = length.Invoke(pyCollectionWithExplicitInterfaceImplementation).As<int>();
Assert.AreEqual(((ICollection<int>)collectionWithExplicitInterfaceImplementation).Count, count);
});
}

private class BasicCollection : ICollection
{
public int Count => 123;
public bool IsSynchronized => false;
public object SyncRoot => this;
public void CopyTo(Array array, int index)
{
for (int i = 0; i < Count; i++)
{
array.SetValue(i, index + i);
}
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return i;
}
}
}

private class GenericCollection : ICollection<int>
{
public int Count => 123;
public bool IsSynchronized => false;
public object SyncRoot => this;

public bool IsReadOnly => throw new NotImplementedException();

public void Add(int item)
{
throw new NotImplementedException();
}

public void Clear()
{
throw new NotImplementedException();
}

public bool Contains(int item)
{
throw new NotImplementedException();
}

public void CopyTo(int[] array, int index)
{
for (int i = 0; i < Count; i++)
{
array[index + i] = i;
}
}
public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return i;
}
}

public bool Remove(int item)
{
throw new NotImplementedException();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

private class CollectionWithExplicitInterfaceImplementation : ICollection<int>
{
public bool IsSynchronized => false;
public object SyncRoot => this;

int ICollection<int>.Count => 123;

bool ICollection<int>.IsReadOnly => true;

void ICollection<int>.CopyTo(int[] array, int index)
{
for (int i = 0; i < ((ICollection<int>)this).Count; i++)
{
array[index + i] = i;
}
}
public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < ((ICollection<int>)this).Count; i++)
{
yield return i;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

void ICollection<int>.Add(int item)
{
throw new NotImplementedException();
}

void ICollection<int>.Clear()
{
throw new NotImplementedException();
}

bool ICollection<int>.Contains(int item)
{
throw new NotImplementedException();
}

bool ICollection<int>.Remove(int item)
{
throw new NotImplementedException();
}

IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
throw new NotImplementedException();
}
}

public class TestDictionary<TKey, TValue> : IDictionary
{
private readonly Dictionary<TKey, TValue> _data = new();
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.53" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.54" 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.53\lib\net10.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.54\lib\net10.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]

[assembly: AssemblyVersion("2.0.53")]
[assembly: AssemblyFileVersion("2.0.53")]
[assembly: AssemblyVersion("2.0.54")]
[assembly: AssemblyFileVersion("2.0.54")]
2 changes: 1 addition & 1 deletion src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<RootNamespace>Python.Runtime</RootNamespace>
<AssemblyName>Python.Runtime</AssemblyName>
<PackageId>QuantConnect.pythonnet</PackageId>
<Version>2.0.53</Version>
<Version>2.0.54</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>
Expand Down
47 changes: 28 additions & 19 deletions src/runtime/Types/MpLengthSlot.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace Python.Runtime.Slots
{
internal static class MpLengthSlot
{
private static Dictionary<Type, MethodInfo> _countGettersCache = new();

public static bool CanAssign(Type clrType)
{
if (typeof(ICollection).IsAssignableFrom(clrType))
if (typeof(IEnumerable).IsAssignableFrom(clrType) && TryGetCountGetter(clrType, clrType, out _))
{
return true;
}
if (clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)))
{
return true;
}
if (clrType.IsInterface && clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(ICollection<>))

var iface = clrType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
if (iface != null)
{
// Get and cache the Count getter for this type and interface
TryGetCountGetter(clrType, iface, out _);
return true;
}

return false;
}

Expand All @@ -46,24 +48,31 @@ internal static nint impl(BorrowedReference ob)
}

Type clrType = co.inst.GetType();

// now look for things that implement ICollection<T> directly (non-explicitly)
PropertyInfo p = clrType.GetProperty("Count");
if (p != null && clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)))
if (TryGetCountGetter(clrType, clrType, out var getter))
{
return (int)p.GetValue(co.inst, null);
return (int)getter.Invoke(co.inst, null);
}

// finally look for things that implement the interface explicitly
var iface = clrType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
if (iface != null)
Exceptions.SetError(Exceptions.TypeError, $"object of type '{clrType.Name}' has no len()");
return -1;
}

/// <summary>
/// Will get the Count getter for the given parent type and cache it for the given clr type.
/// This allows us to cache the Count getter for the give type when it's defined as a private interface implementation.
/// </summary>
private static bool TryGetCountGetter(Type clrType, Type parentType, out MethodInfo getter)
{
if (!_countGettersCache.TryGetValue(clrType, out getter))
{
p = iface.GetProperty(nameof(ICollection<int>.Count));
return (int)p.GetValue(co.inst, null);
var countProperty = parentType.GetProperty("Count");
if (countProperty != null)
{
_countGettersCache[clrType] = getter = countProperty.GetMethod;
}
}

Exceptions.SetError(Exceptions.TypeError, $"object of type '{clrType.Name}' has no len()");
return -1;
return getter != null;
}
}
}
Loading