X Tutup
Skip to content

Commit b19e188

Browse files
committed
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 925c166 commit b19e188

File tree

7 files changed

+324
-8
lines changed

7 files changed

+324
-8
lines changed

src/embed_tests/CodecGroups.cs

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

src/embed_tests/Codecs.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,33 @@ static void TupleRoundtripGeneric<T, TTuple>() {
8383
}
8484
}
8585
}
86+
87+
class FakeEncoder<T> : IPyObjectEncoder
88+
{
89+
public bool CanEncode(Type type) => type == typeof(T);
90+
public PyObject TryEncode(object value) => this.GetRawPythonProxy();
91+
}
92+
93+
class FakeDecoder<TTarget> : IPyObjectDecoder
94+
{
95+
public PyObject TheOnlySupportedSourceType { get; }
96+
public TTarget DecodeResult { get; }
97+
98+
public FakeDecoder(PyObject objectType, TTarget decodeResult)
99+
{
100+
this.TheOnlySupportedSourceType = objectType;
101+
this.DecodeResult = decodeResult;
102+
}
103+
104+
public bool CanDecode(PyObject objectType, Type targetType)
105+
=> objectType.Handle == TheOnlySupportedSourceType.Handle
106+
&& targetType == typeof(TTarget);
107+
public bool TryDecode<T>(PyObject pyObj, out T value)
108+
{
109+
if (typeof(T) != typeof(TTarget))
110+
throw new ArgumentException(nameof(T));
111+
value = (T)(object)DecodeResult;
112+
return true;
113+
}
114+
}
86115
}

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 1 addition & 0 deletions
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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
/// <summary>
16+
/// Gets a modifiable collection of decoders in this group.
17+
/// </summary>
18+
public ICollection<IPyObjectDecoder> Decoders => this.decoders;
19+
20+
/// <inheritdoc />
21+
public bool CanDecode(PyObject objectType, Type targetType)
22+
=> this.decoders.Any(decoder => decoder.CanDecode(objectType, targetType));
23+
/// <inheritdoc />
24+
public bool TryDecode<T>(PyObject pyObj, out T value)
25+
{
26+
if (pyObj is null) throw new ArgumentNullException(nameof(pyObj));
27+
28+
var decoder = this.GetDecoder(pyObj.GetPythonType(), typeof(T));
29+
if (decoder is null)
30+
{
31+
value = default;
32+
return false;
33+
}
34+
return decoder.TryDecode(pyObj, out value);
35+
}
36+
37+
/// <inheritdoc />
38+
public IEnumerator<IPyObjectDecoder> GetEnumerator() => this.decoders.GetEnumerator();
39+
IEnumerator IEnumerable.GetEnumerator() => this.decoders.GetEnumerator();
40+
}
41+
42+
[Obsolete(Util.UnstableApiMessage)]
43+
public static class DecoderGroupExtensions
44+
{
45+
/// <summary>
46+
/// Gets a concrete instance of <see cref="IPyObjectDecoder"/>
47+
/// (potentially selecting one from a collection),
48+
/// that can decode from <paramref name="objectType"/> to <paramref name="targetType"/>,
49+
/// or <c>null</c> if a matching decoder can not be found.
50+
/// </summary>
51+
[Obsolete(Util.UnstableApiMessage)]
52+
public static IPyObjectDecoder GetDecoder(
53+
this IPyObjectDecoder decoder,
54+
PyObject objectType, Type targetType)
55+
{
56+
if (decoder is null) throw new ArgumentNullException(nameof(decoder));
57+
58+
if (decoder is IEnumerable<IPyObjectDecoder> composite)
59+
{
60+
return composite
61+
.Select(nestedDecoder => nestedDecoder.GetDecoder(objectType, targetType))
62+
.FirstOrDefault(d => d != null);
63+
}
64+
65+
return decoder.CanDecode(objectType, targetType) ? decoder : null;
66+
}
67+
}
68+
}

src/runtime/Codecs/EncoderGroup.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
/// <summary>
16+
/// Gets a modifiable collection of encoders in this group.
17+
/// </summary>
18+
public ICollection<IPyObjectEncoder> Encoders => this.encoders;
19+
20+
/// <inheritdoc />
21+
public bool CanEncode(Type type) => this.encoders.Any(encoder => encoder.CanEncode(type));
22+
/// <inheritdoc />
23+
public PyObject TryEncode(object value)
24+
{
25+
if (value is null) throw new ArgumentNullException(nameof(value));
26+
27+
foreach (var encoder in this.GetEncoders(value.GetType()))
28+
{
29+
var result = encoder.TryEncode(value);
30+
if (result != null)
31+
{
32+
return result;
33+
}
34+
}
35+
36+
return null;
37+
}
38+
39+
/// <inheritdoc />
40+
public IEnumerator<IPyObjectEncoder> GetEnumerator() => this.encoders.GetEnumerator();
41+
IEnumerator IEnumerable.GetEnumerator() => this.encoders.GetEnumerator();
42+
}
43+
44+
[Obsolete(Util.UnstableApiMessage)]
45+
public static class EncoderGroupExtensions
46+
{
47+
/// <summary>
48+
/// Gets specific instances of <see cref="IPyObjectEncoder"/>
49+
/// (potentially selecting one from a collection),
50+
/// that can encode the specified <paramref name="type"/>.
51+
/// </summary>
52+
[Obsolete(Util.UnstableApiMessage)]
53+
public static IEnumerable<IPyObjectEncoder> GetEncoders(this IPyObjectEncoder decoder, Type type)
54+
{
55+
if (decoder is null) throw new ArgumentNullException(nameof(decoder));
56+
57+
if (decoder is IEnumerable<IPyObjectEncoder> composite)
58+
{
59+
foreach (var nestedEncoder in composite)
60+
foreach (var match in nestedEncoder.GetEncoders(type))
61+
{
62+
yield return match;
63+
}
64+
} else if (decoder.CanEncode(type))
65+
{
66+
yield return decoder;
67+
}
68+
}
69+
}
70+
}

src/runtime/Python.Runtime.csproj

Lines changed: 2 additions & 0 deletions
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" />

0 commit comments

Comments
 (0)
X Tutup