8000 Add container methods to `IDictionary` by jhonabreul · Pull Request #99 · QuantConnect/pythonnet · GitHub
[go: up one dir, main page]

Skip to content

Add container methods to IDictionary #99

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

Merged
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
159 changes: 159 additions & 0 deletions src/embed_tests/ClassManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -1083,6 +1084,164 @@ def is_enum_value_defined():
Assert.Throws<PythonException>(() => module.InvokeMethod("is_enum_value_defined"));
}
}

private static TestCaseData[] IDictionaryContainsTestCases =>
[
new(typeof(TestDictionary<string, string>)),
new(typeof(Dictionary<string, string>)),
new(typeof(TestKeyValueContainer<string, string>)),
new(typeof(DynamicClassDictionary<string, string>)),
];

[TestCaseSource(nameof(IDictionaryContainsTestCases))]
public void IDictionaryContainsMethodIsBound(Type dictType)
{
using var _ = Py.GIL();

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

from Python.EmbeddingTest import *

def contains(dictionary, key):
return key in dictionary
");

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

var dictionary = Convert.ChangeType(Activator.CreateInstance(dictType), dictType);
var key1 = "key1";
(dictionary as dynamic).Add(key1, "value1");

using var pyDictionary = dictionary.ToPython();
using var pyKey1 = key1.ToPython();

var result = contains.Invoke(pyDictionary, pyKey1).As<bool>();
Assert.IsTrue(result);

using var pyKey2 = "key2".ToPython();
result = contains.Invoke(pyDictionary, pyKey2).As<bool>();
Assert.IsFalse(result);
}

[TestCaseSource(nameof(IDictionaryContainsTestCases))]
public void CanCheckIfNoneIsInDictionary(Type dictType)
{
using var _ = Py.GIL();

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

from Python.EmbeddingTest import *

def contains(dictionary, key):
return key in dictionary
");

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

var dictionary = Convert.ChangeType(Activator.CreateInstance(dictType), dictType);
(dictionary as dynamic).Add("key1", "value1");

using var pyDictionary = dictionary.ToPython();

var result = false;
Assert.DoesNotThrow(() => result = contains.Invoke(pyDictionary, PyObject.None).As<bool>());
Assert.IsFalse(result);
}

public class TestDictionary<TKey, TValue> : IDictionary
{
private readonly Dictionary<TKey, TValue> _data = new();

public object this[object key] { get => ((IDictionary)_data)[key]; set => ((IDictionary)_data)[key] = value; }

public bool IsFixedSize => ((IDictionary)_data).IsFixedSize;

public bool IsReadOnly => ((IDictionary)_data).IsReadOnly;

public ICollection Keys => ((IDictionary)_data).Keys;

public ICollection Values => ((IDictionary)_data).Values;

public int Count => ((ICollection)_data).Count;

public bool IsSynchronized => ((ICollection)_data).IsSynchronized;

public object SyncRoot => ((ICollection)_data).SyncRoot;

public void Add(object key, object value)
{
((IDictionary)_data).Add(key, value);
}

public void Clear()
{
((IDictionary)_data).Clear();
}

public bool Contains(object key)
{
return ((IDictionary)_data).Contains(key);
}

public void CopyTo(Array array, int index)
{
((ICollection)_data).CopyTo(array, index);
}

public IDictionaryEnumerator GetEnumerator()
{
return ((IDictionary)_data).GetEnumerator();
}

public void Remove(object key)
{
((IDictionary)_data).Remove(key);
}

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

public bool ContainsKey(TKey key)
{
return Contains(key);
}
}

public class TestKeyValueContainer<TKey, TValue>
where TKey: class
where TValue: class
{
private readonly Dictionary<TKey, TValue> _data = new();
public int Count => _data.Count;
public bool ContainsKey(TKey key)
{
return _data.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
_data.Add(key, value);
}
}

public class DynamicClassDictionary<TKey, TValue> : TestPropertyAccess.DynamicFixture
{
private readonly Dictionary<TKey, TValue> _data = new();
public int Count => _data.Count;
public bool ContainsKey(TKey key)
{
return _data.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
_data.Add(key, value);
}
}
}

public class NestedTestParent
Expand Down
14 changes: 13 additions & 1 deletion src/runtime/ClassManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,19 @@ internal static ClassBase CreateClass(Type type)

else if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type))
{
impl = new DynamicClassObject(type);
if (type.IsLookUp())
{
impl = new DynamicClassLookUpObject(type);
}
else
{
impl = new DynamicClassObject(type);
}
}

else if (type.IsLookUp())
{
impl = new LookUpObject(type);
}

else
Expand Down
34 changes: 34 additions & 0 deletions src/runtime/Types/DynamicClassLookUpObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;

namespace Python.Runtime
{
/// <summary>
/// Implements a Python type for managed DynamicClass objects that support look up (dictionaries),
/// that is, they implement ContainsKey().
/// This type is essentially the same as a ClassObject, except that it provides
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
/// from Python.
/// </summary>
internal class DynamicClassLookUpObject : DynamicClassObject
{
internal DynamicClassLookUpObject(Type tp) : base(tp)
{
}

/// <summary>
/// Implements __len__ for dictionary types.
/// </summary>
public static int mp_length(BorrowedReference ob)
{
return LookUpObject.mp_length(ob);
}

/// <summary>
/// Implements __contains__ for dictionary types.
/// </summary>
public static int sq_contains(BorrowedReference ob, BorrowedReference v)
{
return LookUpObject.sq_contains(ob, v);
}
}
}
66 changes: 2 additions & 64 deletions src/runtime/Types/KeyValuePairEnumerableObject.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Python.Runtime
{
Expand All @@ -10,75 +9,14 @@ namespace Python.Runtime
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
/// from Python.
/// </summary>
internal class KeyValuePairEnumerableObject : ClassObject
internal class KeyValuePairEnumerableObject : LookUpObject
{
[NonSerialized]
private static Dictionary<Tuple<Type, string>, MethodInfo> methodsByType = new Dictionary<Tuple<Type, string>, MethodInfo>();
private static List<string> requiredMethods = new List<string> { "Count", "ContainsKey" };

internal static bool VerifyMethodRequirements(Type type)
{
foreach (var requiredMethod in requiredMethods)
{
var method = type.GetMethod(requiredMethod);
if (method == null)
{
method = type.GetMethod($"get_{requiredMethod}");
if (method == null)
{
return false;
}
}

var key = Tuple.Create(type, requiredMethod);
methodsByType.Add(key, method);
}

return true;
}

internal KeyValuePairEnumerableObject(Type tp) : base(tp)
{

}

internal override bool CanSubclass() => false;

/// <summary>
/// Implements __len__ for dictionary types.
/// </summary>
public static int mp_length(BorrowedReference ob)
{
var obj = (CLRObject)GetManagedObject(ob);
var self = obj.inst;

var key = Tuple.Create(self.GetType(), "Count");
var methodInfo = methodsByType[key];

return (int)methodInfo.Invoke(self, null);
}

/// <summary>
/// Implements __contains__ for dictionary types.
/// </summary>
public static int sq_contains(BorrowedReference ob, BorrowedReference v)
{
var obj = (CLRObject)GetManagedObject(ob);
var self = obj.inst;

var key = Tuple.Create(self.GetType(), "ContainsKey");
var methodInfo = methodsByType[key];

var parameters = methodInfo.GetParameters();
object arg;
if (!Converter.ToManaged(v, parameters[0].ParameterType, out arg, false))
{
Exceptions.SetError(Exceptions.TypeError,
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}");
}

return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0;
}
}

public static class KeyValuePairEnumerableObjectExtension
Expand All @@ -102,7 +40,7 @@ public static bool IsKeyValuePairEnumerable(this Type type)
a.GetGenericTypeDefinition() == keyValuePairType &&
a.GetGenericArguments().Length == 2)
{
return KeyValuePairEnumerableObject.VerifyMethodRequirements(type);
return LookUpObject.VerifyMethodRequirements(type);
}
}
}
Expand Down
Loading
Loading
0