diff --git a/go.mod b/go.mod index f2b28e6..0b64a44 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/cel-go v0.21.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 - github.com/kralicky/codegen v0.0.0-20240818201632-5c040977c0f2 + github.com/kralicky/codegen v0.0.0-20240818203230-c3317b3cf0bc github.com/kralicky/gpkg v0.0.0-20240119195700-64f32830b14f github.com/kralicky/protocompile v0.0.0-20240803192845-d63fb51209b3 github.com/kralicky/tools-lite v0.0.0-20240313161632-60bfa88304ff diff --git a/go.sum b/go.sum index 90574b5..9eccc45 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kralicky/codegen v0.0.0-20240818201632-5c040977c0f2 h1:2NkpnOtqGv2iPffuKQtsAjXajf9dm3IikdTgmxOp0Ig= github.com/kralicky/codegen v0.0.0-20240818201632-5c040977c0f2/go.mod h1:UK8m9KaVDa19tqN9IssexUx+Nz5Z+5ZNZKGzX2saNC4= +github.com/kralicky/codegen v0.0.0-20240818203230-c3317b3cf0bc h1:RS6a4zzzlGAlR07VfLypyj9N3xC0T/BZrezcUDRuTls= +github.com/kralicky/codegen v0.0.0-20240818203230-c3317b3cf0bc/go.mod h1:UK8m9KaVDa19tqN9IssexUx+Nz5Z+5ZNZKGzX2saNC4= github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c h1:TRkEV8M5PhQU55WI49FKTszEIpFlwZ1wfxcACCRT7SE= github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c/go.mod h1:oJwexVSshEat0E3evyKOH6QzN8GFWrhLvEoh8GiJzss= github.com/kralicky/gpkg v0.0.0-20240119195700-64f32830b14f h1:MsNe8A51V+7Fu5OMXSl8SK02erPJ40vFs2zDHn89w1g= diff --git a/sdk/codegen/generators/x/python/doc.go b/sdk/codegen/generators/x/python/doc.go deleted file mode 100644 index f14e9a6..0000000 --- a/sdk/codegen/generators/x/python/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Migrated from https://github.com/kralicky/ragu/tree/main/pkg/plugins/python -// -// This package is a pure-go implementation of a subset of features from -// python-betterproto (https://github.com/danielgtaylor/python-betterproto). -// Most of the logic here is translated directly from the original python code. -package python diff --git a/sdk/codegen/generators/x/python/generator.go b/sdk/codegen/generators/x/python/generator.go deleted file mode 100644 index 09e3ed9..0000000 --- a/sdk/codegen/generators/x/python/generator.go +++ /dev/null @@ -1,74 +0,0 @@ -package python - -import ( - _ "embed" - "encoding/json" - "path" - - "github.com/flosch/pongo2/v6" - "google.golang.org/protobuf/compiler/protogen" - "google.golang.org/protobuf/reflect/protodesc" - "google.golang.org/protobuf/types/descriptorpb" -) - -//go:embed template.py.j2 -var template []byte - -var Generator = generator{} - -type generator struct{} - -func (generator) Name() string { - return "python" -} - -func (generator) Generate(gen *protogen.Plugin) error { - tpl, err := pongo2.FromBytes(template) - if err != nil { - return err - } - dirs := map[string]struct{}{} - for _, f := range gen.Files { - if f.Generate { - dirs[path.Dir(f.GeneratedFilenamePrefix)] = struct{}{} - filename := f.GeneratedFilenamePrefix + "_pb.py" - g := gen.NewGeneratedFile(filename, "") - - fd, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{ - File: gen.Request.ProtoFile, - }) - if err != nil { - return err - } - - f, err := fd.FindFileByPath(f.Proto.GetName()) - if err != nil { - return err - } - model := buildModel(f) - jsonData, err := json.Marshal(model) - if err != nil { - return err - } - modelMap := map[string]any{} - err = json.Unmarshal(jsonData, &modelMap) - if err != nil { - return err - } - - data, err := tpl.ExecuteBytes(modelMap) - if err != nil { - return err - } - - if _, err := g.Write(data); err != nil { - return err - } - } - } - for dir := range dirs { - gen.NewGeneratedFile(path.Join(dir, "__init__.py"), "") - } - - return nil -} diff --git a/sdk/codegen/generators/x/python/go.mod b/sdk/codegen/generators/x/python/go.mod deleted file mode 100644 index bef887b..0000000 --- a/sdk/codegen/generators/x/python/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/kralicky/protols/sdk/codegen/generators/x/python - -go 1.21 - -require ( - github.com/flosch/pongo2/v6 v6.0.0 - github.com/iancoleman/strcase v0.3.0 - google.golang.org/protobuf v1.34.2 -) - -require ( - github.com/google/go-cmp v0.6.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect -) diff --git a/sdk/codegen/generators/x/python/go.sum b/sdk/codegen/generators/x/python/go.sum deleted file mode 100644 index 70dd4b7..0000000 --- a/sdk/codegen/generators/x/python/go.sum +++ /dev/null @@ -1,11 +0,0 @@ -github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= -github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/sdk/codegen/generators/x/python/importing.go b/sdk/codegen/generators/x/python/importing.go deleted file mode 100644 index eb8239b..0000000 --- a/sdk/codegen/generators/x/python/importing.go +++ /dev/null @@ -1,144 +0,0 @@ -package python - -import ( - "fmt" - "path/filepath" - "regexp" - "slices" - "strings" - - "github.com/iancoleman/strcase" - "google.golang.org/protobuf/reflect/protoreflect" -) - -// https://github.com/danielgtaylor/python-betterproto/blob/master/src/betterproto/compile/importing.py - -var wrapperTypes = map[string]string{ - "google.protobuf.DoubleValue": "google_protobuf.DoubleValue", - "google.protobuf.FloatValue": "google_protobuf.FloatValue", - "google.protobuf.Int32Value": "google_protobuf.Int32Value", - "google.protobuf.Int64Value": "google_protobuf.Int64Value", - "google.protobuf.UInt32Value": "google_protobuf.UInt32Value", - "google.protobuf.UInt64Value": "google_protobuf.UInt64Value", - "google.protobuf.BoolValue": "google_protobuf.BoolValue", - "google.protobuf.StringValue": "google_protobuf.StringValue", - "google.protobuf.BytesValue": "google_protobuf.BytesValue", -} - -func parseSourceTypeName(fieldTypeName string) (string, string) { - packageMatch := regexp.MustCompile(`^\.?([^A-Z]+)\.(.+)`) - if packageMatch.Match([]byte(fieldTypeName)) { - packageName := packageMatch.FindStringSubmatch(fieldTypeName)[1] - typeName := packageMatch.FindStringSubmatch(fieldTypeName)[2] - return packageName, typeName - } else { - return "", strings.TrimPrefix(fieldTypeName, ".") - } -} - -func (m *Model) getTypeReference(from, to protoreflect.FileDescriptor, imports protoreflect.FileImports, sourceType string, unwrap bool) string { - if unwrap { - if wrapperType, ok := wrapperTypes[sourceType]; ok { - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "Optional") - return fmt.Sprintf("Optional[%s]", wrapperType) - } - if sourceType == "google.protobuf.Duration" { - m.OutputFile.DatetimeImports = append(m.OutputFile.DatetimeImports, "timedelta") - return "timedelta" - } - if sourceType == "google.protobuf.Timestamp" { - m.OutputFile.DatetimeImports = append(m.OutputFile.DatetimeImports, "datetime") - return "datetime" - } - } - sourcePackage, sourceType := parseSourceTypeName(sourceType) - pkg := string(from.ParentFile().Package()) - currentPackage := strings.Split(pkg, ".") - pyPackage := strings.Split(sourcePackage, ".") - pyType := formatClassName(sourceType) - - if slices.Equal(pyPackage, []string{"google", "protobuf"}) { - pyPackage = append([]string{"betterproto", "lib"}, pyPackage...) - } - - var ref string - var addImports []string - switch { - case len(pyPackage) > 0 && slices.Equal(pyPackage[:1], []string{"betterproto"}): - ref, addImports = referenceAbsolute(imports, pyPackage, pyType) - case slices.Equal(pyPackage, currentPackage): - ref, addImports = referenceSibling(from, to, pyType) - case len(pyPackage) >= len(currentPackage) && slices.Equal(pyPackage[:len(currentPackage)], currentPackage): - ref, addImports = referenceDescendent(currentPackage, imports, pyPackage, pyType) - case len(currentPackage) >= len(pyPackage) && slices.Equal(currentPackage[:len(pyPackage)], pyPackage): - ref, addImports = referenceAncestor(currentPackage, imports, pyPackage, pyType) - default: - ref, addImports = referenceCousin(currentPackage, imports, pyPackage, pyType) - } - m.OutputFile.Imports = append(m.OutputFile.Imports, addImports...) - return ref -} - -func referenceAbsolute(imports protoreflect.FileImports, pyPackage []string, pyType string) (ref string, addImports []string) { - stringImport := strings.Join(pyPackage, ".") - stringAlias := strcase.ToSnake(stringImport) - return fmt.Sprintf("%s.%s", stringAlias, pyType), []string{fmt.Sprintf("import %s as %s", stringImport, stringAlias)} -} - -func referenceSibling(from, to protoreflect.FileDescriptor, pyType string) (_ string, addImports []string) { - // check if the files are the same, if not we need to add an import - if from.Path() != to.ParentFile().Path() { - filename := strings.TrimSuffix(filepath.Base(string(to.Name())), filepath.Ext(string(to.Name()))) + "_pb" - addImports = append(addImports, fmt.Sprintf("from %s import %s", filename, pyType)) - } - return pyType, addImports -} - -func referenceDescendent(currentPackage []string, imports protoreflect.FileImports, pyPackage []string, pyType string) (ref string, addImports []string) { - importingDescendent := pyPackage[len(currentPackage):] - stringFrom := strings.Join(importingDescendent[:len(importingDescendent)-1], ".") - stringImport := importingDescendent[len(importingDescendent)-1] - if stringFrom != "" { - stringAlias := strings.Join(importingDescendent, "_") - addImports = append(addImports, fmt.Sprintf("from .%s import %s as %s", stringFrom, stringImport, stringAlias)) - ref = fmt.Sprintf("%s.%s", stringAlias, pyType) - return - } - addImports = append(addImports, fmt.Sprintf("from . import %s", stringImport)) - ref = fmt.Sprintf("%s.%s", stringImport, pyType) - return -} - -func referenceAncestor(currentPackage []string, imports protoreflect.FileImports, pyPackage []string, pyType string) (ref string, addImports []string) { - distanceUp := len(currentPackage) - len(pyPackage) - if len(pyPackage) > 0 { - stringImport := pyPackage[len(pyPackage)-1] - stringAlias := fmt.Sprintf("_%s%s__", strings.Repeat("_", distanceUp), stringImport) - stringFrom := fmt.Sprintf("..%s", strings.Repeat(".", distanceUp)) - addImports = append(addImports, fmt.Sprintf("from %s import %s as %s", stringFrom, stringImport, stringAlias)) - ref = fmt.Sprintf("%s.%s", stringAlias, pyType) - return - } - stringAlias := fmt.Sprintf("_%s%s__", strings.Repeat("_", distanceUp), pyType) - addImports = append(addImports, fmt.Sprintf("from .%s import %s as %s", strings.Repeat(".", distanceUp), pyType, stringAlias)) - ref = stringAlias - return -} - -func referenceCousin(currentPackage []string, imports protoreflect.FileImports, pyPackage []string, pyType string) (ref string, addImports []string) { - index := 0 - for ; index < len(currentPackage) && index < len(pyPackage) && currentPackage[index] == pyPackage[index]; index++ { - } - - sharedAncestry := currentPackage[:index] - distanceUp := len(currentPackage) - len(sharedAncestry) - stringFrom := fmt.Sprintf(".%s", strings.Repeat(".", distanceUp)) + strings.Join(pyPackage[len(sharedAncestry):len(pyPackage)-1], ".") - stringImport := pyPackage[len(pyPackage)-1] - // Add trailing __ to avoid name mangling (python.org/dev/peps/pep-0008/#id34) - stringAlias := strings.Repeat("_", distanceUp) + - strcase.ToSnake(strings.Join(pyPackage[len(sharedAncestry):], ".")) + - "__" - addImports = append(addImports, fmt.Sprintf("from %s import %s as %s", stringFrom, stringImport, stringAlias)) - ref = fmt.Sprintf("%s.%s", stringAlias, pyType) - return -} diff --git a/sdk/codegen/generators/x/python/model.go b/sdk/codegen/generators/x/python/model.go deleted file mode 100644 index 02b7f42..0000000 --- a/sdk/codegen/generators/x/python/model.go +++ /dev/null @@ -1,344 +0,0 @@ -package python - -import ( - "fmt" - "slices" - "strings" - - "github.com/iancoleman/strcase" - "github.com/kralicky/protols/sdk/codegen/generators/x/python/util" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/descriptorpb" -) - -type Model struct { - OutputFile *OutputFile `json:"output_file"` -} - -type OutputFile struct { - InputFilenames []string `json:"input_filenames"` - Imports []string `json:"imports"` - DatetimeImports []string `json:"datetime_imports"` - PythonModuleImports []string `json:"python_module_imports"` - ImportsTypeCheckingOnly []string `json:"imports_type_checking_only"` - TypingImports []string `json:"typing_imports"` - Enums []Enum `json:"enums"` - Messages []Message `json:"messages"` - Services []Service `json:"services"` -} - -type Enum struct { - Comment string `json:"comment"` - PyName string `json:"py_name"` - Entries []Entry `json:"entries"` -} - -type Entry struct { - Comment string `json:"comment"` - Name string `json:"name"` - Value int32 `json:"value"` -} - -type Message struct { - Comment string `json:"comment"` - Deprecated bool `json:"deprecated"` - PyName string `json:"py_name"` - HasDeprecatedFields bool `json:"has_deprecated_fields"` - DeprecatedFields []Field `json:"deprecated_fields"` - Fields []Field `json:"fields"` -} - -type Field struct { - FieldString string `json:"field_string"` - Comment string `json:"comment"` -} - -type Service struct { - Comment string `json:"comment"` - PyName string `json:"py_name"` - Methods []Method `json:"methods"` -} - -type Method struct { - PyInputMessageParam string `json:"py_input_message_param"` - Comment string `json:"comment"` - PyOutputMessageType string `json:"py_output_message_type"` - PyInputMessageType string `json:"py_input_message_type"` - PyName string `json:"py_name"` - Route string `json:"route"` - PyInputMessage string `json:"py_input_message"` - ServerStreaming bool `json:"server_streaming"` - ClientStreaming bool `json:"client_streaming"` -} - -func buildModel(f protoreflect.FileDescriptor) *Model { - m := &Model{ - OutputFile: &OutputFile{ - InputFilenames: []string{f.Path()}, - }, - } - m.OutputFile.Enums = m.buildEnums(f) - m.OutputFile.Messages = m.buildMessages(f) - m.OutputFile.Services = m.buildServices(f) - m.OutputFile.cleanImports() - - return m -} - -func (m *Model) buildEnums(f protoreflect.FileDescriptor) []Enum { - enums := []Enum{} - srcLocations := f.SourceLocations() - for _, e := range util.Collect(f.Enums()) { - entries := []Entry{} - for _, value := range util.Collect(e.Values()) { - e := Entry{ - Comment: formatComment(srcLocations.ByDescriptor(value).LeadingComments, 1), - Name: string(value.Name()), - Value: int32(value.Number()), - } - entries = append(entries, e) - } - enums = append(enums, Enum{ - Comment: formatComment(srcLocations.ByDescriptor(e).LeadingComments, 1), - PyName: formatClassName(e.Name()), - Entries: entries, - }) - } - for _, msg := range util.Collect(f.Messages()) { - for _, nestedEnum := range util.Collect(msg.Enums()) { - entries := []Entry{} - for _, value := range util.Collect(nestedEnum.Values()) { - e := Entry{ - Comment: formatComment(srcLocations.ByDescriptor(value).LeadingComments, 1), - Name: string(value.Name()), - Value: int32(value.Number()), - } - entries = append(entries, e) - } - enums = append(enums, Enum{ - Comment: formatComment(srcLocations.ByDescriptor(nestedEnum).LeadingComments, 1), - PyName: formatClassName(msg.Name() + "_" + nestedEnum.Name()), - Entries: entries, - }) - } - } - return enums -} - -func (m *Model) buildMessages(f protoreflect.FileDescriptor) []Message { - srcLocations := f.SourceLocations() - messages := []Message{} - for _, msg := range util.Collect(f.Messages()) { - fields := []Field{} - for _, field := range util.Collect(msg.Fields()) { - fields = append(fields, m.buildField(field)) - } - messages = append(messages, Message{ - Comment: formatComment(srcLocations.ByDescriptor(msg).LeadingComments, 1), - PyName: formatClassName(msg.Name()), - Deprecated: msg.Options().(*descriptorpb.MessageOptions).GetDeprecated(), - Fields: fields, - }) - } - return messages -} - -func (m *Model) buildField(f protoreflect.FieldDescriptor) Field { - name := formatFieldName(f.Name()) - annotation := "" - pyType, err := m.pyType(f) - if err != nil { - panic(err) - } - fieldWraps := f.Kind() == protoreflect.MessageKind && strings.HasPrefix(string(f.Message().FullName()), "google.protobuf") - fieldArgs := []string{fmt.Sprint(f.Number())} - if fieldWraps { - fieldArgs = append(fieldArgs, "wraps=True") - } - var protoFieldType string - - if f.IsMap() { - keyType, err := m.pyType(f.MapKey()) - if err != nil { - panic(err) - } - valueType, err := m.pyType(f.MapValue()) - if err != nil { - panic(err) - } - annotation = fmt.Sprintf(": Dict[%s, %s]", keyType, valueType) - fieldArgs = append(fieldArgs, fmt.Sprintf("key_type=%s", keyType), fmt.Sprintf("value_type=%s", valueType)) - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "Dict") - } else if f.Cardinality() == protoreflect.Repeated { - annotation = fmt.Sprintf(": List[%s]", pyType) - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "List") - } else { - annotation = fmt.Sprintf(": %s", pyType) - } - - if f.IsMap() { - protoFieldType = "map" - } else { - protoFieldType = pyType - } - fieldType := fmt.Sprintf("betterproto.%s_field(%s)", protoFieldType, strings.Join(fieldArgs, ", ")) - return Field{ - Comment: formatComment(f.ParentFile().SourceLocations().ByDescriptor(f).LeadingComments, 1), - FieldString: fmt.Sprintf("%s%s = %s", name, annotation, fieldType), - } -} - -func (m *Model) messageTypeRef(from protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) string { - fieldWraps := strings.HasPrefix(string(msg.FullName()), "google.protobuf") - return m.getTypeReference(from, msg.ParentFile(), - msg.ParentFile().Imports(), - string(msg.FullName()), fieldWraps) -} - -func (m *Model) buildServices(f protoreflect.FileDescriptor) []Service { - anyClientStreaming := false - anyServerStreaming := false - srcLocations := f.SourceLocations() - services := []Service{} - for _, s := range util.Collect(f.Services()) { - methods := []Method{} - for _, method := range util.Collect(s.Methods()) { - - methods = append(methods, Method{ - PyInputMessageParam: formatFieldName(method.Input().Name()), - Comment: formatComment(srcLocations.ByDescriptor(method).LeadingComments, 2), - PyOutputMessageType: m.messageTypeRef(f, method.Output()), - PyInputMessageType: m.messageTypeRef(f, method.Input()), - PyName: formatMethodName(method.Name()), - Route: fmt.Sprintf("/%s/%s", s.FullName(), method.Name()), - PyInputMessage: formatClassName(method.Input().Name()), - ServerStreaming: method.IsStreamingServer(), - ClientStreaming: method.IsStreamingClient(), - }) - if method.IsStreamingClient() { - anyClientStreaming = true - } - if method.IsStreamingServer() { - anyServerStreaming = true - } - } - services = append(services, Service{ - Comment: formatComment(srcLocations.ByDescriptor(s).LeadingComments, 1), - PyName: formatClassName(s.Name()), - Methods: methods, - }) - } - if len(services) > 0 { - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "Dict", "Optional") - m.OutputFile.ImportsTypeCheckingOnly = append(m.OutputFile.ImportsTypeCheckingOnly, - "from betterproto.grpc.grpclib_client import MetadataLike", - "from grpclib.metadata import Deadline", - ) - } - if anyClientStreaming { - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "AsyncIterable", "Iterable", "Union") - } - if anyClientStreaming || anyServerStreaming { - m.OutputFile.TypingImports = append(m.OutputFile.TypingImports, "AsyncIterator") - } - return services -} - -func formatClassName[T ~string](name T) string { - return strcase.ToCamel(string(name)) -} - -func formatFieldName[T ~string](name T) string { - return strcase.ToSnake(string(name)) -} - -func formatMethodName[T ~string](name T) string { - return strcase.ToSnake(string(name)) -} - -func (m *Model) pyType(field protoreflect.FieldDescriptor) (string, error) { - switch field.Kind() { - case protoreflect.DoubleKind, - protoreflect.FloatKind: - return "float", nil - case protoreflect.Int64Kind, - protoreflect.Uint64Kind, - protoreflect.Int32Kind, - protoreflect.Uint32Kind, - protoreflect.Fixed64Kind, - protoreflect.Fixed32Kind, - protoreflect.Sfixed64Kind, - protoreflect.Sfixed32Kind, - protoreflect.Sint32Kind, - protoreflect.Sint64Kind: - return "int", nil - case protoreflect.BoolKind: - return "bool", nil - case protoreflect.StringKind: - return "str", nil - case protoreflect.BytesKind: - return "bytes", nil - case protoreflect.MessageKind: - if field.IsMap() { - keyType, err := m.pyType(field.MapKey()) - if err != nil { - return "", err - } - valueType, err := m.pyType(field.MapValue()) - if err != nil { - return "", err - } - return fmt.Sprintf("Dict[%s, %s]", keyType, valueType), nil - } - fieldWraps := strings.HasPrefix(string(field.Message().FullName()), "google.protobuf") - return m.getTypeReference( - field.ParentFile(), field.Message().ParentFile(), - field.ParentFile().Imports(), - string(field.Message().FullName()), fieldWraps), nil - case protoreflect.EnumKind: - return m.getTypeReference( - field.ParentFile(), field.Enum().ParentFile(), - field.ParentFile().Imports(), - string(field.Enum().FullName()), false), nil - } - return "", fmt.Errorf("unknown/unsupported type %s", field.Kind()) -} - -func (o *OutputFile) cleanImports() { - slices.Sort(o.Imports) - slices.Sort(o.DatetimeImports) - slices.Sort(o.ImportsTypeCheckingOnly) - slices.Sort(o.PythonModuleImports) - slices.Sort(o.TypingImports) - o.Imports = slices.Compact(o.Imports) - o.DatetimeImports = slices.Compact(o.DatetimeImports) - o.ImportsTypeCheckingOnly = slices.Compact(o.ImportsTypeCheckingOnly) - o.PythonModuleImports = slices.Compact(o.PythonModuleImports) - o.TypingImports = slices.Compact(o.TypingImports) -} - -func formatComment(comment string, indent int) string { - var lines []string - for _, line := range strings.Split(comment, "\n") { - line = strings.TrimSpace(line) - if len(line) == 0 { - continue - } - lines = append(lines, line) - } - if len(lines) == 0 { - return "" - } - space := strings.Repeat(" ", indent*4) - if len(lines) == 1 { - return fmt.Sprintf(space+`""" %s """`, lines[0]) - } - - buf := strings.Builder{} - buf.WriteString(space + `"""` + "\n") - for _, line := range lines { - buf.WriteString(space + line + "\n") - } - buf.WriteString(space + `"""`) - return buf.String() -} diff --git a/sdk/codegen/generators/x/python/template.py.j2 b/sdk/codegen/generators/x/python/template.py.j2 deleted file mode 100644 index 71c53f8..0000000 --- a/sdk/codegen/generators/x/python/template.py.j2 +++ /dev/null @@ -1,212 +0,0 @@ -{# This template is derived from https://github.com/danielgtaylor/python-betterproto/blob/master/src/betterproto/templates/template.py.j2 #} - -{%- for i in output_file.python_module_imports %} -import {{ i }} -{% endfor %} -from __future__ import annotations -from dataclasses import dataclass -{% if output_file.datetime_imports %} -from datetime import {% for i in output_file.datetime_imports %}{{ i }}{%-if not forloop.Last %}, {% endif %}{% endfor %} - -{% endif %} -{%- if output_file.typing_imports %} -from typing import {% for i in output_file.typing_imports %}{{ i }}{% if not forloop.Last %}, {% endif %}{% endfor %} -{%- endif %} - -import betterproto -{%- for i in output_file.imports %} -{{ i }} -{%- endfor %} -{%- if output_file.services %} -from betterproto.grpc.grpclib_server import ServiceBase -import grpclib -{%- endif %} - -{%- if output_file.imports_type_checking_only %} -from typing import TYPE_CHECKING - -if TYPE_CHECKING: -{%- for i in output_file.imports_type_checking_only %} - {{ i }} -{%- endfor %} -{%- endif %} - -{% if output_file.enums -%} -{%- for enum in output_file.enums %} -class {{ enum.py_name }}(betterproto.Enum): - {%- if enum.comment %} -{{ enum.comment | safe }} - {%- endif %} - {%- for entry in enum.entries %} - {{ entry.name }} = {{ entry.value | floatformat:"0" }} - {%- if entry.comment %} -{{ entry.comment | safe }} - {%- endif %} - {%- endfor %} - -{%- endfor %} -{%- endif %} - -{% for message in output_file.messages %} -@dataclass(eq=False, repr=False) -class {{ message.py_name }}(betterproto.Message): - {%- if message.comment %} -{{ message.comment | safe }} - {%- endif %} - {%- for field in message.fields %} - {{ field.field_string }} - {%- if field.comment %} -{{ field.comment | safe }} - {%- endif %} - {%- endfor %} - {%- if not message.fields %} - pass - {%- endif %} - - {%- if message.deprecated or message.has_deprecated_fields %} - def __post_init__(self) -> None: - {%- if message.deprecated %} - warnings.warn("{{ message.py_name }} is deprecated", DeprecationWarning) - {%- endif %} - super().__post_init__() - {%- for field in message.deprecated_fields %} - if self.is_set("{{ field }}"): - warnings.warn("{{ message.py_name }}.{{ field }} is deprecated", DeprecationWarning) - {%- endfor %} - {%- endif %} - -{% endfor -%} -{% for service in output_file.services %} -class {{ service.py_name }}Stub(betterproto.ServiceStub): - {%- if service.comment %} -{{ service.comment | safe }} - {%- elif not service.methods %} - pass - {%- endif %} - {%- for method in service.methods %} - async def {{ method.py_name }}( - self, - {% if not method.client_streaming %} - {%- if method.py_input_message %}{{ method.py_input_message_param }}: {{ method.py_input_message_type }}{% endif %}, - {% else %} - {# Client streaming: need a request iterator instead #} - {{ method.py_input_message_param }}_iterator: Union[AsyncIterable[{{ method.py_input_message_type }}], Iterable[{{ method.py_input_message_type }}]], - {%- endif -%} - timeout: Optional[float] = None, - deadline: Optional[Deadline] = None, - metadata: Optional[MetadataLike] = None, - ) -> {% if method.server_streaming %}AsyncIterator[{{ method.py_output_message_type }}]{% else %}{{ method.py_output_message_type }}{% endif %}: - {%- if method.comment %} -{{ method.comment | safe }} - {%- endif %} - {%- if method.server_streaming %} - {%- if method.client_streaming %} - async for response in self._stream_stream( - "{{ method.route }}", - {{ method.py_input_message_param }}_iterator, - {{ method.py_input_message_type }}, - {{ method.py_output_message_type }}, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ): - yield response - {%- else %}{# i.e. not client streaming #} - async for response in self._unary_stream( - "{{ method.route }}", - {{ method.py_input_message_param }}, - {{ method.py_output_message_type }}, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ): - yield response - - {%- endif %}{# if client streaming #} - {%- else %}{# i.e. not server streaming #} - {%- if method.client_streaming %} - return await self._stream_unary( - "{{ method.route }}", - {{ method.py_input_message_param }}_iterator, - {{ method.py_input_message_type }}, - {{ method.py_output_message_type }}, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - {%- else %}{# i.e. not client streaming #} - return await self._unary_unary( - "{{ method.route }}", - {{ method.py_input_message_param }}, - {{ method.py_output_message_type }}, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - {%- endif %}{# client streaming #} - {%- endif %} - - {%- endfor %} -{%- endfor %} - -{% for service in output_file.services %} -class {{ service.py_name }}Base(ServiceBase): - {%- if service.comment %} -{{ service.comment | safe }} - {%- endif %} - - {%- for method in service.methods %} - async def {{ method.py_name }}(self - {%- if not method.client_streaming -%} - {%- if method.py_input_message -%}, {{ method.py_input_message_param }}: {{ method.py_input_message_type }}{%- endif -%} - {%- else -%} - {# Client streaming: need a request iterator instead #} - , {{ method.py_input_message_param }}_iterator: AsyncIterator[{{ method.py_input_message_type }}] - {%- endif -%} - ) -> {% if method.server_streaming %}AsyncIterator[{{ method.py_output_message_type }}]{% else %}{{ method.py_output_message_type }}{% endif %}: - {%- if method.comment %} -{{ method.comment | safe }} - {%- endif %} - raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - {% endfor %} - - {%- for method in service.methods %} - async def __rpc_{{ method.py_name }}(self, stream: grpclib.server.Stream) -> None: - {%- if not method.client_streaming %} - request = await stream.recv_message() - {%- else %} - request = stream.__aiter__() - {%- endif %} - {%- if not method.server_streaming %} - response = await self.{{ method.py_name }}(request) - await stream.send_message(response) - {%- else %} - await self._call_rpc_handler_server_stream( - self.{{ method.py_name }}, - stream, - request, - ) - {%- endif %} - {%- endfor %} - - def __mapping__(self) -> Dict[str, grpclib.const.Handler]: - return { - {%- for method in service.methods %} - "{{ method.route }}": grpclib.const.Handler( - self.__rpc_{{ method.py_name }}, - {%- if not method.client_streaming and not method.server_streaming %} - grpclib.const.Cardinality.UNARY_UNARY, - {%- elif not method.client_streaming and method.server_streaming %} - grpclib.const.Cardinality.UNARY_STREAM, - {%- elif method.client_streaming and not method.server_streaming %} - grpclib.const.Cardinality.STREAM_UNARY, - {%- else %} - grpclib.const.Cardinality.STREAM_STREAM, - {%- endif %} - {{ method.py_input_message_type }}, - {{ method.py_output_message_type }}, - ), - {%- endfor %} - } - -{%- endfor %} \ No newline at end of file diff --git a/sdk/codegen/generators/x/python/util/util.go b/sdk/codegen/generators/x/python/util/util.go deleted file mode 100644 index 1757e5e..0000000 --- a/sdk/codegen/generators/x/python/util/util.go +++ /dev/null @@ -1,25 +0,0 @@ -package util - -func Map[T any, R any](collection []T, iteratee func(T) R) []R { - result := make([]R, len(collection)) - - for i, item := range collection { - result[i] = iteratee(item) - } - - return result -} - -type Collection[T any] interface { - Len() int - Get(i int) T -} - -func Collect[T any, C Collection[T]](c C) []T { - l := c.Len() - out := make([]T, 0, l) - for i := 0; i < l; i++ { - out = append(out, c.Get(i)) - } - return out -}