Merge pull request #2 from danielgtaylor/map-message
Add support for map value message types
This commit is contained in:
		
							
								
								
									
										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; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user