Skip to content

Commit 555f562

Browse files
authored
Codec groups: EncoderGroup and DecoderGroup (#1085)
* Added Codecs: EncoderGroup and DecoderGroup These classes would help to manage codec layers. For example, a library could register its own codecs, but also allow anyone to inject their codecs before library's own: public static EncoderGroup BeforeLibraryEncoders { get; } = new EncoderGroup(); void LibraryRegisterCodecs(){ PyObjectConversions.RegisterEncoder(BeforeLibraryEncoders); PyObjectConversions.RegisterEncoder(LibraryEncoder.Instance); } Then in a program using that library: Library.BeforeLibraryEncoders.Encoders.Add(preencoder);
1 parent 92bc145 commit 555f562

File tree

7 files changed

+334
-4
lines changed

7 files changed

+334
-4
lines changed

src/embed_tests/CodecGroups.cs

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
namespace Python.EmbeddingTest
2+
{
3+
using System;
4+
using System.Linq;
5+
using NUnit.Framework;
6+
using Python.Runtime;
7+
using Python.Runtime.Codecs;
8+
9+
public class CodecGroups
10+
{
11+
[Test]
12+
public void GetEncodersByType()
13+
{
14+
var encoder1 = new ObjectToRawProxyEncoder<Uri>();
15+
var encoder2 = new ObjectToRawProxyEncoder<Uri>();
16+
var group = new EncoderGroup {
17+
new ObjectToRawProxyEncoder<Tuple<int>>(),
18+
encoder1,
19+
encoder2,
20+
};
21+
22+
var got = group.GetEncoders(typeof(Uri)).ToArray();
23+
CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got);
24+
}
25+
26+
[Test]
27+
public void CanEncode()
28+
{
29+
var group = new EncoderGroup {
30+
new ObjectToRawProxyEncoder<Tuple<int>>(),
31+
new ObjectToRawProxyEncoder<Uri>(),
32+
};
33+
34+
Assert.IsTrue(group.CanEncode(typeof(Tuple<int>)));
35+
Assert.IsTrue(group.CanEncode(typeof(Uri)));
36+
Assert.IsFalse(group.CanEncode(typeof(string)));
37+
}
38+
39+
[Test]
40+
public void Encodes()
41+
{
42+
var encoder0 = new ObjectToRawProxyEncoder<Tuple<int>>();
43+
var encoder1 = new ObjectToRawProxyEncoder<Uri>();
44+
var encoder2 = new ObjectToRawProxyEncoder<Uri>();
45+
var group = new EncoderGroup {
46+
encoder0,
47+
encoder1,
48+
encoder2,
49+
};
50+
51+
var uri = group.TryEncode(new Uri("data:"));
52+
var clrObject = (CLRObject)ManagedType.GetManagedObject(uri.Handle);
53+
Assert.AreSame(encoder1, clrObject.inst);
54+
Assert.AreNotSame(encoder2, clrObject.inst);
55+
56+
var tuple = group.TryEncode(Tuple.Create(1));
57+
clrObject = (CLRObject)ManagedType.GetManagedObject(tuple.Handle);
58+
Assert.AreSame(encoder0, clrObject.inst);
59+
}
60+
61+
[Test]
62+
public void GetDecodersByTypes()
63+
{
64+
var pyint = new PyInt(10).GetPythonType();
65+
var pyfloat = new PyFloat(10).GetPythonType();
66+
var pystr = new PyString("world").GetPythonType();
67+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
68+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
69+
var group = new DecoderGroup {
70+
decoder1,
71+
decoder2,
72+
};
73+
74+
var decoder = group.GetDecoder(pyfloat, typeof(string));
75+
Assert.AreSame(decoder2, decoder);
76+
decoder = group.GetDecoder(pystr, typeof(string));
77+
Assert.IsNull(decoder);
78+
decoder = group.GetDecoder(pyint, typeof(long));
79+
Assert.AreSame(decoder1, decoder);
80+
}
81+
[Test]
82+
public void CanDecode()
83+
{
84+
var pyint = new PyInt(10).GetPythonType();
85+
var pyfloat = new PyFloat(10).GetPythonType();
86+
var pystr = new PyString("world").GetPythonType();
87+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
88+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
89+
var group = new DecoderGroup {
90+
decoder1,
91+
decoder2,
92+
};
93+
94+
Assert.IsTrue(group.CanDecode(pyint, typeof(long)));
95+
Assert.IsFalse(group.CanDecode(pyint, typeof(int)));
96+
Assert.IsTrue(group.CanDecode(pyfloat, typeof(string)));
97+
Assert.IsFalse(group.CanDecode(pystr, typeof(string)));
98+
}
99+
100+
[Test]
101+
public void Decodes()
102+
{
103+
var pyint = new PyInt(10).GetPythonType();
104+
var pyfloat = new PyFloat(10).GetPythonType();
105+
var decoder1 = new DecoderReturningPredefinedValue<long>(pyint, decodeResult: 42);
106+
var decoder2 = new DecoderReturningPredefinedValue<string>(pyfloat, decodeResult: "atad:");
107+
var group = new DecoderGroup {
108+
decoder1,
109+
decoder2,
110+
};
111+
112+
Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult));
113+
Assert.AreEqual(42, longResult);
114+
Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult));
115+
Assert.AreSame("atad:", strResult);
116+
117+
Assert.IsFalse(group.TryDecode(new PyInt(10), out int _));
118+
}
119+
120+
[SetUp]
121+
public void SetUp()
122+
{
123+
PythonEngine.Initialize();
124+
}
125+
126+
[TearDown]
127+
public void Dispose()
128+
{
129+
PythonEngine.Shutdown();
130+
}
131+
}
132+
}

src/embed_tests/Codecs.cs

+37
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,41 @@ static void TupleRoundtripGeneric<T, TTuple>() {
8383
}
8484
}
8585
}
86+
87+
/// <summary>
88+
/// "Decodes" only objects of exact type <typeparamref name="T"/>.
89+
/// Result is just a raw Python object proxy.
90+
/// </summary>
91+
class ObjectToRawProxyEncoder<T> : IPyObjectEncoder
92+
{
93+
public bool CanEncode(Type type) => type == typeof(T);
94+
public PyObject TryEncode(object value) => this.GetRawPythonProxy();
95+
}
96+
97+
/// <summary>
98+
/// Decodes object of specified Python type to the predefined value <see cref="DecodeResult"/>
99+
/// </summary>
100+
/// <typeparam name="TTarget">Type of the <see cref="DecodeResult"/></typeparam>
101+
class DecoderReturningPredefinedValue<TTarget> : IPyObjectDecoder
102+
{
103+
public PyObject TheOnlySupportedSourceType { get; }
104+
public TTarget DecodeResult { get; }
105+
106+
public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult)
107+
{
108+
this.TheOnlySupportedSourceType = objectType;
109+
this.DecodeResult = decodeResult;
110+
}
111+
112+
public bool CanDecode(PyObject objectType, Type targetType)
113+
=> objectType.Handle == TheOnlySupportedSourceType.Handle
114+
&& targetType == typeof(TTarget);
115+
public bool TryDecode<T>(PyObject pyObj, out T value)
116+
{
117+
if (typeof(T) != typeof(TTarget))
118+
throw new ArgumentException(nameof(T));
119+
value = (T)(object)DecodeResult;
120+
return true;
121+
}
122+
}
86123
}

src/embed_tests/Python.EmbeddingTest.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<None Include="packages.config" />
8484
</ItemGroup>
8585
<ItemGroup>
86+
<Compile Include="CodecGroups.cs" />
8687
<Compile Include="Codecs.cs" />
8788
<Compile Include="dynamic.cs" />
8889
<Compile Include="pyimport.cs" />

src/runtime/Codecs/DecoderGroup.cs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
namespace Python.Runtime.Codecs
2+
{
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
/// <summary>
9+
/// Represents a group of <see cref="IPyObjectDecoder"/>s. Useful to group them by priority.
10+
/// </summary>
11+
[Obsolete(Util.UnstableApiMessage)]
12+
public sealed class DecoderGroup: IPyObjectDecoder, IEnumerable<IPyObjectDecoder>
13+
{
14+
readonly List<IPyObjectDecoder> decoders = new List<IPyObjectDecoder>();
15+
16+
/// <summary>
17+
/// Add specified decoder to the group
18+
/// </summary>
19+
public void Add(IPyObjectDecoder item)
20+
{
21+
if (item is null) throw new ArgumentNullException(nameof(item));
22+
23+
this.decoders.Add(item);
24+
}
25+
/// <summary>
26+
/// Remove all decoders from the group
27+
/// </summary>
28+
public void Clear() => this.decoders.Clear();
29+
30+
/// <inheritdoc />
31+
public bool CanDecode(PyObject objectType, Type targetType)
32+
=> this.decoders.Any(decoder => decoder.CanDecode(objectType, targetType));
33+
/// <inheritdoc />
34+
public bool TryDecode<T>(PyObject pyObj, out T value)
35+
{
36+
if (pyObj is null) throw new ArgumentNullException(nameof(pyObj));
37+
38+
var decoder = this.GetDecoder(pyObj.GetPythonType(), typeof(T));
39+
if (decoder is null)
40+
{
41+
value = default;
42+
return false;
43+
}
44+
return decoder.TryDecode(pyObj, out value);
45+
}
46+
47+
/// <inheritdoc />
48+
public IEnumerator<IPyObjectDecoder> GetEnumerator() => this.decoders.GetEnumerator();
49+
IEnumerator IEnumerable.GetEnumerator() => this.decoders.GetEnumerator();
50+
}
51+
52+
[Obsolete(Util.UnstableApiMessage)]
53+
public static class DecoderGroupExtensions
54+
{
55+
/// <summary>
56+
/// Gets a concrete instance of <see cref="IPyObjectDecoder"/>
57+
/// (potentially selecting one from a collection),
58+
/// that can decode from <paramref name="objectType"/> to <paramref name="targetType"/>,
59+
/// or <c>null</c> if a matching decoder can not be found.
60+
/// </summary>
61+
[Obsolete(Util.UnstableApiMessage)]
62+
public static IPyObjectDecoder GetDecoder(
63+
this IPyObjectDecoder decoder,
64+
PyObject objectType, Type targetType)
65+
{
66+
if (decoder is null) throw new ArgumentNullException(nameof(decoder));
67+
68+
if (decoder is IEnumerable<IPyObjectDecoder> composite)
69+
{
70+
return composite
71+
.Select(nestedDecoder => nestedDecoder.GetDecoder(objectType, targetType))
72+
.FirstOrDefault(d => d != null);
73+
}
74+
75+
return decoder.CanDecode(objectType, targetType) ? decoder : null;
76+
}
77+
}
78+
}

src/runtime/Codecs/EncoderGroup.cs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
namespace Python.Runtime.Codecs
2+
{
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
/// <summary>
9+
/// Represents a group of <see cref="IPyObjectDecoder"/>s. Useful to group them by priority.
10+
/// </summary>
11+
[Obsolete(Util.UnstableApiMessage)]
12+
public sealed class EncoderGroup: IPyObjectEncoder, IEnumerable<IPyObjectEncoder>
13+
{
14+
readonly List<IPyObjectEncoder> encoders = new List<IPyObjectEncoder>();
15+
16+
/// <summary>
17+
/// Add specified encoder to the group
18+
/// </summary>
19+
public void Add(IPyObjectEncoder item)
20+
{
21+
if (item is null) throw new ArgumentNullException(nameof(item));
22+
this.encoders.Add(item);
23+
}
24+
/// <summary>
25+
/// Remove all encoders from the group
26+
/// </summary>
27+
public void Clear() => this.encoders.Clear();
28+
29+
/// <inheritdoc />
30+
public bool CanEncode(Type type) => this.encoders.Any(encoder => encoder.CanEncode(type));
31+
/// <inheritdoc />
32+
public PyObject TryEncode(object value)
33+
{
34+
if (value is null) throw new ArgumentNullException(nameof(value));
35+
36+
foreach (var encoder in this.GetEncoders(value.GetType()))
37+
{
38+
var result = encoder.TryEncode(value);
39+
if (result != null)
40+
{
41+
return result;
42+
}
43+
}
44+
45+
return null;
46+
}
47+
48+
/// <inheritdoc />
49+
public IEnumerator<IPyObjectEncoder> GetEnumerator() => this.encoders.GetEnumerator();
50+
IEnumerator IEnumerable.GetEnumerator() => this.encoders.GetEnumerator();
51+
}
52+
53+
[Obsolete(Util.UnstableApiMessage)]
54+
public static class EncoderGroupExtensions
55+
{
56+
/// <summary>
57+
/// Gets specific instances of <see cref="IPyObjectEncoder"/>
58+
/// (potentially selecting one from a collection),
59+
/// that can encode the specified <paramref name="type"/>.
60+
/// </summary>
61+
[Obsolete(Util.UnstableApiMessage)]
62+
public static IEnumerable<IPyObjectEncoder> GetEncoders(this IPyObjectEncoder decoder, Type type)
63+
{
64+
if (decoder is null) throw new ArgumentNullException(nameof(decoder));
65+
66+
if (decoder is IEnumerable<IPyObjectEncoder> composite)
67+
{
68+
foreach (var nestedEncoder in composite)
69+
foreach (var match in nestedEncoder.GetEncoders(type))
70+
{
71+
yield return match;
72+
}
73+
} else if (decoder.CanEncode(type))
74+
{
75+
yield return decoder;
76+
}
77+
}
78+
}
79+
}

src/runtime/Python.Runtime.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
<Reference Include="System" />
7777
</ItemGroup>
7878
<ItemGroup>
79+
<Compile Include="Codecs\EncoderGroup.cs" />
80+
<Compile Include="Codecs\DecoderGroup.cs" />
7981
<Compile Include="Codecs\TupleCodecs.cs" />
8082
<Compile Include="converterextensions.cs" />
8183
<Compile Include="finalizer.cs" />

src/runtime/converterextensions.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Python.Runtime
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Reflection;
8+
using Python.Runtime.Codecs;
89

910
/// <summary>
1011
/// Defines <see cref="PyObject"/> conversion to CLR types (unmarshalling)
@@ -49,8 +50,8 @@ public interface IPyObjectEncoder
4950
[Obsolete(Util.UnstableApiMessage)]
5051
public static class PyObjectConversions
5152
{
52-
static readonly List<IPyObjectDecoder> decoders = new List<IPyObjectDecoder>();
53-
static readonly List<IPyObjectEncoder> encoders = new List<IPyObjectEncoder>();
53+
static readonly DecoderGroup decoders = new DecoderGroup();
54+
static readonly EncoderGroup encoders = new EncoderGroup();
5455

5556
/// <summary>
5657
/// Registers specified encoder (marshaller)
@@ -101,7 +102,7 @@ static IPyObjectEncoder[] GetEncoders(Type type)
101102
{
102103
lock (encoders)
103104
{
104-
return encoders.Where(encoder => encoder.CanEncode(type)).ToArray();
105+
return encoders.GetEncoders(type).ToArray();
105106
}
106107
}
107108
#endregion
@@ -128,7 +129,7 @@ static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type
128129
{
129130
lock (decoders)
130131
{
131-
decoder = decoders.Find(d => d.CanDecode(pyType, targetType));
132+
decoder = decoders.GetDecoder(pyType, targetType);
132133
if (decoder == null) return null;
133134
}
134135
}

0 commit comments

Comments
 (0)