Add support for map value message types
This commit is contained in:
parent
32bc8d50fb
commit
7dbaee0cfb
23
README.md
23
README.md
@ -1,4 +1,22 @@
|
|||||||
# TODO
|
# Better Protobuf / gRPC Support for Python
|
||||||
|
|
||||||
|
This project aims to provide an improved experience when using Protobuf / gRPC in a modern Python environment by making use of modern language features and generating readable, understandable code. It will not support legacy features or environments. The following are supported:
|
||||||
|
|
||||||
|
- Protobuf 3 & gRPC code generation
|
||||||
|
- Both binary & JSON serialization is built-in
|
||||||
|
- Python 3.7+
|
||||||
|
- Enums
|
||||||
|
- Dataclasses
|
||||||
|
- `async`/`await`
|
||||||
|
- Relative imports
|
||||||
|
- Mypy type checking
|
||||||
|
|
||||||
|
This project is heavily inspired by, and borrows functionality from:
|
||||||
|
|
||||||
|
- https://github.com/eigenein/protobuf/
|
||||||
|
- https://github.com/vmagamedov/grpclib
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
- [x] Fixed length fields
|
- [x] Fixed length fields
|
||||||
- [x] Packed fixed-length
|
- [x] Packed fixed-length
|
||||||
@ -7,11 +25,12 @@
|
|||||||
- [x] Enums
|
- [x] Enums
|
||||||
- [x] Repeated message fields
|
- [x] Repeated message fields
|
||||||
- [x] Maps
|
- [x] Maps
|
||||||
- [ ] Maps of message fields
|
- [x] Maps of message fields
|
||||||
- [ ] Support passthrough of unknown fields
|
- [ ] Support passthrough of unknown fields
|
||||||
- [ ] Refs to nested types
|
- [ ] Refs to nested types
|
||||||
- [ ] Imports in proto files
|
- [ ] Imports in proto files
|
||||||
- [ ] Well-known Google types
|
- [ ] Well-known Google types
|
||||||
- [ ] JSON that isn't completely naive.
|
- [ ] JSON that isn't completely naive.
|
||||||
- [ ] Async service stubs
|
- [ ] Async service stubs
|
||||||
|
- [ ] Python package
|
||||||
- [ ] Cleanup!
|
- [ ] Cleanup!
|
||||||
|
@ -385,14 +385,13 @@ class Message(ABC):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _cls_for(self, field: dataclasses.Field) -> Type:
|
def _cls_for(self, field: dataclasses.Field, index: int = 0) -> Type:
|
||||||
"""Get the message class for a field from the type hints."""
|
"""Get the message class for a field from the type hints."""
|
||||||
module = inspect.getmodule(self)
|
module = inspect.getmodule(self)
|
||||||
type_hints = get_type_hints(self, vars(module))
|
type_hints = get_type_hints(self, vars(module))
|
||||||
cls = type_hints[field.name]
|
cls = type_hints[field.name]
|
||||||
if hasattr(cls, "__args__"):
|
if hasattr(cls, "__args__"):
|
||||||
print(type_hints[field.name].__args__[0])
|
cls = type_hints[field.name].__args__[index]
|
||||||
cls = type_hints[field.name].__args__[0]
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
def _postprocess_single(
|
def _postprocess_single(
|
||||||
@ -420,11 +419,13 @@ class Message(ABC):
|
|||||||
elif meta.proto_type in [TYPE_MAP]:
|
elif meta.proto_type in [TYPE_MAP]:
|
||||||
# TODO: This is slow, use a cache to make it faster since each
|
# TODO: This is slow, use a cache to make it faster since each
|
||||||
# key/value pair will recreate the class.
|
# key/value pair will recreate the class.
|
||||||
|
kt = self._cls_for(field, index=0)
|
||||||
|
vt = self._cls_for(field, index=1)
|
||||||
Entry = dataclasses.make_dataclass(
|
Entry = dataclasses.make_dataclass(
|
||||||
"Entry",
|
"Entry",
|
||||||
[
|
[
|
||||||
("key", Any, dataclass_field(1, meta.map_types[0], None)),
|
("key", kt, dataclass_field(1, meta.map_types[0], None)),
|
||||||
("value", Any, dataclass_field(2, meta.map_types[1], None)),
|
("value", vt, dataclass_field(2, meta.map_types[1], None)),
|
||||||
],
|
],
|
||||||
bases=(Message,),
|
bases=(Message,),
|
||||||
)
|
)
|
||||||
@ -500,10 +501,18 @@ class Message(ABC):
|
|||||||
v = [i for i in v if i]
|
v = [i for i in v if i]
|
||||||
else:
|
else:
|
||||||
v = v.to_dict()
|
v = v.to_dict()
|
||||||
|
|
||||||
|
if v:
|
||||||
|
output[field.name] = v
|
||||||
|
elif meta.proto_type == "map":
|
||||||
|
for k in v:
|
||||||
|
if hasattr(v[k], "to_dict"):
|
||||||
|
v[k] = v[k].to_dict()
|
||||||
|
|
||||||
if v:
|
if v:
|
||||||
output[field.name] = v
|
output[field.name] = v
|
||||||
elif v != field.default:
|
elif v != field.default:
|
||||||
output[field.name] = getattr(self, field.name)
|
output[field.name] = v
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def from_dict(self, value: dict) -> T:
|
def from_dict(self, value: dict) -> T:
|
||||||
@ -516,13 +525,18 @@ class Message(ABC):
|
|||||||
if field.name in value:
|
if field.name in value:
|
||||||
if meta.proto_type == "message":
|
if meta.proto_type == "message":
|
||||||
v = getattr(self, field.name)
|
v = getattr(self, field.name)
|
||||||
print(v, value[field.name])
|
# print(v, value[field.name])
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
cls = self._cls_for(field)
|
cls = self._cls_for(field)
|
||||||
for i in range(len(value[field.name])):
|
for i in range(len(value[field.name])):
|
||||||
v.append(cls().from_dict(value[field.name][i]))
|
v.append(cls().from_dict(value[field.name][i]))
|
||||||
else:
|
else:
|
||||||
v.from_dict(value[field.name])
|
v.from_dict(value[field.name])
|
||||||
|
elif meta.proto_type == "map" and meta.map_types[1] == TYPE_MESSAGE:
|
||||||
|
v = getattr(self, field.name)
|
||||||
|
cls = self._cls_for(field, index=1)
|
||||||
|
for k in value[field.name]:
|
||||||
|
v[k] = cls().from_dict(value[field.name][k])
|
||||||
else:
|
else:
|
||||||
setattr(self, field.name, value[field.name])
|
setattr(self, field.name, value[field.name])
|
||||||
return self
|
return self
|
||||||
|
10
betterproto/tests/mapmessage.json
Normal file
10
betterproto/tests/mapmessage.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"items": {
|
||||||
|
"foo": {
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
betterproto/tests/mapmessage.proto
Normal file
9
betterproto/tests/mapmessage.proto
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message Test {
|
||||||
|
map<string, Nested> items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Nested {
|
||||||
|
int32 count = 1;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user