Handle fields that clash with Python reserved keywords

This commit is contained in:
Daniel G. Taylor 2019-10-23 21:28:31 -07:00
parent eff9021529
commit ff8463cf12
No known key found for this signature in database
GPG Key ID: 7BD6DC99C9A87E22
6 changed files with 63 additions and 6 deletions

View File

@ -155,7 +155,7 @@ You can use it like so (enable async in the interactive shell first):
EchoResponse(values=["hello", "hello"])
>>> async for response in service.echo_stream(value="hello", extra_times=1)
print(response)
print(response)
EchoStreamResponse(value="hello")
EchoStreamResponse(value="hello")
@ -296,13 +296,13 @@ $ pipenv run tests
- [ ] Any support
- [x] Enum strings
- [ ] Well known types support (timestamp, duration, wrappers)
- [ ] Support different casing (orig vs. camel vs. others?)
- [x] Support different casing (orig vs. camel vs. others?)
- [ ] Async service stubs
- [x] Unary-unary
- [x] Server streaming response
- [ ] Client streaming request
- [x] Renaming messages and fields to conform to Python name standards
- [ ] Renaming clashes with language keywords and standard library top-level packages
- [x] Renaming clashes with language keywords
- [x] Python package
- [x] Automate running tests
- [ ] Cleanup!

View File

@ -26,6 +26,8 @@ import grpclib.client
import grpclib.const
import stringcase
from .casing import safe_snake_case
# Proto 3 data types
TYPE_ENUM = "enum"
TYPE_BOOL = "bool"
@ -642,7 +644,7 @@ class Message(ABC):
for field in dataclasses.fields(self):
meta = FieldMetadata.get(field)
v = getattr(self, field.name)
cased_name = casing(field.name)
cased_name = casing(field.name).rstrip("_")
if meta.proto_type == "message":
if isinstance(v, list):
# Convert each item.
@ -686,7 +688,7 @@ class Message(ABC):
self._serialized_on_wire = True
fields_by_name = {f.name: f for f in dataclasses.fields(self)}
for key in value:
snake_cased = stringcase.snakecase(key)
snake_cased = safe_snake_case(key)
if snake_cased in fields_by_name:
field = fields_by_name[snake_cased]
meta = FieldMetadata.get(field)

41
betterproto/casing.py Normal file
View File

@ -0,0 +1,41 @@
import stringcase
def safe_snake_case(value: str) -> str:
"""Snake case a value taking into account Python keywords."""
value = stringcase.snakecase(value)
if value in [
"and",
"as",
"assert",
"break",
"class",
"continue",
"def",
"del",
"elif",
"else",
"except",
"finally",
"for",
"from",
"global",
"if",
"import",
"in",
"is",
"lambda",
"nonlocal",
"not",
"or",
"pass",
"raise",
"return",
"try",
"while",
"with",
"yield",
]:
# https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
value += "_"
return value

View File

@ -27,6 +27,8 @@ from google.protobuf.descriptor_pb2 import (
ServiceDescriptorProto,
)
from betterproto.casing import safe_snake_case
def get_ref_type(package: str, imports: set, type_name: str) -> str:
"""
@ -255,7 +257,7 @@ def generate_code(request, response):
data["properties"].append(
{
"name": f.name,
"py_name": stringcase.snakecase(f.name),
"py_name": safe_snake_case(f.name),
"number": f.number,
"comment": get_comment(proto_file, path + [2, i]),
"proto_type": int(f.type),

View File

@ -0,0 +1,5 @@
{
"for": 1,
"with": 2,
"as": 3
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
message Test {
int32 for = 1;
int32 with = 2;
int32 as = 3;
}