Skip to content

Commit ff8463c

Browse files
committed
Handle fields that clash with Python reserved keywords
1 parent eff9021 commit ff8463c

File tree

6 files changed

+63
-6
lines changed

6 files changed

+63
-6
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ You can use it like so (enable async in the interactive shell first):
155155
EchoResponse(values=["hello", "hello"])
156156

157157
>>> async for response in service.echo_stream(value="hello", extra_times=1)
158-
print(response)
158+
print(response)
159159

160160
EchoStreamResponse(value="hello")
161161
EchoStreamResponse(value="hello")
@@ -296,13 +296,13 @@ $ pipenv run tests
296296
- [ ] Any support
297297
- [x] Enum strings
298298
- [ ] Well known types support (timestamp, duration, wrappers)
299-
- [ ] Support different casing (orig vs. camel vs. others?)
299+
- [x] Support different casing (orig vs. camel vs. others?)
300300
- [ ] Async service stubs
301301
- [x] Unary-unary
302302
- [x] Server streaming response
303303
- [ ] Client streaming request
304304
- [x] Renaming messages and fields to conform to Python name standards
305-
- [ ] Renaming clashes with language keywords and standard library top-level packages
305+
- [x] Renaming clashes with language keywords
306306
- [x] Python package
307307
- [x] Automate running tests
308308
- [ ] Cleanup!

betterproto/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import grpclib.const
2727
import stringcase
2828

29+
from .casing import safe_snake_case
30+
2931
# Proto 3 data types
3032
TYPE_ENUM = "enum"
3133
TYPE_BOOL = "bool"
@@ -642,7 +644,7 @@ def to_dict(self, casing: Casing = Casing.CAMEL) -> dict:
642644
for field in dataclasses.fields(self):
643645
meta = FieldMetadata.get(field)
644646
v = getattr(self, field.name)
645-
cased_name = casing(field.name)
647+
cased_name = casing(field.name).rstrip("_")
646648
if meta.proto_type == "message":
647649
if isinstance(v, list):
648650
# Convert each item.
@@ -686,7 +688,7 @@ def from_dict(self: T, value: dict) -> T:
686688
self._serialized_on_wire = True
687689
fields_by_name = {f.name: f for f in dataclasses.fields(self)}
688690
for key in value:
689-
snake_cased = stringcase.snakecase(key)
691+
snake_cased = safe_snake_case(key)
690692
if snake_cased in fields_by_name:
691693
field = fields_by_name[snake_cased]
692694
meta = FieldMetadata.get(field)

betterproto/casing.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import stringcase
2+
3+
4+
def safe_snake_case(value: str) -> str:
5+
"""Snake case a value taking into account Python keywords."""
6+
value = stringcase.snakecase(value)
7+
if value in [
8+
"and",
9+
"as",
10+
"assert",
11+
"break",
12+
"class",
13+
"continue",
14+
"def",
15+
"del",
16+
"elif",
17+
"else",
18+
"except",
19+
"finally",
20+
"for",
21+
"from",
22+
"global",
23+
"if",
24+
"import",
25+
"in",
26+
"is",
27+
"lambda",
28+
"nonlocal",
29+
"not",
30+
"or",
31+
"pass",
32+
"raise",
33+
"return",
34+
"try",
35+
"while",
36+
"with",
37+
"yield",
38+
]:
39+
# https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
40+
value += "_"
41+
return value

betterproto/plugin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
ServiceDescriptorProto,
2828
)
2929

30+
from betterproto.casing import safe_snake_case
31+
3032

3133
def get_ref_type(package: str, imports: set, type_name: str) -> str:
3234
"""
@@ -255,7 +257,7 @@ def generate_code(request, response):
255257
data["properties"].append(
256258
{
257259
"name": f.name,
258-
"py_name": stringcase.snakecase(f.name),
260+
"py_name": safe_snake_case(f.name),
259261
"number": f.number,
260262
"comment": get_comment(proto_file, path + [2, i]),
261263
"proto_type": int(f.type),

betterproto/tests/keywords.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"for": 1,
3+
"with": 2,
4+
"as": 3
5+
}

betterproto/tests/keywords.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
3+
message Test {
4+
int32 for = 1;
5+
int32 with = 2;
6+
int32 as = 3;
7+
}

0 commit comments

Comments
 (0)