danielgtaylor / python-betterproto

Clean, modern, Python 3.6+ code generator & library for Protobuf 3 and async gRPC
MIT License
1.45k stars 196 forks source link

Suggestion: give user an option not to change field name of the message. #379

Open honglei opened 2 years ago

honglei commented 2 years ago

Every prevailing project has its own name-style, forcing users to rename every name may be not a good idea. To communicate with Java/C++/JavaScript, mixedCase (also known as Lower CamelCase) is accepted by every other language, and it is much shorter than PEP8 suggested-style: private_multicast_addr . In my case, the InitConfig is passed by an JSON-object from web client, changing the name back and forth is useless and boring.

message InitConfig{
    string sendFileDir=1; 
    string publicMulitcastAddr=2;
    uint32 linkID =3;
    string privateMulticastAddr=4;
    uint32 CIR=5; 
    repeated string channels=6; 
    repeated string waitingFiles=7;
}

the generated python file by betterproto:

@dataclass(eq=False, repr=False)
class InitConfig(betterproto.Message):
    send_file_dir: str = betterproto.string_field(1)
    public_mulitcast_addr: str = betterproto.string_field(2)
    link_id: int = betterproto.uint32_field(3)
    private_multicast_addr: str = betterproto.string_field(4)
    cir: int = betterproto.uint32_field(5)
    channels: List[str] = betterproto.string_field(6)
    waiting_files: List[str] = betterproto.string_field(7)
honglei commented 2 years ago

JS(brower)<--JSON--> Python(web server) <--GRPC--> C++(file stransfer backend)

{
 "sendFileDir": "/home/lccy/sendfile",
 "publicMulitcastAddr": "223.0.1.1",
 "linkID": 1,
 "privateMulticastAddr": "234.0.1.1",
 "CIR": 22333,
 "channels": [
  "\u7b2c\u4e00\u9891\u9053",
  "\u7b2c\u4e8c\u9891\u9053"
 ],
 "waitingFiles": [
  "\u7b2c\u4e00\u9891\u9053/11.txt",
  "\u7b2c\u4e8c\u9891\u9053/22.txt"
 ]
}
Gobot1234 commented 2 years ago

If you are serialising as JSON you can pass a betterproto.Casing to make sure that the field names are in camelCase

honglei commented 2 years ago

Question 1: betterproto.Casing.CAMEL seems not enough:

message ReplyX{
    bool aBigCDEndOne=1;//
}
import betterproto
from uftplink import ReplyX
a = ReplyX(a_big_cd_end_one=True)
b =a.to_dict(casing=betterproto.Casing.CAMEL )

expect = {'aBigCDEndOne':True}
assert b== expect  #not equal

question 2: from JSON to ReplyX message?

honglei commented 2 years ago

question2.1: easy way from JSON to Nested-Type message?

message _FileInfo{
    string name=1;
    uint64 size=2;
}
message _SendFileStatus{
    string name=1;
    uint64 size=2;
    uint64 sended =3;
}

message LinkFileStatus{
    repeated _FileInfo waitingFiles=1;
    _SendFileStatus currentFile=2;
}
a = {
     "currentFile":{"name":"c/11.txt", "size":532323,"sended":3322,}, 
     "waitingFiles":[
         {"name":"c/12.txt",
          "size":32325,
         }
         ,{"name":"c/22.txt",
           "size":32335,
        }
         ],   
}
LinkFileStatus( **a ) #not work 
honglei commented 2 years ago

a changed generated Python file:

@dataclass(eq=False, repr=False)
class FileInfo(betterproto.Message):
    name: str = betterproto.string_field(1)
    size: int = betterproto.uint64_field(2)

@dataclass(eq=False, repr=False)
class SendFileStatus(betterproto.Message):
    name: str = betterproto.string_field(1)
    size: int = betterproto.uint64_field(2)
    sended: int = betterproto.uint64_field(3)

@dataclass(eq=False, repr=False)
class LinkFileStatus(betterproto.Message):
    waitingFiles: List[FileInfo] = betterproto.message_field(1)
    currentFile: SendFileStatus = betterproto.message_field(2)

a simple JSON-> betterproto.Message

from betterproto import Message
def set_attrs(value_type:Message, value):
    '''
      oneof is not considered 
    '''
    if value_type == Empty:
        return Empty()

    result = value_type()
    for field_name, field_type in result.__annotations__.items():
        if hasattr(field_type,'_name'):
            list_field_type = field_type.__args__[0]
            if field_type._name=='List': #repeated/python typing.List
                if issubclass(list_field_type, Message):
                    list_value = [set_attrs(list_field_type, v) for v in value.get(field_name)]
                    setattr(result, field_name, list_value )
                else:
                    setattr(result, field_name, value.get(field_name)) 
            elif field_type._name =='Optional':
                if issubclass(list_field_type, Message):
                    if field_value:= value.get(field_name):
                        setattr(result, field_name, set_attrs(list_field_type,field_value) )

        elif issubclass(field_type, Message): #Nested Message 
            setattr(result, field_name, set_attrs(field_type, value.get(field_name)) )
        else:
            setattr(result, field_name, value.get(field_name)) 
    return result

b = set_attrs(LinkFileStatus, a)
bytes(b)