Skip to content

Commit 0d46fd4

Browse files
committed
add codec tests
1 parent 4d19435 commit 0d46fd4

File tree

9 files changed

+257
-99
lines changed

9 files changed

+257
-99
lines changed

src/embed_tests/Codecs.cs

Lines changed: 116 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,34 @@ namespace Python.EmbeddingTest {
66
using Python.Runtime;
77
using Python.Runtime.Codecs;
88

9-
public class Codecs {
9+
public class Codecs
10+
{
1011
[SetUp]
11-
public void SetUp() {
12+
public void SetUp()
13+
{
1214
PythonEngine.Initialize();
1315
}
1416

1517
[TearDown]
16-
public void Dispose() {
18+
public void Dispose()
19+
{
1720
PythonEngine.Shutdown();
1821
}
1922

2023
[Test]
21-
public void ConversionsGeneric() {
22-
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
24+
public void TupleConversionsGeneric()
25+
{
26+
TupleConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
2327
}
2428

25-
static void ConversionsGeneric<T, TTuple>() {
29+
static void TupleConversionsGeneric<T, TTuple>()
30+
{
2631
TupleCodec<TTuple>.Register();
2732
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
2833
T restored = default;
2934
using (Py.GIL())
30-
using (var scope = Py.CreateScope()) {
35+
using (var scope = Py.CreateScope())
36+
{
3137
void Accept(T value) => restored = value;
3238
var accept = new Action<T>(Accept).ToPython();
3339
scope.Set(nameof(tuple), tuple);
@@ -38,15 +44,18 @@ static void ConversionsGeneric<T, TTuple>() {
3844
}
3945

4046
[Test]
41-
public void ConversionsObject() {
42-
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
47+
public void TupleConversionsObject()
48+
{
49+
TupleConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
4350
}
44-
static void ConversionsObject<T, TTuple>() {
51+
static void TupleConversionsObject<T, TTuple>()
52+
{
4553
TupleCodec<TTuple>.Register();
4654
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
4755
T restored = default;
4856
using (Py.GIL())
49-
using (var scope = Py.CreateScope()) {
57+
using (var scope = Py.CreateScope())
58+
{
5059
void Accept(object value) => restored = (T)value;
5160
var accept = new Action<object>(Accept).ToPython();
5261
scope.Set(nameof(tuple), tuple);
@@ -57,38 +66,42 @@ static void ConversionsObject<T, TTuple>() {
5766
}
5867

5968
[Test]
60-
public void TupleRoundtripObject() {
69+
public void TupleRoundtripObject()
70+
{
6171
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
6272
}
63-
static void TupleRoundtripObject<T, TTuple>() {
73+
static void TupleRoundtripObject<T, TTuple>()
74+
{
6475
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
65-
using (Py.GIL()) {
76+
using (Py.GIL())
77+
{
6678
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
6779
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
6880
Assert.AreEqual(expected: tuple, actual: restored);
6981
}
7082
}
7183

7284
[Test]
73-
public void TupleRoundtripGeneric() {
85+
public void TupleRoundtripGeneric()
86+
{
7487
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>();
7588
}
7689

77-
static void TupleRoundtripGeneric<T, TTuple>() {
90+
static void TupleRoundtripGeneric<T, TTuple>()
91+
{
7892
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
79-
using (Py.GIL()) {
93+
using (Py.GIL())
94+
{
8095
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
8196
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored));
8297
Assert.AreEqual(expected: tuple, actual: restored);
8398
}
8499
}
85100

86-
static PyObject GetPythonIterable()
101+
static string GetIntIterableCommands(string instanceName)
87102
{
88-
var locals = new PyDict();
89-
using (Py.GIL())
90-
{
91-
PythonEngine.Exec(@"
103+
var builder = new System.Text.StringBuilder();
104+
builder.AppendLine(@"
92105
class foo():
93106
def __init__(self):
94107
self.counter = 0
@@ -98,9 +111,18 @@ def __next__(self):
98111
if self.counter == 3:
99112
raise StopIteration
100113
self.counter = self.counter + 1
101-
return self.counter
102-
foo_instance = foo()
103-
", null, locals.Handle);
114+
return self.counter");
115+
116+
builder.AppendLine(instanceName + " = foo()");
117+
return builder.ToString();
118+
}
119+
120+
static PyObject GetPythonIterable()
121+
{
122+
var locals = new PyDict();
123+
using (Py.GIL())
124+
{
125+
PythonEngine.Exec(GetIntIterableCommands("foo_instance"), null, locals.Handle);
104126
}
105127

106128
return locals.GetItem("foo_instance");
@@ -113,16 +135,18 @@ public void ListDecoderTest()
113135
var items = new List<PyObject>() { new PyInt(1), new PyInt(2), new PyInt(3) };
114136

115137
var pyList = new PyList(items.ToArray());
116-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IList<bool>)));
117-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IList<int>)));
118-
Assert.IsFalse(codec.CanDecode(pyList, typeof(System.Collections.IEnumerable)));
119-
Assert.IsFalse(codec.CanDecode(pyList, typeof(IEnumerable<int>)));
120-
Assert.IsFalse(codec.CanDecode(pyList, typeof(ICollection<float>)));
121-
Assert.IsFalse(codec.CanDecode(pyList, typeof(bool)));
138+
139+
var pyListType = pyList.GetPythonType();
140+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList<bool>)));
141+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList<int>)));
142+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable)));
143+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
144+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection<float>)));
145+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool)));
122146

123147
//we'd have to copy into a list instance to do this, it would not be lossless.
124148
//lossy converters can be implemented outside of the python.net core library
125-
Assert.IsFalse(codec.CanDecode(pyList, typeof(List<int>)));
149+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(List<int>)));
126150

127151
//convert to list of int
128152
IList<int> intList = null;
@@ -137,11 +161,12 @@ public void ListDecoderTest()
137161
IList<string> stringList = null;
138162
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); });
139163
Assert.AreEqual(stringList.Count, 3);
140-
Assert.Throws(typeof(PythonException), ()=> { var x = stringList[0]; });
164+
Assert.Throws(typeof(PythonException), () => { var x = stringList[0]; });
141165

142166
//can't convert python iterable to list (this will require a copy which isn't lossless)
143167
var foo = GetPythonIterable();
144-
Assert.IsFalse(codec.CanDecode(foo, typeof(IList<int>)));
168+
var fooType = foo.GetPythonType();
169+
Assert.IsFalse(codec.CanDecode(fooType, typeof(IList<int>)));
145170
}
146171

147172
[Test]
@@ -189,18 +214,20 @@ public void SequenceDecoderTest()
189214
//can't convert python iterable to collection (this will require a copy which isn't lossless)
190215
//python iterables do not satisfy the python sequence protocol
191216
var foo = GetPythonIterable();
192-
Assert.IsFalse(codec.CanDecode(foo, typeof(ICollection<int>)));
217+
var fooType = foo.GetPythonType();
218+
Assert.IsFalse(codec.CanDecode(fooType, typeof(ICollection<int>)));
193219

194220
//python tuples do satisfy the python sequence protocol
195221
var pyTuple = new PyObject(Runtime.PyTuple_New(3));
222+
var pyTupleType = pyTuple.GetPythonType();
196223

197224
Runtime.PyTuple_SetItem(pyTuple.Handle, 0, items[0].Handle);
198225
Runtime.PyTuple_SetItem(pyTuple.Handle, 1, items[1].Handle);
199226
Runtime.PyTuple_SetItem(pyTuple.Handle, 2, items[2].Handle);
200227

201-
Assert.IsTrue(codec.CanDecode(pyTuple, typeof(ICollection<float>)));
202-
Assert.IsTrue(codec.CanDecode(pyTuple, typeof(ICollection<int>)));
203-
Assert.IsTrue(codec.CanDecode(pyTuple, typeof(ICollection<string>)));
228+
Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<float>)));
229+
Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<int>)));
230+
Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<string>)));
204231

205232
//convert to collection of int
206233
ICollection<int> intCollection2 = null;
@@ -226,46 +253,6 @@ public void SequenceDecoderTest()
226253
Runtime.CheckExceptionOccurred();
227254

228255
}
229-
}
230-
231-
/// <summary>
232-
/// "Decodes" only objects of exact type <typeparamref name="T"/>.
233-
/// Result is just the raw proxy to the encoder instance itself.
234-
/// </summary>
235-
class ObjectToEncoderInstanceEncoder<T> : IPyObjectEncoder
236-
{
237-
public bool CanEncode(Type type) => type == typeof(T);
238-
public PyObject TryEncode(object value) => PyObject.FromManagedObject(this);
239-
}
240-
241-
/// <summary>
242-
/// Decodes object of specified Python type to the predefined value <see cref="DecodeResult"/>
243-
/// </summary>
244-
/// <typeparam name="TTarget">Type of the <see cref="DecodeResult"/></typeparam>
245-
class DecoderReturningPredefinedValue<TTarget> : IPyObjectDecoder
246-
{
247-
public PyObject TheOnlySupportedSourceType { get; }
248-
public TTarget DecodeResult { get; }
249-
250-
public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult)
251-
{
252-
this.TheOnlySupportedSourceType = objectType;
253-
this.DecodeResult = decodeResult;
254-
}
255-
256-
public bool CanDecode(PyObject objectType, Type targetType)
257-
=> objectType.Handle == TheOnlySupportedSourceType.Handle
258-
&& targetType == typeof(TTarget);
259-
public bool TryDecode<T>(PyObject pyObj, out T value)
260-
{
261-
if (typeof(T) != typeof(TTarget))
262-
throw new ArgumentException(nameof(T));
263-
value = (T)(object)DecodeResult;
264-
return true;
265-
}
266-
}
267-
}
268-
269256

270257
[Test]
271258
public void IterableDecoderTest()
@@ -274,11 +261,12 @@ public void IterableDecoderTest()
274261
var items = new List<PyObject>() { new PyInt(1), new PyInt(2), new PyInt(3) };
275262

276263
var pyList = new PyList(items.ToArray());
277-
Assert.IsFalse(codec.CanDecode(pyList, typeof(IList<bool>)));
278-
Assert.IsTrue(codec.CanDecode(pyList, typeof(System.Collections.IEnumerable)));
279-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IEnumerable<int>)));
280-
Assert.IsFalse(codec.CanDecode(pyList, typeof(ICollection<float>)));
281-
Assert.IsFalse(codec.CanDecode(pyList, typeof(bool)));
264+
var pyListType = pyList.GetPythonType();
265+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(IList<bool>)));
266+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable)));
267+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
268+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection<float>)));
269+
Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool)));
282270

283271
//ensure a PyList can be converted to a plain IEnumerable
284272
System.Collections.IEnumerable plainEnumerable1 = null;
@@ -287,9 +275,9 @@ public void IterableDecoderTest()
287275

288276
//can convert to any generic ienumerable. If the type is not assignable from the python element
289277
//it will lead to an empty iterable when decoding. TODO - should it throw?
290-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IEnumerable<int>)));
291-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IEnumerable<double>)));
292-
Assert.IsTrue(codec.CanDecode(pyList, typeof(IEnumerable<string>)));
278+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
279+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<double>)));
280+
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<string>)));
293281

294282
IEnumerable<int> intEnumerable = null;
295283
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); });
@@ -320,18 +308,57 @@ public void IterableDecoderTest()
320308

321309
//ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable
322310
var foo = GetPythonIterable();
311+
var fooType = foo.GetPythonType();
323312
System.Collections.IEnumerable plainEnumerable2 = null;
324313
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); });
325314
CollectionAssert.AreEqual(plainEnumerable2, new List<object> { 1, 2, 3 });
326315

327316
//can convert to any generic ienumerable. If the type is not assignable from the python element
328317
//it will be an exception during TryDecode
329-
Assert.IsTrue(codec.CanDecode(foo, typeof(IEnumerable<int>)));
330-
Assert.IsTrue(codec.CanDecode(foo, typeof(IEnumerable<double>)));
331-
Assert.IsTrue(codec.CanDecode(foo, typeof(IEnumerable<string>)));
318+
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<int>)));
319+
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<double>)));
320+
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<string>)));
332321

333322
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); });
334323
CollectionAssert.AreEqual(intEnumerable, new List<object> { 1, 2, 3 });
335324
}
336325
}
326+
}
327+
328+
/// <summary>
329+
/// "Decodes" only objects of exact type <typeparamref name="T"/>.
330+
/// Result is just the raw proxy to the encoder instance itself.
331+
/// </summary>
332+
class ObjectToEncoderInstanceEncoder<T> : IPyObjectEncoder
333+
{
334+
public bool CanEncode(Type type) => type == typeof(T);
335+
public PyObject TryEncode(object value) => PyObject.FromManagedObject(this);
336+
}
337+
338+
/// <summary>
339+
/// Decodes object of specified Python type to the predefined value <see cref="DecodeResult"/>
340+
/// </summary>
341+
/// <typeparam name="TTarget">Type of the <see cref="DecodeResult"/></typeparam>
342+
class DecoderReturningPredefinedValue<TTarget> : IPyObjectDecoder
343+
{
344+
public PyObject TheOnlySupportedSourceType { get; }
345+
public TTarget DecodeResult { get; }
346+
347+
public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult)
348+
{
349+
this.TheOnlySupportedSourceType = objectType;
350+
this.DecodeResult = decodeResult;
351+
}
352+
353+
public bool CanDecode(PyObject objectType, Type targetType)
354+
=> objectType.Handle == TheOnlySupportedSourceType.Handle
355+
&& targetType == typeof(TTarget);
356+
public bool TryDecode<T>(PyObject pyObj, out T value)
357+
{
358+
if (typeof(T) != typeof(TTarget))
359+
throw new ArgumentException(nameof(T));
360+
value = (T)(object)DecodeResult;
361+
return true;
362+
}
363+
}
337364
}

src/runtime/Codecs/IterableDecoder.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ internal static bool IsIterable(Type targetType)
1919

2020
internal static bool IsIterable(PyObject objectType)
2121
{
22-
//TODO - do I need to decref iterObject?
23-
IntPtr iterObject = Runtime.PyObject_GetIter(objectType.Handle);
24-
return iterObject != IntPtr.Zero;
22+
return objectType.HasAttr("__iter__");
2523
}
2624

2725
public bool CanDecode(PyObject objectType, Type targetType)

src/runtime/Codecs/ListDecoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ private static bool IsList(PyObject objectType)
1818
//must implement sequence protocol to fully implement list protocol
1919
if (!SequenceDecoder.IsSequence(objectType)) return false;
2020

21-
//returns wheter it implements the list protocol
22-
return Runtime.PyList_Check(objectType.Handle);
21+
//returns wheter the type is a list.
22+
return objectType.Handle == Runtime.PyListType;
2323
}
2424

2525
public bool CanDecode(PyObject objectType, Type targetType)

src/runtime/Codecs/SequenceDecoder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ internal static bool IsSequence(PyObject objectType)
1818
//must implement iterable protocol to fully implement sequence protocol
1919
if (!IterableDecoder.IsIterable(objectType)) return false;
2020

21-
//returns wheter it implements the sequence protocol
22-
return Runtime.PySequence_Check(objectType.Handle);
21+
//returns wheter it implements the sequence protocol
22+
//according to python doc this needs to exclude dict subclasses
23+
//but I don't know how to look for that given the objectType
24+
//rather than the instance.
25+
return objectType.HasAttr("__getitem__");
2326
}
2427

2528
public bool CanDecode(PyObject objectType, Type targetType)

0 commit comments

Comments
 (0)