Skip to content

Commit 86379f6

Browse files
authored
fix(ModelGenerator): Rename fields named after Python keywords (#870)
We now append an "_" to any fields that are Python keywords in order to still generate valid Python code. Co-authored-by: George Ungureanu Vranceanu <george.ungureanuvranceanu@imc.com>
1 parent c2d2cef commit 86379f6

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

dataclasses_avroschema/model_generator/lang/python/base.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
import dataclasses
33
import json
4+
import keyword
45
import typing
56
from abc import abstractmethod
67
from dataclasses import dataclass, field
@@ -527,19 +528,29 @@ def render_field(self, field: JsonDict, model_name: str, parent_field_name: str)
527528
@staticmethod
528529
def generate_field_name(field: JsonDict) -> str:
529530
field_name = field.get("name", "")
531+
if not field_name:
532+
return field_name
530533

531-
if field_name and not field_name.isidentifier():
534+
if keyword.iskeyword(field_name):
535+
valid_identifier = f"{field_name}_"
536+
BaseGenerator.maybe_add_alias(field, field_name)
537+
return valid_identifier
538+
539+
if not field_name.isidentifier():
532540
valid_identifier = casefy.snakecase(field_name)
533541
if valid_identifier != field_name:
534-
aliases = field.get("aliases", [])
535-
536-
if valid_identifier not in aliases:
537-
aliases.append(field_name)
538-
field["aliases"] = aliases
539-
542+
BaseGenerator.maybe_add_alias(field, field_name)
540543
return valid_identifier
541544
return field_name
542545

546+
@staticmethod
547+
def maybe_add_alias(field: JsonDict, alias: str) -> None:
548+
aliases = field.get("aliases", [])
549+
550+
if alias not in aliases:
551+
aliases.append(alias)
552+
field["aliases"] = aliases
553+
543554
@staticmethod
544555
def is_logical_type(*, field: JsonDict) -> bool:
545556
if field.get("logicalType"):

tests/model_generator/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ def schema_with_invalid_python_identifiers() -> Dict:
6262
}
6363

6464

65+
@pytest.fixture
66+
def schema_with_python_keywords() -> Dict:
67+
return {
68+
"type": "record",
69+
"name": "Address",
70+
"fields": [
71+
{"name": "class", "type": "string"},
72+
{"name": "yield", "type": "string", "aliases": ["yield"]},
73+
{"name": "yield_class", "type": "long"},
74+
],
75+
"doc": "An Address",
76+
}
77+
78+
6579
@pytest.fixture
6680
def schema_primitive_types_as_defined_types() -> Dict:
6781
return {

tests/model_generator/test_model_pydantic_generator.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,28 @@ class Address(AvroBaseModel):
115115
assert result.strip() == expected_result.strip()
116116

117117

118+
def test_avro_pydantic_python_keywords(schema_with_python_keywords: types.JsonDict) -> None:
119+
expected_result = """
120+
from dataclasses_avroschema.pydantic import AvroBaseModel
121+
import pydantic
122+
123+
124+
125+
class Address(AvroBaseModel):
126+
\"""
127+
An Address
128+
\"""
129+
class_: str = pydantic.Field(metadata={'aliases': ['class']})
130+
yield_: str = pydantic.Field(metadata={'aliases': ['yield']})
131+
yield_class: int
132+
133+
134+
"""
135+
model_generator = ModelGenerator()
136+
result = model_generator.render(schema=schema_with_python_keywords, model_type=ModelType.AVRODANTIC.value)
137+
assert result.strip() == expected_result.strip()
138+
139+
118140
def test_avro_pydantic_model_with_meta_fields(
119141
schema_one_to_self_relationship: types.JsonDict,
120142
) -> None:

0 commit comments

Comments
 (0)