Repository: mirumee/ariadne-codegen (python)

Issue:
NameError: Field name "schema" shadows a BaseModel attribute; use a different field name with "alias='schema'".
Schema that use a field called "schema" fail the pydantic post generation check. See https://pythonfix.com/error/pydantic-field-name-schema-shadows-basemodel-attribute/ and https://stackoverflow.com/questions/73828037/pydantic-field-does-not-take-value it would be nice if the generator worked around this by automatically renaming the field to "schema_" and adding = Field(..., alias='schema')

Pull request description:
Handle field names reserved by pydantic
This pr handles input and result field names which were colliding with names reserved by pydantic's `BaseModel` methods and attributes. The same as python keywords, we add `_` as a suffix. Full list of reserved names:
```python
In [2]: [name for name in dir(BaseModel) if not name.startswith("_")]
Out[2]: 
['construct',
 'copy',
 'dict',
 'from_orm',
 'json',
 'model_computed_fields',
 'model_config',
 'model_construct',
 'model_copy',
 'model_dump',
 'model_dump_json',
 'model_extra',
 'model_fields',
 'model_fields_set',
 'model_json_schema',
 'model_parametrized_name',
 'model_post_init',
 'model_rebuild',
 'model_validate',
 'model_validate_json',
 'parse_file',
 'parse_obj',
 'parse_raw',
 'schema',
 'schema_json',
 'update_forward_refs',
 'validate']
```

resolves #191 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56ac0bc..6603027 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
 - Changed generated client and models to use pydantic v2.
 - Changed custom scalars implementation to utilize pydantic's `BeforeValidator` and `PlainSerializer`. Added `scalars_module_name` option. Replaced `generate_scalars_parse_dict` and `generate_scalars_serialize_dict` with `generate_scalar_annotation` and `generate_scalar_imports` plugin hooks.
 - Fixed generating default values of input types from remote schemas.
+- Changed generating of input and result field names to add `_` to names reserved by pydantic.
 
 
 ## 0.7.1 (2023-06-06)
diff --git a/ariadne_codegen/client_generators/input_types.py b/ariadne_codegen/client_generators/input_types.py
index 7761a12..9485194 100644
--- a/ariadne_codegen/client_generators/input_types.py
+++ b/ariadne_codegen/client_generators/input_types.py
@@ -127,6 +127,7 @@ class InputTypesGenerator:
                 plugin_manager=self.plugin_manager,
                 node=field,
                 trim_leading_underscore=True,
+                handle_pydantic_resrved_field_names=True,
             )
             annotation, field_type = parse_input_field_type(
                 field.type, custom_scalars=self.custom_scalars
diff --git a/ariadne_codegen/client_generators/result_types.py b/ariadne_codegen/client_generators/result_types.py
index 8e3c915..8415ddc 100644
--- a/ariadne_codegen/client_generators/result_types.py
+++ b/ariadne_codegen/client_generators/result_types.py
@@ -379,6 +379,7 @@ class ResultTypesGenerator:
             plugin_manager=self.plugin_manager,
             node=field,
             trim_leading_underscore=True,
+            handle_pydantic_resrved_field_names=True,
         )
 
     def _get_field_from_schema(self, type_name: str, field_name: str) -> GraphQLField:
diff --git a/ariadne_codegen/utils.py b/ariadne_codegen/utils.py
index 67c413f..838035a 100644
--- a/ariadne_codegen/utils.py
+++ b/ariadne_codegen/utils.py
@@ -8,9 +8,14 @@ import isort
 from autoflake import fix_code  # type: ignore
 from black import Mode, format_str
 from graphql import Node
+from pydantic import BaseModel
 
 from .plugins.manager import PluginManager
 
+PYDANTIC_RESERVED_FIELD_NAMES = [
+    name for name in dir(BaseModel) if not name.startswith("_")
+]
+
 
 def ast_to_str(
     ast_obj: ast.AST,
@@ -83,6 +88,7 @@ def process_name(
     plugin_manager: Optional[PluginManager] = None,
     node: Optional[Node] = None,
     trim_leading_underscore: bool = False,
+    handle_pydantic_resrved_field_names: bool = False,
 ) -> str:
     """Processes the GraphQL name to remove keywords
     and optionally convert to snake_case."""
@@ -91,6 +97,11 @@ def process_name(
         processed_name = str_to_snake_case(processed_name)
     if iskeyword(processed_name):
         processed_name += "_"
+    if (
+        handle_pydantic_resrved_field_names
+        and processed_name in PYDANTIC_RESERVED_FIELD_NAMES
+    ):
+        processed_name += "_"
     if trim_leading_underscore:
         processed_name = processed_name.lstrip("_")
     if plugin_manager:


Test patch:
diff --git a/tests/client_generators/input_types_generator/test_names.py b/tests/client_generators/input_types_generator/test_names.py
index 8f9dcc5..a5d1ea9 100644
--- a/tests/client_generators/input_types_generator/test_names.py
+++ b/tests/client_generators/input_types_generator/test_names.py
@@ -160,6 +160,7 @@ def test_generate_returns_module_with_valid_field_names(
         _Bar: String!
         ____baz_: String!
         _: String!
+        schema: String!
     }
     """
 
@@ -186,4 +187,5 @@ def test_generate_returns_module_with_valid_field_names(
         "bar",
         "baz_",
         "underscore_named_field_",
+        "schema_",
     }
diff --git a/tests/client_generators/result_types_generator/schema.py b/tests/client_generators/result_types_generator/schema.py
index 95b07e9..a0932d0 100644
--- a/tests/client_generators/result_types_generator/schema.py
+++ b/tests/client_generators/result_types_generator/schema.py
@@ -33,6 +33,7 @@ type CustomType {
   _Field5: String!
   scalarField: SCALARA
   _: String!
+  schema: String!
 }
 
 type CustomType1 {
diff --git a/tests/client_generators/result_types_generator/test_names.py b/tests/client_generators/result_types_generator/test_names.py
index ed4685e..f2c07d1 100644
--- a/tests/client_generators/result_types_generator/test_names.py
+++ b/tests/client_generators/result_types_generator/test_names.py
@@ -120,6 +120,7 @@ def test_generate_returns_module_with_valid_field_names():
             _field4
             _Field5
             _
+            schema
         }
     }
     """
@@ -140,4 +141,10 @@ def test_generate_returns_module_with_valid_field_names():
     )  # Round trip because invalid identifiers get picked up in parse
     class_def = get_class_def(parsed, name_filter="CustomQueryCamelCaseQuery")
     field_names = get_assignment_target_names(class_def)
-    assert field_names == {"in_", "field4", "field5", "underscore_named_field_"}
+    assert field_names == {
+        "in_",
+        "field4",
+        "field5",
+        "underscore_named_field_",
+        "schema_",
+    }