Merge pull request #2 from danielgtaylor/map-message

Add support for map value message types
This commit is contained in:
Daniel G. Taylor 2019-10-11 07:45:43 -07:00 committed by GitHub
commit 55be5eed69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 9 deletions

View File

@ -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] Packed fixed-length
@ -7,11 +25,12 @@
- [x] Enums
- [x] Repeated message fields
- [x] Maps
- [ ] Maps of message fields
- [x] Maps of message fields
- [ ] Support passthrough of unknown fields
- [ ] Refs to nested types
- [ ] Imports in proto files
- [ ] Well-known Google types
- [ ] JSON that isn't completely naive.
- [ ] Async service stubs
- [ ] Python package
- [ ] Cleanup!

View File

@ -385,14 +385,13 @@ class Message(ABC):
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."""
module = inspect.getmodule(self)
type_hints = get_type_hints(self, vars(module))
cls = type_hints[field.name]
if hasattr(cls, "__args__"):
print(type_hints[field.name].__args__[0])
cls = type_hints[field.name].__args__[0]
cls = type_hints[field.name].__args__[index]
return cls
def _postprocess_single(
@ -420,11 +419,13 @@ class Message(ABC):
elif meta.proto_type in [TYPE_MAP]:
# TODO: This is slow, use a cache to make it faster since each
# 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",
[
("key", Any, dataclass_field(1, meta.map_types[0], None)),
("value", Any, dataclass_field(2, meta.map_types[1], None)),
("key", kt, dataclass_field(1, meta.map_types[0], None)),
("value", vt, dataclass_field(2, meta.map_types[1], None)),
],
bases=(Message,),
)
@ -500,10 +501,18 @@ class Message(ABC):
v = [i for i in v if i]
else:
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:
output[field.name] = v
elif v != field.default:
output[field.name] = getattr(self, field.name)
output[field.name] = v
return output
def from_dict(self, value: dict) -> T:
@ -516,13 +525,18 @@ class Message(ABC):
if field.name in value:
if meta.proto_type == "message":
v = getattr(self, field.name)
print(v, value[field.name])
# print(v, value[field.name])
if isinstance(v, list):
cls = self._cls_for(field)
for i in range(len(value[field.name])):
v.append(cls().from_dict(value[field.name][i]))
else:
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:
setattr(self, field.name, value[field.name])
return self

View File

@ -0,0 +1,10 @@
{
"items": {
"foo": {
"count": 1
},
"bar": {
"count": 2
}
}
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
message Test {
map<string, Nested> items = 1;
}
message Nested {
int32 count = 1;
}