Repository: microsoft/kiota (csharp)

Issue:
Array of item /w enum creates invalid dataclass in python
### What are you generating using Kiota, clients or plugins?

API Client/SDK

### In what context or format are you using Kiota?

Linux executable

### Client library/SDK language

Python

### Describe the bug

I am trying to generate a library for https://developer.sophos.com/docs/endpoint-v1/1/overview
What I'm finding is that the generated python is creating invalid dataclasses for query parameters that include enums. For example in the api yaml:
```
        - in: query
          name: healthStatus
          description: Find endpoints by health status.
          style: form
          explode: false
          required: false
          schema:
            type: array
            items:
              type: string
              enum:
                - bad
                - good
                - suspicious
                - unknown
```
Creates a parameter in dataclass:
```
        # Find endpoints by health status.
        health_status: list[GetHealthStatusQueryParameterType] = []
```
Which causes the error:
```
Exception has occurred: ValueError
mutable default <class 'list'> for field health_status is not allowed: use default_factory
```

I don't really see a way to fix this without editing the generated code. I'm assuming it should be wrapped with Optional[] since it's an optional value. 

### Expected behavior

I should be able to run a get() without running into the "Exception has occurred: ValueError
mutable default <class 'list'> for field health_status is not allowed: use default_factory" Error. 

### How to reproduce

Generate for python using the attached yaml.
See endpoints/endpoints_request_builder.py

### Open API description file

[openapi-yaml-client-generated.zip](https://github.com/user-attachments/files/19457070/openapi-yaml-client-generated.zip)

### Kiota Version

1.24.1

### Latest Kiota version known to work for scenario above?(Not required)

_No response_

### Known Workarounds

_No response_

### Configuration

- OS: MacOS
- Arm64

### Debug output

<details><summary>Click to expand log</summary>
```


<log output here>

```
</details>


### Other information

_No response_

Pull request description:
Fixes #6350
closes #6489

* Use the PythonRefiner to set the default value for non-nullable array types.
* Update the CodePropertyWriter to respect the default value if set.
* Add tests for the changes

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 138dfee9e2..cbb3d68fe0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+- Python: Set a default value for non-nullable array fields. [#6350](https://github.com/microsoft/kiota/issues/6350)
+
 ## [1.27.0] - 2025-06-11
 
 ### Added
diff --git a/src/Kiota.Builder/Refiners/PythonRefiner.cs b/src/Kiota.Builder/Refiners/PythonRefiner.cs
index e23dd02b6e..4d00c86cee 100644
--- a/src/Kiota.Builder/Refiners/PythonRefiner.cs
+++ b/src/Kiota.Builder/Refiners/PythonRefiner.cs
@@ -8,6 +8,7 @@
 using Kiota.Builder.Extensions;
 
 namespace Kiota.Builder.Refiners;
+
 public class PythonRefiner : CommonLanguageRefiner, ILanguageRefiner
 {
     public PythonRefiner(GenerationConfiguration configuration) : base(configuration) { }
@@ -294,6 +295,19 @@ private static void CorrectPropertyType(CodeProperty currentProperty)
             currentProperty.DefaultValue = "None";
             currentProperty.Type.Name = currentProperty.Type.Name.ToFirstCharacterUpperCase();
         }
+        else if (currentProperty.IsOfKind(CodePropertyKind.QueryParameters, CodePropertyKind.QueryParameter)
+                 && currentProperty.Type.IsArray && !currentProperty.Type.IsNullable)
+        {
+            // Set the default_factory so that one single instance of the default values
+            // are not shared across instances of the class.
+            // This is required as of Python 3.11 with dataclasses.
+            // https://github.com/python/cpython/issues/8884
+            //
+            // Also handle the case change that would otherwise have been done
+            // below in the final else block.
+            currentProperty.Type.Name = currentProperty.Type.Name.ToFirstCharacterUpperCase();
+            currentProperty.DefaultValue = "field(default_factory=list)";
+        }
         else
         {
             currentProperty.Type.Name = currentProperty.Type.Name.ToFirstCharacterUpperCase();
diff --git a/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs
index d2e9e5a0d1..4e5c40cebd 100644
--- a/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs
+++ b/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs
@@ -3,6 +3,7 @@
 using Kiota.Builder.Extensions;
 
 namespace Kiota.Builder.Writers.Python;
+
 public class CodePropertyWriter : BaseElementWriter<CodeProperty, PythonConventionService>
 {
     private readonly CodeUsingWriter _codeUsingWriter;
@@ -40,7 +41,12 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w
             case CodePropertyKind.QueryParameter:
                 conventions.WriteInLineDescription(codeElement, writer);
                 var isNonNullableCollection = !codeElement.Type.IsNullable && codeElement.Type.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None;
-                writer.WriteLine($"{conventions.GetAccessModifier(codeElement.Access)}{codeElement.NamePrefix}{codeElement.Name}: {(codeElement.Type.IsNullable ? "Optional[" : string.Empty)}{returnType}{(codeElement.Type.IsNullable ? "]" : string.Empty)} {(isNonNullableCollection ? "= []" : "= None")}");
+                var defaultValue = isNonNullableCollection ? "[]" : "None";
+                if (!string.IsNullOrEmpty(codeElement.DefaultValue))
+                {
+                    defaultValue = codeElement.DefaultValue;
+                }
+                writer.WriteLine($"{conventions.GetAccessModifier(codeElement.Access)}{codeElement.NamePrefix}{codeElement.Name}: {(codeElement.Type.IsNullable ? "Optional[" : string.Empty)}{returnType}{(codeElement.Type.IsNullable ? "]" : string.Empty)} = {defaultValue}");
                 writer.WriteLine();
                 break;
             case CodePropertyKind.ErrorMessageOverride when parentClass.IsErrorDefinition:


Test patch:
diff --git a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
index 2e1722ffc5..f0635cfb4e 100644
--- a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
@@ -9,6 +9,7 @@
 using Xunit;
 
 namespace Kiota.Builder.Tests.Refiners;
+
 public class PythonLanguageRefinerTests
 {
     private readonly CodeNamespace root;
@@ -89,6 +90,37 @@ public async Task AddsQueryParameterMapperMethodAfterManglingAsync()
         Assert.Single(model.Properties, x => x.IsNameEscaped);
         Assert.Single(model.Methods, x => x.IsOfKind(CodeMethodKind.QueryParametersMapper));
     }
+
+    [Fact]
+    public async Task AddsQueryParameterDefaultValueToNonNullListsAsync()
+    {
+        var model = graphNS.AddClass(new CodeClass
+        {
+            Name = "somemodel",
+            Kind = CodeClassKind.QueryParameters,
+        }).First();
+
+        model.AddProperty(new CodeProperty
+        {
+            Name = "sortBy",
+            Type = new CodeType
+            {
+                Name = "string",
+                CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array,
+                IsNullable = false
+            },
+
+            Kind = CodePropertyKind.QueryParameter
+        });
+
+        Assert.Empty(model.Methods);
+
+        await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, graphNS);
+        Assert.Single(model.Properties, x => x.Name.Equals("sort_by"));
+        Assert.Single(model.Properties, x => x.IsNameEscaped);
+        Assert.Single(model.Properties, x => x.DefaultValue.Equals("field(default_factory=list)"));
+    }
+
     [Theory]
     [InlineData("None")]
     [InlineData("while")]
diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodePropertyWriterTests.cs
index 46e354959b..ce0163df05 100644
--- a/tests/Kiota.Builder.Tests/Writers/Python/CodePropertyWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Python/CodePropertyWriterTests.cs
@@ -7,6 +7,7 @@
 using Xunit;
 
 namespace Kiota.Builder.Tests.Writers.Python;
+
 public sealed class CodePropertyWriterTests : IDisposable
 {
     private const string DefaultPath = "./";
@@ -110,6 +111,18 @@ public void WritesDefaultValuesForProperties()
         Assert.Contains("= None", result);
     }
 
+    [Fact]
+    public void WritesDefaultValuesForNonNullableArrayPropertiesWhenDefaultValueSet()
+    {
+        property.Kind = CodePropertyKind.QueryParameter;
+        property.Type.IsNullable = false;
+        property.Type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array;
+        property.DefaultValue = "field(default_factory=list)";
+        writer.Write(property);
+        var result = tw.ToString();
+        Assert.Contains("= field(default_factory=list)", result);
+    }
+
     [Fact]
     public void WritePrimaryErrorMessagePropertyOption1()
     {