11namespace Python . EmbeddingTest {
22 using System ;
33 using System . Collections . Generic ;
4- using System . Text ;
4+ using System . Linq ;
55 using NUnit . Framework ;
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,31 +66,236 @@ 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 }
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+ }
85299 }
86300
87301 /// <summary>
0 commit comments