| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,33 +1,39 @@ | |||
| 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> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,55 @@ | |||
| 1 | + using System; | ||
| 2 | + using System.Collections.Generic; | ||
| 3 | + | ||
| 4 | + namespace Python.Runtime.Codecs | ||
| 5 | + { | ||
| 6 | + public class IterableDecoder : IPyObjectDecoder | ||
| 7 | + { | ||
| 8 | + internal static bool IsIterable(Type targetType) | ||
| 9 | + { | ||
| 10 | + //if it is a plain IEnumerable, we can decode it using sequence protocol. | ||
| 11 | + if (targetType == typeof(System.Collections.IEnumerable)) | ||
| 12 | + return true; | ||
| 13 | + | ||
| 14 | + if (!targetType.IsGenericType) | ||
| 15 | + return false; | ||
| 16 | + | ||
| 17 | + return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + internal static bool IsIterable(PyObject objectType) | ||
| 21 | + { | ||
| 22 | + return objectType.HasAttr("__iter__"); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public bool CanDecode(PyObject objectType, Type targetType) | ||
| 26 | + { | ||
| 27 | + return IsIterable(objectType) && IsIterable(targetType); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public bool TryDecode<T>(PyObject pyObj, out T value) | ||
| 31 | + { | ||
| 32 | + //first see if T is a plan IEnumerable | ||
| 33 | + if (typeof(T) == typeof(System.Collections.IEnumerable)) | ||
| 34 | + { | ||
| 35 | + object enumerable = new CollectionWrappers.IterableWrapper<object>(pyObj); | ||
| 36 | + value = (T)enumerable; | ||
| 37 | + return true; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + var elementType = typeof(T).GetGenericArguments()[0]; | ||
| 41 | + var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); | ||
| 42 | + | ||
| 43 | + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); | ||
| 44 | + value = (T)instance; | ||
| 45 | + return true; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public static IterableDecoder Instance { get; } = new IterableDecoder(); | ||
| 49 | + | ||
| 50 | + public static void Register() | ||
| 51 | + { | ||
| 52 | + PyObjectConversions.RegisterDecoder(Instance); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments