-
Notifications
You must be signed in to change notification settings - Fork 751
Codecs: customize set of Py<->C# conversions via PyObjectConversions #1022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
28143d5
enable expanding set of marshaling conversions via PyObjectConversions
lostmsu 6eca169
fixed ConversionsObject test failing due to sequence to array convers…
lostmsu 97e33c7
attempt to fix CI build issue with ValueTuple under Mono
lostmsu 449338f
added RefereneAssemblies package reference to fix CI build
lostmsu 39b2347
marked the new codecs API as unstable
lostmsu daa2901
attempt to fix PyScopeTest.TestThread() reading stale value from res …
lostmsu e8e3b4b
Merge branch 'master' into PR/Codecs
lostmsu 8818610
Merge branch 'master' into PR/Codecs
filmor 82f6b99
Merge branch 'master' into PR/Codecs
lostmsu ec98209
fixed bad IncRef after PyTuple_New
lostmsu 50a3822
corrected reference counting in Codecs
lostmsu 2e19f2c
don't dispose encoded object in case codec keeps a cache of them
lostmsu 44bfec2
Merge branch 'master' into PR/Codecs
filmor 399ae54
do not dispose object, that might have been just decoded succesfully,…
lostmsu e2d3333
remove incref for tuple fields, as Converter.ToPython is supposed to …
lostmsu 5619fb9
Merge branch 'master' into PR/Codecs
filmor 41de69d
Merge branch 'master' into PR/Codecs
lostmsu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
namespace Python.EmbeddingTest { | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using NUnit.Framework; | ||
using Python.Runtime; | ||
using Python.Runtime.Codecs; | ||
|
||
public class Codecs { | ||
[SetUp] | ||
public void SetUp() { | ||
PythonEngine.Initialize(); | ||
} | ||
|
||
[TearDown] | ||
public void Dispose() { | ||
PythonEngine.Shutdown(); | ||
} | ||
|
||
[Test] | ||
public void ConversionsGeneric() { | ||
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
|
||
static void ConversionsGeneric<T, TTuple>() { | ||
TupleCodec<TTuple>.Register(); | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
T restored = default; | ||
using (Py.GIL()) | ||
using (var scope = Py.CreateScope()) { | ||
void Accept(T value) => restored = value; | ||
var accept = new Action<T>(Accept).ToPython(); | ||
scope.Set(nameof(tuple), tuple); | ||
scope.Set(nameof(accept), accept); | ||
scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
|
||
[Test] | ||
public void ConversionsObject() { | ||
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void ConversionsObject<T, TTuple>() { | ||
TupleCodec<TTuple>.Register(); | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
T restored = default; | ||
using (Py.GIL()) | ||
using (var scope = Py.CreateScope()) { | ||
void Accept(object value) => restored = (T)value; | ||
var accept = new Action<object>(Accept).ToPython(); | ||
scope.Set(nameof(tuple), tuple); | ||
scope.Set(nameof(accept), accept); | ||
scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
|
||
[Test] | ||
public void TupleRoundtripObject() { | ||
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void TupleRoundtripObject<T, TTuple>() { | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
using (Py.GIL()) { | ||
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored)); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
|
||
[Test] | ||
public void TupleRoundtripGeneric() { | ||
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
|
||
static void TupleRoundtripGeneric<T, TTuple>() { | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
using (Py.GIL()) { | ||
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored)); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<packages> | ||
<package id="NUnit" version="3.12.0" targetFramework="net40" /> | ||
<package id="NUnit.ConsoleRunner" version="3.11.1" targetFramework="net40" /> | ||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net40" /> | ||
</packages> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
namespace Python.Runtime.Codecs | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
[Obsolete(Util.UnstableApiMessage)] | ||
public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder | ||
{ | ||
TupleCodec() { } | ||
public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>(); | ||
|
||
public bool CanEncode(Type type) | ||
{ | ||
if (type == typeof(object) || type == typeof(TTuple)) return true; | ||
return type.Namespace == typeof(TTuple).Namespace | ||
// generic versions of tuples are named Tuple`TYPE_ARG_COUNT | ||
&& type.Name.StartsWith(typeof(TTuple).Name + '`'); | ||
} | ||
|
||
public PyObject TryEncode(object value) | ||
{ | ||
if (value == null) return null; | ||
|
||
var tupleType = value.GetType(); | ||
if (tupleType == typeof(object)) return null; | ||
if (!this.CanEncode(tupleType)) return null; | ||
if (tupleType == typeof(TTuple)) return new PyTuple(); | ||
|
||
long fieldCount = tupleType.GetGenericArguments().Length; | ||
var tuple = Runtime.PyTuple_New(fieldCount); | ||
Exceptions.ErrorCheck(tuple); | ||
int fieldIndex = 0; | ||
foreach (FieldInfo field in tupleType.GetFields()) | ||
{ | ||
var item = field.GetValue(value); | ||
IntPtr pyItem = Converter.ToPython(item); | ||
Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); | ||
fieldIndex++; | ||
} | ||
return new PyTuple(tuple); | ||
} | ||
|
||
public bool CanDecode(PyObject objectType, Type targetType) | ||
=> objectType.Handle == Runtime.PyTupleType && this.CanEncode(targetType); | ||
|
||
public bool TryDecode<T>(PyObject pyObj, out T value) | ||
{ | ||
if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); | ||
|
||
value = default; | ||
|
||
if (!Runtime.PyTuple_Check(pyObj.Handle)) return false; | ||
|
||
if (typeof(T) == typeof(object)) | ||
{ | ||
bool converted = Decode(pyObj, out object result); | ||
if (converted) | ||
{ | ||
value = (T)result; | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
var itemTypes = typeof(T).GetGenericArguments(); | ||
long itemCount = Runtime.PyTuple_Size(pyObj.Handle); | ||
if (itemTypes.Length != itemCount) return false; | ||
|
||
if (itemCount == 0) | ||
{ | ||
value = (T)EmptyTuple; | ||
return true; | ||
} | ||
|
||
var elements = new object[itemCount]; | ||
for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++) | ||
{ | ||
IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex); | ||
if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false)) | ||
{ | ||
return false; | ||
} | ||
} | ||
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
value = (T)factory.Invoke(null, elements); | ||
return true; | ||
} | ||
|
||
static bool Decode(PyObject tuple, out object value) | ||
{ | ||
long itemCount = Runtime.PyTuple_Size(tuple.Handle); | ||
if (itemCount == 0) | ||
{ | ||
value = EmptyTuple; | ||
return true; | ||
} | ||
var elements = new object[itemCount]; | ||
var itemTypes = new Type[itemCount]; | ||
value = null; | ||
for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++) | ||
{ | ||
var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex); | ||
if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false)) | ||
{ | ||
return false; | ||
} | ||
|
||
itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object); | ||
} | ||
|
||
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
value = factory.Invoke(null, elements); | ||
return true; | ||
} | ||
|
||
static readonly MethodInfo[] tupleCreate = | ||
typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static) | ||
.Where(m => m.Name == nameof(Tuple.Create)) | ||
.OrderBy(m => m.GetParameters().Length) | ||
.ToArray(); | ||
|
||
static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]); | ||
|
||
public static void Register() | ||
{ | ||
PyObjectConversions.RegisterEncoder(Instance); | ||
PyObjectConversions.RegisterDecoder(Instance); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intentional? @lostmsu
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think I am using a few features. Is there a C# compiler, that does not support 7.3 yet?
It is 7.3 in Runtime for a few months now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lostmsu just wanted to make sure it wasn't added by mistake.