Skip to content
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

Add support for deserializing python types #37

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions Bonsai.Sgen/CSharpClassTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,27 +295,5 @@ public override void BuildType(CodeTypeDeclaration type)
type.Members.Add(toStringMethod);
}
}

static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "bool", "System.Boolean" },
{ "byte", "System.Byte" },
{ "sbyte", "System.SByte" },
{ "char", "System.Char" },
{ "decimal", "System.Decimal" },
{ "double", "System.Double" },
{ "float", "System.Single" },
{ "int", "System.Int32" },
{ "uint", "System.UInt32" },
{ "nint", "System.IntPtr" },
{ "nuint", "System.UIntPtr" },
{ "long", "System.Int64" },
{ "ulong", "System.UInt64" },
{ "short", "System.Int16" },
{ "ushort", "System.UInt16" },

{ "object", "System.Object" },
{ "string", "System.String" }
};
}
}
7 changes: 7 additions & 0 deletions Bonsai.Sgen/CSharpCodeDomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ public override IEnumerable<CodeArtifact> GenerateTypes()
extraTypes.Add(GenerateClass(serializer));
extraTypes.Add(GenerateClass(deserializer));
}
if (Settings.SerializerLibraries.HasFlag(SerializerLibraries.PythonNet))
{
var codec = new CSharpPythonCodecTemplate(classTypes, _provider, _options, Settings);
var deserializer = new CSharpPythonDeserializerTemplate(schema, classTypes, _provider, _options, Settings);
extraTypes.Add(GenerateClass(codec));
extraTypes.Add(GenerateClass(deserializer));
}

return types.Select(ReplaceInitOnlyProperties).Concat(extraTypes);
}
Expand Down
26 changes: 25 additions & 1 deletion Bonsai.Sgen/CSharpCodeDomTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ private string GetVersionString()
{
serializerLibraries.Add(GetVersionString(YamlDotNetAssemblyName));
}
return $"{GeneratorAssemblyName.Version} ({string.Join(", ", serializerLibraries)})";
return serializerLibraries.Count > 0
? $"{GeneratorAssemblyName.Version} ({string.Join(", ", serializerLibraries)})"
: GeneratorAssemblyName.Version!.ToString();
}

public string Render()
Expand All @@ -63,5 +65,27 @@ public string Render()
Provider.GenerateCodeFromType(type, writer, Options);
return writer.ToString();
}

protected static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "bool", "System.Boolean" },
{ "byte", "System.Byte" },
{ "sbyte", "System.SByte" },
{ "char", "System.Char" },
{ "decimal", "System.Decimal" },
{ "double", "System.Double" },
{ "float", "System.Single" },
{ "int", "System.Int32" },
{ "uint", "System.UInt32" },
{ "nint", "System.IntPtr" },
{ "nuint", "System.UIntPtr" },
{ "long", "System.Int64" },
{ "ulong", "System.UInt64" },
{ "short", "System.Int16" },
{ "ushort", "System.UInt16" },

{ "object", "System.Object" },
{ "string", "System.String" }
};
}
}
127 changes: 127 additions & 0 deletions Bonsai.Sgen/CSharpPythonCodecTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.CodeDom;
using System.CodeDom.Compiler;
using NJsonSchema.CodeGeneration;

namespace Bonsai.Sgen
{
internal class CSharpPythonCodecTemplate : CSharpSerializerTemplate
{
const string DataVariableName = "value";
static readonly CodeTypeReference PyObjectTypeReference = new("Python.Runtime.PyObject");

public CSharpPythonCodecTemplate(
IEnumerable<CodeArtifact> modelTypes,
CodeDomProvider provider,
CodeGeneratorOptions options,
CSharpCodeDomGeneratorSettings settings)
: base(modelTypes, provider, options, settings)
{
}

public override string Description => "Provides a collection of methods for converting generic Python objects into data model objects.";

public override string TypeName => "PyObjectConverter";

public override void BuildType(CodeTypeDeclaration type)
{
base.BuildType(type);
type.BaseTypes.Add("Bonsai.Sink");
type.BaseTypes.Add("Python.Runtime.IPyObjectDecoder");

var decoderItems = new List<string>();
var decoderDictionaryMember = new CodeSnippetTypeMember();
type.Members.Add(decoderDictionaryMember);

type.Members.Add(new CodeSnippetTypeMember(
@" private static T GetAttr<T>(Python.Runtime.PyObject pyObj, string attributeName)
{
using (var attr = pyObj.GetAttr(attributeName))
{
return attr.As<T>();
}
}

static System.Func<Python.Runtime.PyObject, object> CreateDecoder<T>(System.Action<Python.Runtime.PyObject, T> decode)
where T : new()
{
return pyObj =>
{
T value = new T();
decode(pyObj, value);
return value;
};
}

public bool CanDecode(Python.Runtime.PyType objectType, System.Type targetType)
{
return decoders.ContainsKey(targetType);
}

public bool TryDecode<T>(Python.Runtime.PyObject pyObj, out T value)
{
System.Func<Python.Runtime.PyObject, object> decoder;
if (decoders.TryGetValue(typeof(T), out decoder))
{
value = (T)decoder(pyObj);
return true;
}
value = default(T);
return false;
}"));

foreach (var modelType in ModelTypes.Cast<CSharpClassCodeArtifact>())
{
var model = modelType.Model;
var modelTypeReference = new CodeTypeReference(model.ClassName);
var decodeMethod = new CodeMemberMethod
{
Name = $"Decode{model.ClassName}",
Attributes = MemberAttributes.Static,
Parameters = { new(PyObjectTypeReference, "pyObj"), new(modelTypeReference, DataVariableName) },
};

var modelObjectReference = new CodeVariableReferenceExpression(DataVariableName);
var pyObjectReference = new CodeVariableReferenceExpression("pyObj");
if (model.BaseClass != null)
{
decodeMethod.Statements.Add(new CodeMethodInvokeExpression(
targetObject: null,
$"Decode{model.BaseClassName}",
pyObjectReference,
modelObjectReference));
}

foreach (var property in model.Properties)
{
var isPrimitive = PrimitiveTypes.TryGetValue(property.Type, out string? underlyingType);
var propertyTypeReference = new CodeTypeReference(isPrimitive ? underlyingType : property.Type);
decodeMethod.Statements.Add(new CodeAssignStatement(
new CodePropertyReferenceExpression(modelObjectReference, property.PropertyName),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(null, "GetAttr", propertyTypeReference),
pyObjectReference,
new CodePrimitiveExpression(property.Name))));
}
type.Members.Add(decodeMethod);
if (!model.IsAbstract)
{
decoderItems.Add($" {{ typeof({model.ClassName}), CreateDecoder<{model.ClassName}>({decodeMethod.Name}) }}");
}
}

decoderDictionaryMember.Text =
@$" static readonly System.Collections.Generic.Dictionary<System.Type, System.Func<Python.Runtime.PyObject, object>> decoders =
new System.Collections.Generic.Dictionary<System.Type, System.Func<Python.Runtime.PyObject, object>>
{{
{string.Join($",{Environment.NewLine}", decoderItems)}
}};
";

type.Members.Add(new CodeSnippetTypeMember(
@" public override System.IObservable<TSource> Process<TSource>(System.IObservable<TSource> source)
{
return System.Reactive.Linq.Observable.Do(source, _ => Python.Runtime.PyObjectConversions.RegisterDecoder(this));
}"));
}
}
}
33 changes: 33 additions & 0 deletions Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.CodeDom;
using System.CodeDom.Compiler;
using NJsonSchema;

namespace Bonsai.Sgen
{
internal class CSharpPythonDeserializerTemplate : CSharpDeserializerTemplate
{
public CSharpPythonDeserializerTemplate(
JsonSchema schema,
IEnumerable<CSharpClassCodeArtifact> modelTypes,
CodeDomProvider provider,
CodeGeneratorOptions options,
CSharpCodeDomGeneratorSettings settings)
: base(schema, modelTypes, provider, options, settings)
{
}

public override string Description => "Converts a sequence of generic Python objects into data model objects.";

public override string TypeName => "FromPython";

public override void BuildType(CodeTypeDeclaration type)
{
base.BuildType(type);
type.Members.Add(new CodeSnippetTypeMember(
@" private static System.IObservable<T> Process<T>(System.IObservable<Python.Runtime.PyObject> source)
{
return System.Reactive.Linq.Observable.Select(source, value => value.As<T>());
}"));
}
}
}
1 change: 1 addition & 0 deletions Bonsai.Sgen/SerializerLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public enum SerializerLibraries
None = 0x0,
NewtonsoftJson = 0x1,
YamlDotNet = 0x4,
PythonNet = 0x8
}
}
Loading