Repository: mirumee/ariadne-codegen (python)

Issue:
Applying @skip/@include to Nullable field lead to wrong model
According to [the spec](https://github.com/graphql/graphql-spec/blame/October2021/spec/Section%206%20--%20Execution.md#L487-L489), if `@skip`/`@include` is applied, execution of that field is ignored, thus not producing field in final data.

Using https://beta.pokeapi.co/graphql/console/ and take this example query:

```graphql
{
  pokemon_v2_pokemon(where: {name: {_eq: "pikachu"}}) {
    skipped_notnullable_field: name @skip(if: true)
    skipped_nullable_field: order @skip(if: true)
  }
}
```

And the result would be:

```json
{
  "data": {
    "pokemon_v2_pokemon": [
      {}
    ]
  }
}
```

Thus like #204, the expected code should be:

```python
class GetPokemonPokemonV2Pokemon(BaseModel):
    skipped_notnullable_field: Optional[str] = None
    skipped_nullable_field: Optional[int] = None
```

But the current generated code is:

```python
class GetPokemonPokemonV2Pokemon(BaseModel):
    skipped_notnullable_field: Optional[str] = None
    skipped_nullable_field: Optional[int]
```

Leading to a Pydantic verify error during runtime.

This is a follow up of #204

Pull request:
Set to None if nullable directive used on nullable field
The None should always be set on fields decorated by nullable directives.

Resolves #217 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a08bc82..26bfd50 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # CHANGELOG
 
+## UNRELEASED
+
+- Fixed generating results for nullable fields with nullable directives.
+
+
 ## 0.9.0 (2023-09-11)
 
 - Fixed generating operation string for nested inline fragments.
diff --git a/ariadne_codegen/client_generators/result_fields.py b/ariadne_codegen/client_generators/result_fields.py
index 111faad..e249d1e 100644
--- a/ariadne_codegen/client_generators/result_fields.py
+++ b/ariadne_codegen/client_generators/result_fields.py
@@ -79,11 +79,12 @@ def parse_operation_field(
             cast(AnnotationSlice, annotation.slice)
         )
 
-    if not (is_nullable(annotation)) and directives:
+    if directives:
         nullable_directives = [INCLUDE_DIRECTIVE_NAME, SKIP_DIRECTIVE_NAME]
         directives_names = [d.name.value for d in directives]
         if any(n in nullable_directives for n in directives_names):
-            annotation = generate_nullable_annotation(annotation)
+            if not is_nullable(annotation):
+                annotation = generate_nullable_annotation(annotation)
             default_value = generate_constant(None)
     return annotation, default_value, field_types_names
 


Test patch:
diff --git a/tests/client_generators/result_types_generator/schema.py b/tests/client_generators/result_types_generator/schema.py
index 1e5fa4e..3eff265 100644
--- a/tests/client_generators/result_types_generator/schema.py
+++ b/tests/client_generators/result_types_generator/schema.py
@@ -48,6 +48,7 @@ type CustomType2 {
 type CustomType3 {
   field1: CustomType1!
   field2: CustomType1!
+  field3: CustomType1
 }
 
 enum CustomEnum {
diff --git a/tests/client_generators/result_types_generator/test_directives.py b/tests/client_generators/result_types_generator/test_directives.py
index fb2c134..106432d 100644
--- a/tests/client_generators/result_types_generator/test_directives.py
+++ b/tests/client_generators/result_types_generator/test_directives.py
@@ -176,10 +176,13 @@ def test_generator_returns_module_with_handled_skip_and_include_directives(direc
             field2 {{
                 fielda
             }}
+            field3 @{directive}{{
+                fielda
+            }}
         }}
     }}
     """
-    expected_field_def = ast.AnnAssign(
+    expected_field_def_1 = ast.AnnAssign(
         target=ast.Name(id="field1"),
         annotation=ast.Subscript(
             value=ast.Name(id=OPTIONAL),
@@ -188,6 +191,15 @@ def test_generator_returns_module_with_handled_skip_and_include_directives(direc
         value=ast.Constant(value=None),
         simple=1,
     )
+    expected_field_def_2 = ast.AnnAssign(
+        target=ast.Name(id="field3"),
+        annotation=ast.Subscript(
+            value=ast.Name(id=OPTIONAL),
+            slice=ast.Name(id='"CustomQueryQuery3Field3"'),
+        ),
+        value=ast.Constant(value=None),
+        simple=1,
+    )
     generator = ResultTypesGenerator(
         schema=build_ast_schema(parse(SCHEMA_STR)),
         operation_definition=cast(
@@ -200,5 +212,5 @@ def test_generator_returns_module_with_handled_skip_and_include_directives(direc
 
     class_def = get_class_def(module, 1)
     assert class_def.name == "CustomQueryQuery3"
-    field_def = class_def.body[0]
-    assert compare_ast(field_def, expected_field_def)
+    assert compare_ast(class_def.body[0], expected_field_def_1)
+    assert compare_ast(class_def.body[2], expected_field_def_2)