1
1
namespace Python . EmbeddingTest {
2
2
using System ;
3
3
using System . Collections . Generic ;
4
- using System . Text ;
4
+ using System . Linq ;
5
5
using NUnit . Framework ;
6
6
using Python . Runtime ;
7
7
using Python . Runtime . Codecs ;
8
8
9
- public class Codecs {
9
+ public class Codecs
10
+ {
10
11
[ SetUp ]
11
- public void SetUp ( ) {
12
+ public void SetUp ( )
13
+ {
12
14
PythonEngine . Initialize ( ) ;
13
15
}
14
16
15
17
[ TearDown ]
16
- public void Dispose ( ) {
18
+ public void Dispose ( )
19
+ {
17
20
PythonEngine . Shutdown ( ) ;
18
21
}
19
22
20
23
[ Test ]
21
- public void ConversionsGeneric ( ) {
22
- ConversionsGeneric < ValueTuple < int , string , object > , ValueTuple > ( ) ;
24
+ public void TupleConversionsGeneric ( )
25
+ {
26
+ TupleConversionsGeneric < ValueTuple < int , string , object > , ValueTuple > ( ) ;
23
27
}
24
28
25
- static void ConversionsGeneric < T , TTuple > ( ) {
29
+ static void TupleConversionsGeneric < T , TTuple > ( )
30
+ {
26
31
TupleCodec < TTuple > . Register ( ) ;
27
32
var tuple = Activator . CreateInstance ( typeof ( T ) , 42 , "42" , new object ( ) ) ;
28
33
T restored = default ;
29
34
using ( Py . GIL ( ) )
30
- using ( var scope = Py . CreateScope ( ) ) {
35
+ using ( var scope = Py . CreateScope ( ) )
36
+ {
31
37
void Accept ( T value ) => restored = value ;
32
38
var accept = new Action < T > ( Accept ) . ToPython ( ) ;
33
39
scope . Set ( nameof ( tuple ) , tuple ) ;
@@ -38,15 +44,18 @@ static void ConversionsGeneric<T, TTuple>() {
38
44
}
39
45
40
46
[ Test ]
41
- public void ConversionsObject ( ) {
42
- ConversionsObject < ValueTuple < int , string , object > , ValueTuple > ( ) ;
47
+ public void TupleConversionsObject ( )
48
+ {
49
+ TupleConversionsObject < ValueTuple < int , string , object > , ValueTuple > ( ) ;
43
50
}
44
- static void ConversionsObject < T , TTuple > ( ) {
51
+ static void TupleConversionsObject < T , TTuple > ( )
52
+ {
45
53
TupleCodec < TTuple > . Register ( ) ;
46
54
var tuple = Activator . CreateInstance ( typeof ( T ) , 42 , "42" , new object ( ) ) ;
47
55
T restored = default ;
48
56
using ( Py . GIL ( ) )
49
- using ( var scope = Py . CreateScope ( ) ) {
57
+ using ( var scope = Py . CreateScope ( ) )
58
+ {
50
59
void Accept ( object value ) => restored = ( T ) value ;
51
60
var accept = new Action < object > ( Accept ) . ToPython ( ) ;
52
61
scope . Set ( nameof ( tuple ) , tuple ) ;
@@ -57,31 +66,236 @@ static void ConversionsObject<T, TTuple>() {
57
66
}
58
67
59
68
[ Test ]
60
- public void TupleRoundtripObject ( ) {
69
+ public void TupleRoundtripObject ( )
70
+ {
61
71
TupleRoundtripObject < ValueTuple < int , string , object > , ValueTuple > ( ) ;
62
72
}
63
- static void TupleRoundtripObject < T , TTuple > ( ) {
73
+ static void TupleRoundtripObject < T , TTuple > ( )
74
+ {
64
75
var tuple = Activator . CreateInstance ( typeof ( T ) , 42 , "42" , new object ( ) ) ;
65
- using ( Py . GIL ( ) ) {
76
+ using ( Py . GIL ( ) )
77
+ {
66
78
var pyTuple = TupleCodec < TTuple > . Instance . TryEncode ( tuple ) ;
67
79
Assert . IsTrue ( TupleCodec < TTuple > . Instance . TryDecode ( pyTuple , out object restored ) ) ;
68
80
Assert . AreEqual ( expected : tuple , actual : restored ) ;
69
81
}
70
82
}
71
83
72
84
[ Test ]
73
- public void TupleRoundtripGeneric ( ) {
85
+ public void TupleRoundtripGeneric ( )
86
+ {
74
87
TupleRoundtripGeneric < ValueTuple < int , string , object > , ValueTuple > ( ) ;
75
88
}
76
89
77
- static void TupleRoundtripGeneric < T , TTuple > ( ) {
90
+ static void TupleRoundtripGeneric < T , TTuple > ( )
91
+ {
78
92
var tuple = Activator . CreateInstance ( typeof ( T ) , 42 , "42" , new object ( ) ) ;
79
- using ( Py . GIL ( ) ) {
93
+ using ( Py . GIL ( ) )
94
+ {
80
95
var pyTuple = TupleCodec < TTuple > . Instance . TryEncode ( tuple ) ;
81
96
Assert . IsTrue ( TupleCodec < TTuple > . Instance . TryDecode ( pyTuple , out T restored ) ) ;
82
97
Assert . AreEqual ( expected : tuple , actual : restored ) ;
83
98
}
84
99
}
100
+
101
+ static PyObject GetPythonIterable ( )
102
+ {
103
+ using ( Py . GIL ( ) )
104
+ {
105
+ return PythonEngine . Eval ( "map(lambda x: x, [1,2,3])" ) ;
106
+ }
107
+ }
108
+
109
+ [ Test ]
110
+ public void ListDecoderTest ( )
111
+ {
112
+ var codec = ListDecoder . Instance ;
113
+ var items = new List < PyObject > ( ) { new PyInt ( 1 ) , new PyInt ( 2 ) , new PyInt ( 3 ) } ;
114
+
115
+ var pyList = new PyList ( items . ToArray ( ) ) ;
116
+
117
+ var pyListType = pyList . GetPythonType ( ) ;
118
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IList < bool > ) ) ) ;
119
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IList < int > ) ) ) ;
120
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( System . Collections . IEnumerable ) ) ) ;
121
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( IEnumerable < int > ) ) ) ;
122
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( ICollection < float > ) ) ) ;
123
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( bool ) ) ) ;
124
+
125
+ //we'd have to copy into a list instance to do this, it would not be lossless.
126
+ //lossy converters can be implemented outside of the python.net core library
127
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( List < int > ) ) ) ;
128
+
129
+ //convert to list of int
130
+ IList < int > intList = null ;
131
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out intList ) ; } ) ;
132
+ CollectionAssert . AreEqual ( intList , new List < object > { 1 , 2 , 3 } ) ;
133
+
134
+ //convert to list of string. This will not work.
135
+ //The ListWrapper class will throw a python exception when it tries to access any element.
136
+ //TryDecode is a lossless conversion so there will be no exception at that point
137
+ //interestingly, since the size of the python list can be queried without any conversion,
138
+ //the IList will report a Count of 3.
139
+ IList < string > stringList = null ;
140
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out stringList ) ; } ) ;
141
+ Assert . AreEqual ( stringList . Count , 3 ) ;
142
+ Assert . Throws ( typeof ( InvalidCastException ) , ( ) => { var x = stringList [ 0 ] ; } ) ;
143
+
144
+ //can't convert python iterable to list (this will require a copy which isn't lossless)
145
+ var foo = GetPythonIterable ( ) ;
146
+ var fooType = foo . GetPythonType ( ) ;
147
+ Assert . IsFalse ( codec . CanDecode ( fooType , typeof ( IList < int > ) ) ) ;
148
+ }
149
+
150
+ [ Test ]
151
+ public void SequenceDecoderTest ( )
152
+ {
153
+ var codec = SequenceDecoder . Instance ;
154
+ var items = new List < PyObject > ( ) { new PyInt ( 1 ) , new PyInt ( 2 ) , new PyInt ( 3 ) } ;
155
+
156
+ //SequenceConverter can only convert to any ICollection
157
+ var pyList = new PyList ( items . ToArray ( ) ) ;
158
+ //it can convert a PyList, since PyList satisfies the python sequence protocol
159
+
160
+ Assert . IsFalse ( codec . CanDecode ( pyList , typeof ( bool ) ) ) ;
161
+ Assert . IsFalse ( codec . CanDecode ( pyList , typeof ( IList < int > ) ) ) ;
162
+ Assert . IsFalse ( codec . CanDecode ( pyList , typeof ( System . Collections . IEnumerable ) ) ) ;
163
+ Assert . IsFalse ( codec . CanDecode ( pyList , typeof ( IEnumerable < int > ) ) ) ;
164
+
165
+ Assert . IsTrue ( codec . CanDecode ( pyList , typeof ( ICollection < float > ) ) ) ;
166
+ Assert . IsTrue ( codec . CanDecode ( pyList , typeof ( ICollection < string > ) ) ) ;
167
+ Assert . IsTrue ( codec . CanDecode ( pyList , typeof ( ICollection < int > ) ) ) ;
168
+
169
+ //convert to collection of int
170
+ ICollection < int > intCollection = null ;
171
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out intCollection ) ; } ) ;
172
+ CollectionAssert . AreEqual ( intCollection , new List < object > { 1 , 2 , 3 } ) ;
173
+
174
+ //no python exception should have occurred during the above conversion and check
175
+ Runtime . CheckExceptionOccurred ( ) ;
176
+
177
+ //convert to collection of string. This will not work.
178
+ //The SequenceWrapper class will throw a python exception when it tries to access any element.
179
+ //TryDecode is a lossless conversion so there will be no exception at that point
180
+ //interestingly, since the size of the python sequence can be queried without any conversion,
181
+ //the IList will report a Count of 3.
182
+ ICollection < string > stringCollection = null ;
183
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out stringCollection ) ; } ) ;
184
+ Assert . AreEqual ( 3 , stringCollection . Count ( ) ) ;
185
+ Assert . Throws ( typeof ( InvalidCastException ) , ( ) => {
186
+ string [ ] array = new string [ 3 ] ;
187
+ stringCollection . CopyTo ( array , 0 ) ;
188
+ } ) ;
189
+
190
+ Runtime . CheckExceptionOccurred ( ) ;
191
+
192
+ //can't convert python iterable to collection (this will require a copy which isn't lossless)
193
+ //python iterables do not satisfy the python sequence protocol
194
+ var foo = GetPythonIterable ( ) ;
195
+ var fooType = foo . GetPythonType ( ) ;
196
+ Assert . IsFalse ( codec . CanDecode ( fooType , typeof ( ICollection < int > ) ) ) ;
197
+
198
+ //python tuples do satisfy the python sequence protocol
199
+ var pyTuple = new PyTuple ( items . ToArray ( ) ) ;
200
+ var pyTupleType = pyTuple . GetPythonType ( ) ;
201
+
202
+ Assert . IsTrue ( codec . CanDecode ( pyTupleType , typeof ( ICollection < float > ) ) ) ;
203
+ Assert . IsTrue ( codec . CanDecode ( pyTupleType , typeof ( ICollection < int > ) ) ) ;
204
+ Assert . IsTrue ( codec . CanDecode ( pyTupleType , typeof ( ICollection < string > ) ) ) ;
205
+
206
+ //convert to collection of int
207
+ ICollection < int > intCollection2 = null ;
208
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyTuple , out intCollection2 ) ; } ) ;
209
+ CollectionAssert . AreEqual ( intCollection2 , new List < object > { 1 , 2 , 3 } ) ;
210
+
211
+ //no python exception should have occurred during the above conversion and check
212
+ Runtime . CheckExceptionOccurred ( ) ;
213
+
214
+ //convert to collection of string. This will not work.
215
+ //The SequenceWrapper class will throw a python exception when it tries to access any element.
216
+ //TryDecode is a lossless conversion so there will be no exception at that point
217
+ //interestingly, since the size of the python sequence can be queried without any conversion,
218
+ //the IList will report a Count of 3.
219
+ ICollection < string > stringCollection2 = null ;
220
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyTuple , out stringCollection2 ) ; } ) ;
221
+ Assert . AreEqual ( 3 , stringCollection2 . Count ( ) ) ;
222
+ Assert . Throws ( typeof ( InvalidCastException ) , ( ) => {
223
+ string [ ] array = new string [ 3 ] ;
224
+ stringCollection2 . CopyTo ( array , 0 ) ;
225
+ } ) ;
226
+
227
+ Runtime . CheckExceptionOccurred ( ) ;
228
+
229
+ }
230
+
231
+ [ Test ]
232
+ public void IterableDecoderTest ( )
233
+ {
234
+ var codec = IterableDecoder . Instance ;
235
+ var items = new List < PyObject > ( ) { new PyInt ( 1 ) , new PyInt ( 2 ) , new PyInt ( 3 ) } ;
236
+
237
+ var pyList = new PyList ( items . ToArray ( ) ) ;
238
+ var pyListType = pyList . GetPythonType ( ) ;
239
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( IList < bool > ) ) ) ;
240
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( System . Collections . IEnumerable ) ) ) ;
241
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IEnumerable < int > ) ) ) ;
242
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( ICollection < float > ) ) ) ;
243
+ Assert . IsFalse ( codec . CanDecode ( pyListType , typeof ( bool ) ) ) ;
244
+
245
+ //ensure a PyList can be converted to a plain IEnumerable
246
+ System . Collections . IEnumerable plainEnumerable1 = null ;
247
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out plainEnumerable1 ) ; } ) ;
248
+ CollectionAssert . AreEqual ( plainEnumerable1 , new List < object > { 1 , 2 , 3 } ) ;
249
+
250
+ //can convert to any generic ienumerable. If the type is not assignable from the python element
251
+ //it will lead to an empty iterable when decoding. TODO - should it throw?
252
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IEnumerable < int > ) ) ) ;
253
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IEnumerable < double > ) ) ) ;
254
+ Assert . IsTrue ( codec . CanDecode ( pyListType , typeof ( IEnumerable < string > ) ) ) ;
255
+
256
+ IEnumerable < int > intEnumerable = null ;
257
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out intEnumerable ) ; } ) ;
258
+ CollectionAssert . AreEqual ( intEnumerable , new List < object > { 1 , 2 , 3 } ) ;
259
+
260
+ Runtime . CheckExceptionOccurred ( ) ;
261
+
262
+ IEnumerable < double > doubleEnumerable = null ;
263
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out doubleEnumerable ) ; } ) ;
264
+ CollectionAssert . AreEqual ( doubleEnumerable , new List < object > { 1 , 2 , 3 } ) ;
265
+
266
+ Runtime . CheckExceptionOccurred ( ) ;
267
+
268
+ IEnumerable < string > stringEnumerable = null ;
269
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out stringEnumerable ) ; } ) ;
270
+
271
+ Assert . Throws ( typeof ( InvalidCastException ) , ( ) => {
272
+ foreach ( string item in stringEnumerable )
273
+ {
274
+ var x = item ;
275
+ }
276
+ } ) ;
277
+ Assert . Throws ( typeof ( InvalidCastException ) , ( ) => {
278
+ stringEnumerable . Count ( ) ;
279
+ } ) ;
280
+
281
+ Runtime . CheckExceptionOccurred ( ) ;
282
+
283
+ //ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable
284
+ var foo = GetPythonIterable ( ) ;
285
+ var fooType = foo . GetPythonType ( ) ;
286
+ System . Collections . IEnumerable plainEnumerable2 = null ;
287
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out plainEnumerable2 ) ; } ) ;
288
+ CollectionAssert . AreEqual ( plainEnumerable2 , new List < object > { 1 , 2 , 3 } ) ;
289
+
290
+ //can convert to any generic ienumerable. If the type is not assignable from the python element
291
+ //it will be an exception during TryDecode
292
+ Assert . IsTrue ( codec . CanDecode ( fooType , typeof ( IEnumerable < int > ) ) ) ;
293
+ Assert . IsTrue ( codec . CanDecode ( fooType , typeof ( IEnumerable < double > ) ) ) ;
294
+ Assert . IsTrue ( codec . CanDecode ( fooType , typeof ( IEnumerable < string > ) ) ) ;
295
+
296
+ Assert . DoesNotThrow ( ( ) => { codec . TryDecode ( pyList , out intEnumerable ) ; } ) ;
297
+ CollectionAssert . AreEqual ( intEnumerable , new List < object > { 1 , 2 , 3 } ) ;
298
+ }
85
299
}
86
300
87
301
/// <summary>
0 commit comments