Repository: marcosschroh/dataclasses-avroschema

Issue:
Handling of schemas whose fields using reserved keywords for Python variables
**Describe the bug**
I have avro schema which contains field name like yield and fake. The generated model contains yield and fake as a field. having fake field in the model create issue when using fake method and yield causes issue because its a reserved keyword in python.

fake field resulted in

`AssertionError: Invalid default type <class 'method'> for field "fake". Default should be <class 'str'>`

**To Reproduce**

```
import json

from dataclasses_avroschema import ModelGenerator, ModelType

model_generator = ModelGenerator()
schema = """
	{
		"type": "record",
		"name": "Test",
		"namespace": "com.foo",
		"fields": [{
			"name": "yield",
			"type": "double"
		}, {
			"name": "fake",
			"type": {
				"type": "string"
			}
		}]
	}
"""
new_schema = json.loads(schema)
result = model_generator.render(schema=new_schema, model_type=ModelType.AVRODANTIC.value, include_original_schema=True)
print(result)

==========
class Test(AvroBaseModel):
    yield: float
    fake: str

    
    class Meta:
        namespace = "com.foo"
        original_schema = """{"type": "record", "name": "Test", "namespace": "com.foo", "fields": [{"name": "yield", "type": "double"}, {"name": "fake", "type": {"type": "string"}}]}"""
```

**Expected behavior**
It should generate valid model perhaps use alias field


Discussion hints:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/870?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.45%. Comparing base [(`c2d2cef`)](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/commit/c2d2cefe4b7e54df9defd05f28aade188abfd08f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) to head [(`d424b44`)](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/commit/d424b440a393bcca53518a192c5ddba5c119eb09?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #870   +/-   ##
=======================================
  Coverage   99.44%   99.45%           
=======================================
  Files          35       35           
  Lines        2177     2187   +10     
=======================================
+ Hits         2165     2175   +10     
  Misses         12       12           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/870?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Solution patch:
diff --git a/dataclasses_avroschema/model_generator/lang/python/base.py b/dataclasses_avroschema/model_generator/lang/python/base.py
index 7df142f..0e07625 100644
--- a/dataclasses_avroschema/model_generator/lang/python/base.py
+++ b/dataclasses_avroschema/model_generator/lang/python/base.py
@@ -1,6 +1,7 @@
 import copy
 import dataclasses
 import json
+import keyword
 import typing
 from abc import abstractmethod
 from dataclasses import dataclass, field
@@ -527,19 +528,29 @@ class BaseGenerator:
     @staticmethod
     def generate_field_name(field: JsonDict) -> str:
         field_name = field.get("name", "")
+        if not field_name:
+            return field_name
 
-        if field_name and not field_name.isidentifier():
+        if keyword.iskeyword(field_name):
+            valid_identifier = f"{field_name}_"
+            BaseGenerator.maybe_add_alias(field, field_name)
+            return valid_identifier
+
+        if not field_name.isidentifier():
             valid_identifier = casefy.snakecase(field_name)
             if valid_identifier != field_name:
-                aliases = field.get("aliases", [])
-
-                if valid_identifier not in aliases:
-                    aliases.append(field_name)
-                    field["aliases"] = aliases
-
+                BaseGenerator.maybe_add_alias(field, field_name)
             return valid_identifier
         return field_name
 
+    @staticmethod
+    def maybe_add_alias(field: JsonDict, alias: str) -> None:
+        aliases = field.get("aliases", [])
+
+        if alias not in aliases:
+            aliases.append(alias)
+            field["aliases"] = aliases
+
     @staticmethod
     def is_logical_type(*, field: JsonDict) -> bool:
         if field.get("logicalType"):


Test patch:
diff --git a/tests/model_generator/conftest.py b/tests/model_generator/conftest.py
index f512d62..9fb1dd9 100644
--- a/tests/model_generator/conftest.py
+++ b/tests/model_generator/conftest.py
@@ -62,6 +62,20 @@ def schema_with_invalid_python_identifiers() -> Dict:
     }
 
 
+@pytest.fixture
+def schema_with_python_keywords() -> Dict:
+    return {
+        "type": "record",
+        "name": "Address",
+        "fields": [
+            {"name": "class", "type": "string"},
+            {"name": "yield", "type": "string", "aliases": ["yield"]},
+            {"name": "yield_class", "type": "long"},
+        ],
+        "doc": "An Address",
+    }
+
+
 @pytest.fixture
 def schema_primitive_types_as_defined_types() -> Dict:
     return {
diff --git a/tests/model_generator/test_model_pydantic_generator.py b/tests/model_generator/test_model_pydantic_generator.py
index 088ec2e..ebdf637 100644
--- a/tests/model_generator/test_model_pydantic_generator.py
+++ b/tests/model_generator/test_model_pydantic_generator.py
@@ -115,6 +115,28 @@ class Address(AvroBaseModel):
     assert result.strip() == expected_result.strip()
 
 
+def test_avro_pydantic_python_keywords(schema_with_python_keywords: types.JsonDict) -> None:
+    expected_result = """
+from dataclasses_avroschema.pydantic import AvroBaseModel
+import pydantic
+
+
+
+class Address(AvroBaseModel):
+    \"""
+    An Address
+    \"""
+    class_: str = pydantic.Field(metadata={'aliases': ['class']})
+    yield_: str = pydantic.Field(metadata={'aliases': ['yield']})
+    yield_class: int
+
+
+"""
+    model_generator = ModelGenerator()
+    result = model_generator.render(schema=schema_with_python_keywords, model_type=ModelType.AVRODANTIC.value)
+    assert result.strip() == expected_result.strip()
+
+
 def test_avro_pydantic_model_with_meta_fields(
     schema_one_to_self_relationship: types.JsonDict,
 ) -> None: