Skip to content

Commit 101624e

Browse files
committed
Reflect PR #42 KeyValuePairEnumerableObject
1 parent cde9b51 commit 101624e

File tree

4 files changed

+369
-0
lines changed

4 files changed

+369
-0
lines changed

src/runtime/classmanager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ private static ClassBase CreateClass(Type type)
224224
impl = new ArrayObject(type);
225225
}
226226

227+
else if (type.IsKeyValuePairEnumerable())
228+
{
229+
impl = new KeyValuePairEnumerableObject(type);
230+
}
231+
227232
else if (type.IsInterface)
228233
{
229234
impl = new InterfaceObject(type);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace Python.Runtime
6+
{
7+
/// <summary>
8+
/// Implements a Python type for managed KeyValuePairEnumerable (dictionaries).
9+
/// This type is essentially the same as a ClassObject, except that it provides
10+
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
11+
/// from Python.
12+
/// </summary>
13+
internal class KeyValuePairEnumerableObject : ClassObject
14+
{
15+
private static Dictionary<Tuple<Type, string>, MethodInfo> methodsByType = new Dictionary<Tuple<Type, string>, MethodInfo>();
16+
private static List<string> requiredMethods = new List<string> { "Count", "ContainsKey" };
17+
18+
internal static bool VerifyMethodRequirements(Type type)
19+
{
20+
foreach (var requiredMethod in requiredMethods)
21+
{
22+
var method = type.GetMethod(requiredMethod);
23+
if (method == null)
24+
{
25+
method = type.GetMethod($"get_{requiredMethod}");
26+
if (method == null)
27+
{
28+
return false;
29+
}
30+
}
31+
32+
var key = Tuple.Create(type, requiredMethod);
33+
methodsByType.Add(key, method);
34+
}
35+
36+
return true;
37+
}
38+
39+
internal KeyValuePairEnumerableObject(Type tp) : base(tp)
40+
{
41+
42+
}
43+
44+
internal override bool CanSubclass() => false;
45+
46+
/// <summary>
47+
/// Implements __len__ for dictionary types.
48+
/// </summary>
49+
public static int mp_length(IntPtr ob)
50+
{
51+
var obj = (CLRObject)GetManagedObject(ob);
52+
var self = obj.inst;
53+
54+
var key = Tuple.Create(self.GetType(), "Count");
55+
var methodInfo = methodsByType[key];
56+
57+
return (int)methodInfo.Invoke(self, null);
58+
}
59+
60+
/// <summary>
61+
/// Implements __contains__ for dictionary types.
62+
/// </summary>
63+
public static int sq_contains(IntPtr ob, IntPtr v)
64+
{
65+
var obj = (CLRObject)GetManagedObject(ob);
66+
var self = obj.inst;
67+
68+
var key = Tuple.Create(self.GetType(), "ContainsKey");
69+
var methodInfo = methodsByType[key];
70+
71+
var parameters = methodInfo.GetParameters();
72+
object arg;
73+
if (!Converter.ToManaged(v, parameters[0].ParameterType, out arg, false))
74+
{
75+
Exceptions.SetError(Exceptions.TypeError,
76+
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}");
77+
}
78+
79+
return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0;
80+
}
81+
}
82+
83+
public static class KeyValuePairEnumerableObjectExtension
84+
{
85+
public static bool IsKeyValuePairEnumerable(this Type type)
86+
{
87+
var iEnumerableType = typeof(IEnumerable<>);
88+
var keyValuePairType = typeof(KeyValuePair<,>);
89+
90+
var interfaces = type.GetInterfaces();
91+
foreach (var i in interfaces)
92+
{
93+
if (i.IsGenericType &&
94+
i.GetGenericTypeDefinition() == iEnumerableType)
95+
{
96+
var arguments = i.GetGenericArguments();
97+
if (arguments.Length != 1) continue;
98+
99+
var a = arguments[0];
100+
if (a.IsGenericType &&
101+
a.GetGenericTypeDefinition() == keyValuePairType &&
102+
a.GetGenericArguments().Length == 2)
103+
{
104+
return KeyValuePairEnumerableObject.VerifyMethodRequirements(type);
105+
}
106+
}
107+
}
108+
109+
return false;
110+
}
111+
}
112+
}

src/testing/dictionarytest.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Python.Test
6+
{
7+
/// <summary>
8+
/// Supports units tests for dictionary __contains__ and __len__
9+
/// </summary>
10+
public class PublicDictionaryTest
11+
{
12+
public IDictionary<string, int> items;
13+
14+
public PublicDictionaryTest()
15+
{
16+
items = new int[5] { 0, 1, 2, 3, 4 }
17+
.ToDictionary(k => k.ToString(), v => v);
18+
}
19+
}
20+
21+
22+
public class ProtectedDictionaryTest
23+
{
24+
protected IDictionary<string, int> items;
25+
26+
public ProtectedDictionaryTest()
27+
{
28+
items = new int[5] { 0, 1, 2, 3, 4 }
29+
.ToDictionary(k => k.ToString(), v => v);
30+
}
31+
}
32+
33+
34+
public class InternalDictionaryTest
35+
{
36+
internal IDictionary<string, int> items;
37+
38+
public InternalDictionaryTest()
39+
{
40+
items = new int[5] { 0, 1, 2, 3, 4 }
41+
.ToDictionary(k => k.ToString(), v => v);
42+
}
43+
}
44+
45+
46+
public class PrivateDictionaryTest
47+
{
48+
private IDictionary<string, int> items;
49+
50+
public PrivateDictionaryTest()
51+
{
52+
items = new int[5] { 0, 1, 2, 3, 4 }
53+
.ToDictionary(k => k.ToString(), v => v);
54+
}
55+
}
56+
57+
public class InheritedDictionaryTest : IDictionary<string, int>
58+
{
59+
private readonly IDictionary<string, int> items;
60+
61+
public InheritedDictionaryTest()
62+
{
63+
items = new int[5] { 0, 1, 2, 3, 4 }
64+
.ToDictionary(k => k.ToString(), v => v);
65+
}
66+
67+
public int this[string key]
68+
{
69+
get { return items[key]; }
70+
set { items[key] = value; }
71+
}
72+
73+
public ICollection<string> Keys => items.Keys;
74+
75+
public ICollection<int> Values => items.Values;
76+
77+
public int Count => items.Count;
78+
79+
public bool IsReadOnly => false;
80+
81+
public void Add(string key, int value) => items.Add(key, value);
82+
83+
public void Add(KeyValuePair<string, int> item) => items.Add(item);
84+
85+
public void Clear() => items.Clear();
86+
87+
public bool Contains(KeyValuePair<string, int> item) => items.Contains(item);
88+
89+
public bool ContainsKey(string key) => items.ContainsKey(key);
90+
91+
public void CopyTo(KeyValuePair<string, int>[] array, int arrayIndex)
92+
{
93+
items.CopyTo(array, arrayIndex);
94+
}
95+
96+
public IEnumerator<KeyValuePair<string, int>> GetEnumerator() => items.GetEnumerator();
97+
98+
public bool Remove(string key) => items.Remove(key);
99+
100+
public bool Remove(KeyValuePair<string, int> item) => items.Remove(item);
101+
102+
public bool TryGetValue(string key, out int value) => items.TryGetValue(key, out value);
103+
104+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
105+
}
106+
}

src/tests/test_dictionary.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test support for managed dictionaries."""
4+
5+
import Python.Test as Test
6+
import System
7+
import pytest
8+
9+
10+
def test_public_dict():
11+
"""Test public dict."""
12+
ob = Test.PublicDictionaryTest()
13+
items = ob.items
14+
15+
assert len(items) == 5
16+
17+
assert items['0'] == 0
18+
assert items['4'] == 4
19+
20+
items['0'] = 8
21+
assert items['0'] == 8
22+
23+
items['4'] = 9
24+
assert items['4'] == 9
25+
26+
items['-4'] = 0
27+
assert items['-4'] == 0
28+
29+
items['-1'] = 4
30+
assert items['-1'] == 4
31+
32+
def test_protected_dict():
33+
"""Test protected dict."""
34+
ob = Test.ProtectedDictionaryTest()
35+
items = ob.items
36+
37+
assert len(items) == 5
38+
39+
assert items['0'] == 0
40+
assert items['4'] == 4
41+
42+
items['0'] = 8
43+
assert items['0'] == 8
44+
45+
items['4'] = 9
46+
assert items['4'] == 9
47+
48+
items['-4'] = 0
49+
assert items['-4'] == 0
50+
51+
items['-1'] = 4
52+
assert items['-1'] == 4
53+
54+
def test_internal_dict():
55+
"""Test internal dict."""
56+
57+
with pytest.raises(AttributeError):
58+
ob = Test.InternalDictionaryTest()
59+
_ = ob.items
60+
61+
def test_private_dict():
62+
"""Test private dict."""
63+
64+
with pytest.raises(AttributeError):
65+
ob = Test.PrivateDictionaryTest()
66+
_ = ob.items
67+
68+
def test_dict_contains():
69+
"""Test dict support for __contains__."""
70+
71+
ob = Test.PublicDictionaryTest()
72+
items = ob.items
73+
74+
assert '0' in items
75+
assert '1' in items
76+
assert '2' in items
77+
assert '3' in items
78+
assert '4' in items
79+
80+
assert not ('5' in items)
81+
assert not ('-1' in items)
82+
83+
def test_dict_abuse():
84+
"""Test dict abuse."""
85+
_class = Test.PublicDictionaryTest
86+
ob = Test.PublicDictionaryTest()
87+
88+
with pytest.raises(AttributeError):
89+
del _class.__getitem__
90+
91+
with pytest.raises(AttributeError):
92+
del ob.__getitem__
93+
94+
with pytest.raises(AttributeError):
95+
del _class.__setitem__
96+
97+
with pytest.raises(AttributeError):
98+
del ob.__setitem__
99+
100+
with pytest.raises(TypeError):
101+
Test.PublicArrayTest.__getitem__(0, 0)
102+
103+
with pytest.raises(TypeError):
104+
Test.PublicArrayTest.__setitem__(0, 0, 0)
105+
106+
with pytest.raises(TypeError):
107+
desc = Test.PublicArrayTest.__dict__['__getitem__']
108+
desc(0, 0)
109+
110+
with pytest.raises(TypeError):
111+
desc = Test.PublicArrayTest.__dict__['__setitem__']
112+
desc(0, 0, 0)
113+
114+
def test_InheritedDictionary():
115+
"""Test class that inherited from IDictionary."""
116+
items = Test.InheritedDictionaryTest()
117+
118+
assert len(items) == 5
119+
120+
assert items['0'] == 0
121+
assert items['4'] == 4
122+
123+
items['0'] = 8
124+
assert items['0'] == 8
125+
126+
items['4'] = 9
127+
assert items['4'] == 9
128+
129+
items['-4'] = 0
130+
assert items['-4'] == 0
131+
132+
items['-1'] = 4
133+
assert items['-1'] == 4
134+
135+
def test_InheritedDictionary_contains():
136+
"""Test dict support for __contains__ in class that inherited from IDictionary"""
137+
items = Test.InheritedDictionaryTest()
138+
139+
assert '0' in items
140+
assert '1' in items
141+
assert '2' in items
142+
assert '3' in items
143+
assert '4' in items
144+
145+
assert not ('5' in items)
146+
assert not ('-1' in items)

0 commit comments

Comments
 (0)