From f4ed84bd2c3c0c05bca2d9cbd4b14d2e01617273 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 10 Mar 2024 21:32:47 +0000 Subject: [PATCH 1/4] Move primitive type dictionary to base class --- Bonsai.Sgen/CSharpClassTemplate.cs | 22 ---------------------- Bonsai.Sgen/CSharpCodeDomTemplate.cs | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Bonsai.Sgen/CSharpClassTemplate.cs b/Bonsai.Sgen/CSharpClassTemplate.cs index 001596f..dd7d3d3 100644 --- a/Bonsai.Sgen/CSharpClassTemplate.cs +++ b/Bonsai.Sgen/CSharpClassTemplate.cs @@ -295,27 +295,5 @@ public override void BuildType(CodeTypeDeclaration type) type.Members.Add(toStringMethod); } } - - static readonly Dictionary 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" } - }; } } diff --git a/Bonsai.Sgen/CSharpCodeDomTemplate.cs b/Bonsai.Sgen/CSharpCodeDomTemplate.cs index 97c1006..ec495bd 100644 --- a/Bonsai.Sgen/CSharpCodeDomTemplate.cs +++ b/Bonsai.Sgen/CSharpCodeDomTemplate.cs @@ -63,5 +63,27 @@ public string Render() Provider.GenerateCodeFromType(type, writer, Options); return writer.ToString(); } + + protected static readonly Dictionary 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" } + }; } } From 9119bfe4c3f4a4c816d54612c64d91e51d3fe198 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 10 Mar 2024 21:35:14 +0000 Subject: [PATCH 2/4] Add support for deserializing python types --- Bonsai.Sgen/CSharpCodeDomGenerator.cs | 5 +++ .../CSharpPythonDeserializerTemplate.cs | 33 +++++++++++++++++++ Bonsai.Sgen/SerializerLibraries.cs | 1 + 3 files changed, 39 insertions(+) create mode 100644 Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs diff --git a/Bonsai.Sgen/CSharpCodeDomGenerator.cs b/Bonsai.Sgen/CSharpCodeDomGenerator.cs index aa5d1fb..3477bd7 100644 --- a/Bonsai.Sgen/CSharpCodeDomGenerator.cs +++ b/Bonsai.Sgen/CSharpCodeDomGenerator.cs @@ -132,6 +132,11 @@ public override IEnumerable GenerateTypes() extraTypes.Add(GenerateClass(serializer)); extraTypes.Add(GenerateClass(deserializer)); } + if (Settings.SerializerLibraries.HasFlag(SerializerLibraries.PythonNet)) + { + var deserializer = new CSharpPythonDeserializerTemplate(schema, classTypes, _provider, _options, Settings); + extraTypes.Add(GenerateClass(deserializer)); + } return types.Select(ReplaceInitOnlyProperties).Concat(extraTypes); } diff --git a/Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs b/Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs new file mode 100644 index 0000000..0806f49 --- /dev/null +++ b/Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs @@ -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 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 Process(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, value => value.As()); + }")); + } + } +} diff --git a/Bonsai.Sgen/SerializerLibraries.cs b/Bonsai.Sgen/SerializerLibraries.cs index 13cfbbc..802f36a 100644 --- a/Bonsai.Sgen/SerializerLibraries.cs +++ b/Bonsai.Sgen/SerializerLibraries.cs @@ -6,5 +6,6 @@ public enum SerializerLibraries None = 0x0, NewtonsoftJson = 0x1, YamlDotNet = 0x4, + PythonNet = 0x8 } } From 13505c97aa3ea54286d7d1496f80700c261e3ebd Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 10 Mar 2024 21:42:29 +0000 Subject: [PATCH 3/4] Add generator for python object decoder --- Bonsai.Sgen/CSharpCodeDomGenerator.cs | 2 + Bonsai.Sgen/CSharpPythonCodecTemplate.cs | 127 +++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 Bonsai.Sgen/CSharpPythonCodecTemplate.cs diff --git a/Bonsai.Sgen/CSharpCodeDomGenerator.cs b/Bonsai.Sgen/CSharpCodeDomGenerator.cs index 3477bd7..83c7c01 100644 --- a/Bonsai.Sgen/CSharpCodeDomGenerator.cs +++ b/Bonsai.Sgen/CSharpCodeDomGenerator.cs @@ -134,7 +134,9 @@ public override IEnumerable GenerateTypes() } 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)); } diff --git a/Bonsai.Sgen/CSharpPythonCodecTemplate.cs b/Bonsai.Sgen/CSharpPythonCodecTemplate.cs new file mode 100644 index 0000000..f81d1b2 --- /dev/null +++ b/Bonsai.Sgen/CSharpPythonCodecTemplate.cs @@ -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 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(); + var decoderDictionaryMember = new CodeSnippetTypeMember(); + type.Members.Add(decoderDictionaryMember); + + type.Members.Add(new CodeSnippetTypeMember( +@" private static T GetAttr(Python.Runtime.PyObject pyObj, string attributeName) + { + using (var attr = pyObj.GetAttr(attributeName)) + { + return attr.As(); + } + } + + static System.Func CreateDecoder(System.Action 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(Python.Runtime.PyObject pyObj, out T value) + { + System.Func 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()) + { + 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> decoders = + new System.Collections.Generic.Dictionary> + {{ +{string.Join($",{Environment.NewLine}", decoderItems)} + }}; +"; + + type.Members.Add(new CodeSnippetTypeMember( +@" public override System.IObservable Process(System.IObservable source) + { + return System.Reactive.Linq.Observable.Do(source, _ => Python.Runtime.PyObjectConversions.RegisterDecoder(this)); + }")); + } + } +} From 65d9c68e72b63efcba7b13cb24244ad6658dd570 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 10 Mar 2024 22:00:50 +0000 Subject: [PATCH 4/4] Avoid suffix if no annotated libraries are used --- Bonsai.Sgen/CSharpCodeDomTemplate.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Bonsai.Sgen/CSharpCodeDomTemplate.cs b/Bonsai.Sgen/CSharpCodeDomTemplate.cs index ec495bd..f9d0a7b 100644 --- a/Bonsai.Sgen/CSharpCodeDomTemplate.cs +++ b/Bonsai.Sgen/CSharpCodeDomTemplate.cs @@ -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()