| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,86 @@ | |||
| 1 | + namespace Python.EmbeddingTest { | ||
| 2 | + using System; | ||
| 3 | + using System.Collections.Generic; | ||
| 4 | + using System.Text; | ||
| 5 | + using NUnit.Framework; | ||
| 6 | + using Python.Runtime; | ||
| 7 | + using Python.Runtime.Codecs; | ||
| 8 | + | ||
| 9 | + public class Codecs { | ||
| 10 | + [SetUp] | ||
| 11 | + public void SetUp() { | ||
| 12 | + PythonEngine.Initialize(); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + [TearDown] | ||
| 16 | + public void Dispose() { | ||
| 17 | + PythonEngine.Shutdown(); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + [Test] | ||
| 21 | + public void ConversionsGeneric() { | ||
| 22 | + ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + static void ConversionsGeneric<T, TTuple>() { | ||
| 26 | + TupleCodec<TTuple>.Register(); | ||
| 27 | + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
| 28 | + T restored = default; | ||
| 29 | + using (Py.GIL()) | ||
| 30 | + using (var scope = Py.CreateScope()) { | ||
| 31 | + void Accept(T value) => restored = value; | ||
| 32 | + var accept = new Action<T>(Accept).ToPython(); | ||
| 33 | + scope.Set(nameof(tuple), tuple); | ||
| 34 | + scope.Set(nameof(accept), accept); | ||
| 35 | + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
| 36 | + Assert.AreEqual(expected: tuple, actual: restored); | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + [Test] | ||
| 41 | + public void ConversionsObject() { | ||
| 42 | + ConversionsObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
| 43 | + } | ||
| 44 | + static void ConversionsObject<T, TTuple>() { | ||
| 45 | + TupleCodec<TTuple>.Register(); | ||
| 46 | + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
| 47 | + T restored = default; | ||
| 48 | + using (Py.GIL()) | ||
| 49 | + using (var scope = Py.CreateScope()) { | ||
| 50 | + void Accept(object value) => restored = (T)value; | ||
| 51 | + var accept = new Action<object>(Accept).ToPython(); | ||
| 52 | + scope.Set(nameof(tuple), tuple); | ||
| 53 | + scope.Set(nameof(accept), accept); | ||
| 54 | + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
| 55 | + Assert.AreEqual(expected: tuple, actual: restored); | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + [Test] | ||
| 60 | + public void TupleRoundtripObject() { | ||
| 61 | + TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
| 62 | + } | ||
| 63 | + static void TupleRoundtripObject<T, TTuple>() { | ||
| 64 | + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
| 65 | + using (Py.GIL()) { | ||
| 66 | + var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
| 67 | + Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored)); | ||
| 68 | + Assert.AreEqual(expected: tuple, actual: restored); | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + [Test] | ||
| 73 | + public void TupleRoundtripGeneric() { | ||
| 74 | + TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + static void TupleRoundtripGeneric<T, TTuple>() { | ||
| 78 | + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
| 79 | + using (Py.GIL()) { | ||
| 80 | + var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
| 81 | + Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored)); | ||
| 82 | + Assert.AreEqual(expected: tuple, actual: restored); | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,7 +23,7 @@ | |||
| 23 | 23 | <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> | |
| 24 | 24 | <PythonBuildDir Condition="'$(TargetFramework)'=='net40' AND '$(PythonBuildDir)' == ''">$(SolutionDir)\bin\</PythonBuildDir> | |
| 25 | 25 | <PublishDir Condition="'$(TargetFramework)'!='net40'">$(OutputPath)\$(TargetFramework)_publish</PublishDir> | |
| 26 | - <LangVersion>6</LangVersion> | ||
| 26 | + <LangVersion>7.3</LangVersion> | ||
| 27 | 27 | <ErrorReport>prompt</ErrorReport> | |
| 28 | 28 | <CustomDefineConstants Condition="'$(CustomDefineConstants)' == ''">$(PYTHONNET_DEFINE_CONSTANTS)</CustomDefineConstants> | |
| 29 | 29 | <BaseDefineConstants>XPLAT</BaseDefineConstants> | |
@@ -81,6 +81,7 @@ | |||
| 81 | 81 | <PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" /> | |
| 82 | 82 | <PackageReference Include="NUnit3TestAdapter" Version="3.8.0" /> | |
| 83 | 83 | <PackageReference Include="NUnitLite" Version="3.7.2" /> | |
| 84 | + <PackageReference Include="System.ValueTuple" Version="4.5.0" /> | ||
| 84 | 85 | </ItemGroup> | |
| 85 | 86 | <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'"> | |
| 86 | 87 | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,7 +14,7 @@ | |||
| 14 | 14 | <NoWarn>1591</NoWarn> | |
| 15 | 15 | <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> | |
| 16 | 16 | <PythonBuildDir Condition=" '$(PythonBuildDir)' == '' ">$(SolutionDir)\bin\</PythonBuildDir> | |
| 17 | - <LangVersion>6</LangVersion> | ||
| 17 | + <LangVersion>7.3</LangVersion> | ||
| 18 | 18 | <RestorePackages>true</RestorePackages> | |
| 19 | 19 | <ErrorReport>prompt</ErrorReport> | |
| 20 | 20 | </PropertyGroup> | |
@@ -80,6 +80,7 @@ | |||
| 80 | 80 | <None Include="packages.config" /> | |
| 81 | 81 | </ItemGroup> | |
| 82 | 82 | <ItemGroup> | |
| 83 | + <Compile Include="Codecs.cs" /> | ||
| 83 | 84 | <Compile Include="dynamic.cs" /> | |
| 84 | 85 | <Compile Include="pyimport.cs" /> | |
| 85 | 86 | <Compile Include="pyinitialize.cs" /> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | - <?xml version="1.0" encoding="utf-8"?> | ||
| 1 | + <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | 2 | <packages> | |
| 3 | 3 | <package id="NUnit" version="3.7.1" targetFramework="net40" /> | |
| 4 | 4 | <package id="NUnit.ConsoleRunner" version="3.7.0" targetFramework="net40" /> | |
| 5 | - </packages> | ||
| 5 | + <package id="System.ValueTuple" version="4.5.0" targetFramework="net40" /> | ||
| 6 | + </packages> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,129 @@ | |||
| 1 | + namespace Python.Runtime.Codecs | ||
| 2 | + { | ||
| 3 | + using System; | ||
| 4 | + using System.Collections.Generic; | ||
| 5 | + using System.Linq; | ||
| 6 | + using System.Reflection; | ||
| 7 | + | ||
| 8 | + public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder | ||
| 9 | + { | ||
| 10 | + TupleCodec() { } | ||
| 11 | + public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>(); | ||
| 12 | + | ||
| 13 | + public bool CanEncode(Type type) | ||
| 14 | + => type.Namespace == typeof(TTuple).Namespace && type.Name.StartsWith(typeof(TTuple).Name + '`') | ||
| 15 | + || type == typeof(object) || type == typeof(TTuple); | ||
| 16 | + | ||
| 17 | + public PyObject TryEncode(object value) | ||
| 18 | + { | ||
| 19 | + if (value == null) return null; | ||
| 20 | + | ||
| 21 | + var tupleType = value.GetType(); | ||
| 22 | + if (tupleType == typeof(object)) return null; | ||
| 23 | + if (!this.CanEncode(tupleType)) return null; | ||
| 24 | + if (tupleType == typeof(TTuple)) return new PyTuple(); | ||
| 25 | + | ||
| 26 | + long fieldCount = tupleType.GetGenericArguments().Length; | ||
| 27 | + var tuple = Runtime.PyTuple_New(fieldCount); | ||
| 28 | + Exceptions.ErrorCheck(tuple); | ||
| 29 | + int fieldIndex = 0; | ||
| 30 | + foreach (FieldInfo field in tupleType.GetFields()) | ||
| 31 | + { | ||
| 32 | + var item = field.GetValue(value); | ||
| 33 | + IntPtr pyItem = Converter.ToPython(item); | ||
| 34 | + Runtime.XIncref(pyItem); | ||
| 35 | + Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); | ||
| 36 | + fieldIndex++; | ||
| 37 | + } | ||
| 38 | + return new PyTuple(Runtime.SelfIncRef(tuple)); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public bool CanDecode(PyObject objectType, Type targetType) | ||
| 42 | + => objectType.Handle == Runtime.PyTupleType && this.CanEncode(targetType); | ||
| 43 | + | ||
| 44 | + public bool TryDecode<T>(PyObject pyObj, out T value) | ||
| 45 | + { | ||
| 46 | + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); | ||
| 47 | + | ||
| 48 | + value = default; | ||
| 49 | + | ||
| 50 | + if (!Runtime.PyTuple_Check(pyObj.Handle)) return false; | ||
| 51 | + | ||
| 52 | + if (typeof(T) == typeof(object)) | ||
| 53 | + { | ||
| 54 | + bool converted = Decode(pyObj, out object result); | ||
| 55 | + if (converted) | ||
| 56 | + { | ||
| 57 | + value = (T)result; | ||
| 58 | + return true; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + return false; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + var itemTypes = typeof(T).GetGenericArguments(); | ||
| 65 | + long itemCount = Runtime.PyTuple_Size(pyObj.Handle); | ||
| 66 | + if (itemTypes.Length != itemCount) return false; | ||
| 67 | + | ||
| 68 | + if (itemCount == 0) | ||
| 69 | + { | ||
| 70 | + value = (T)EmptyTuple; | ||
| 71 | + return true; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + var elements = new object[itemCount]; | ||
| 75 | + for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++) | ||
| 76 | + { | ||
| 77 | + IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex); | ||
| 78 | + if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false)) | ||
| 79 | + { | ||
| 80 | + return false; | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
| 84 | + value = (T)factory.Invoke(null, elements); | ||
| 85 | + return true; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + static bool Decode(PyObject tuple, out object value) | ||
| 89 | + { | ||
| 90 | + long itemCount = Runtime.PyTuple_Size(tuple.Handle); | ||
| 91 | + if (itemCount == 0) | ||
| 92 | + { | ||
| 93 | + value = EmptyTuple; | ||
| 94 | + return true; | ||
| 95 | + } | ||
| 96 | + var elements = new object[itemCount]; | ||
| 97 | + var itemTypes = new Type[itemCount]; | ||
| 98 | + value = null; | ||
| 99 | + for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++) | ||
| 100 | + { | ||
| 101 | + var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex); | ||
| 102 | + if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false)) | ||
| 103 | + { | ||
| 104 | + return false; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
| 111 | + value = factory.Invoke(null, elements); | ||
| 112 | + return true; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + static readonly MethodInfo[] tupleCreate = | ||
| 116 | + typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static) | ||
| 117 | + .Where(m => m.Name == nameof(Tuple.Create)) | ||
| 118 | + .OrderBy(m => m.GetParameters().Length) | ||
| 119 | + .ToArray(); | ||
| 120 | + | ||
| 121 | + static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]); | ||
| 122 | + | ||
| 123 | + public static void Register() | ||
| 124 | + { | ||
| 125 | + PyObjectConversions.RegisterEncoder(Instance); | ||
| 126 | + PyObjectConversions.RegisterDecoder(Instance); | ||
| 127 | + } | ||
| 128 | + } | ||
| 129 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - <?xml version="1.0" encoding="utf-8"?> | ||
| 1 | + <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | 2 | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | |
| 3 | 3 | <PropertyGroup> | |
| 4 | 4 | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |
@@ -76,6 +76,8 @@ | |||
| 76 | 76 | <Reference Include="System" /> | |
| 77 | 77 | </ItemGroup> | |
| 78 | 78 | <ItemGroup> | |
| 79 | + <Compile Include="Codecs\TupleCodecs.cs" /> | ||
| 80 | + <Compile Include="converterextensions.cs" /> | ||
| 79 | 81 | <Compile Include="finalizer.cs" /> | |
| 80 | 82 | <Compile Include="Properties\AssemblyInfo.cs" /> | |
| 81 | 83 | <Compile Include="..\SharedAssemblyInfo.cs"> | |
@@ -172,4 +174,4 @@ | |||
| 172 | 174 | <Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" /> | |
| 173 | 175 | <!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /--> | |
| 174 | 176 | </Target> | |
| 175 | - </Project> | ||
| 177 | + </Project> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -135,8 +135,16 @@ internal static IntPtr ToPython(object value, Type type) | |||
| 135 | 135 | return result; | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | - if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) | ||
| 139 | - { | ||
| 138 | + if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)) { | ||
| 139 | + var encoded = PyObjectConversions.TryEncode(value, type); | ||
| 140 | + if (encoded != null) { | ||
| 141 | + Runtime.XIncref(encoded.Handle); | ||
| 142 | + return encoded.Handle; | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) | ||
| 147 | + { | ||
| 140 | 148 | using (var resultlist = new PyList()) | |
| 141 | 149 | { | |
| 142 | 150 | foreach (object o in (IEnumerable)value) | |
@@ -437,9 +445,21 @@ internal static bool ToManagedValue(IntPtr value, Type obType, | |||
| 437 | 445 | return false; | |
| 438 | 446 | } | |
| 439 | 447 | ||
| 448 | + TypeCode typeCode = Type.GetTypeCode(obType); | ||
| 449 | + if (typeCode == TypeCode.Object) | ||
| 450 | + { | ||
| 451 | + IntPtr pyType = Runtime.PyObject_TYPE(value); | ||
| 452 | + if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) | ||
| 453 | + { | ||
| 454 | + return true; | ||
| 455 | + } | ||
| 456 | + } | ||
| 457 | + | ||
| 440 | 458 | return ToPrimitive(value, obType, out result, setError); | |
| 441 | 459 | } | |
| 442 | 460 | ||
| 461 | + internal delegate bool TryConvertFromPythonDelegate(IntPtr pyObj, out object result); | ||
| 462 | + | ||
| 443 | 463 | /// <summary> | |
| 444 | 464 | /// Convert a Python value to an instance of a primitive managed type. | |
| 445 | 465 | /// </summary> | |
| Back | FazBrowse Home | New Git URL |
0 commit comments