Repository: marcosschroh/dataclasses-avroschema

Issue:
field ordering issue causing invalid python models
**Describe the bug**
Whenever there is a field with some additional metadata like "aliases" or "doc" it causes the generated python field to look like this:
```
    fieldwithdefault: str = "some default value"
    someotherfield: int = dataclasses.field(metadata={'aliases': ['oldname']})
```
However these get ordered behind (original order?) fields with a default value which is not valid python. Without aliases or doc these fields do correctly get shifted before fields with defaults.

**To Reproduce**
```
{
  "type": "record",
  "name": "Message",
  "fields": [
    {
      "name": "fieldwithdefault",
      "type": "string",
      "default": "some default value"
    },
    {
      "name": "someotherfield",
      "type": "long",
      "aliases": ["oldname"],
      "doc": "test"
    }
  ]
}
```

**Expected behavior**
fields get ordered with default values last
```
    someotherfield: int = dataclasses.field(metadata={'aliases': ['oldname']})
    fieldwithdefault: str = "some default value"
```


Discussion:
github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.4.4
:---:
:rocket: Deployed preview to https://marcosschroh.github.io/dataclasses-avroschema/pr-preview/pr-403/
on branch [`gh-pages`](https://github.com/marcosschroh/dataclasses-avroschema/tree/gh-pages) at 2023-08-30 14:14 UTC
<!-- Sticky Pull Request Commentpr-preview -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/403?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) Report
> Merging [#403](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/403?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) (8af3b65) into [master](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/commit/f009f4a98e50c5051dbc016f4f2c5724150c0b53?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) (f009f4a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master     #403   +/-   ##
=======================================
  Coverage   99.26%   99.26%           
=======================================
  Files          20       20           
  Lines        1497     1498    +1     
  Branches      274      275    +1     
=======================================
+ Hits         1486     1487    +1     
  Misses          3        3           
  Partials        8        8           
```


| [Files Changed](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/403?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) | Coverage Δ | |
|---|---|---|
| [...ataclasses\_avroschema/model\_generator/generator.py](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/403?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh#diff-ZGF0YWNsYXNzZXNfYXZyb3NjaGVtYS9tb2RlbF9nZW5lcmF0b3IvZ2VuZXJhdG9yLnB5) | `99.31% <100.00%> (+<0.01%)` | :arrow_up: |



Patch:
diff --git a/dataclasses_avroschema/model_generator/generator.py b/dataclasses_avroschema/model_generator/generator.py
index acce0e1..6da2653 100644
--- a/dataclasses_avroschema/model_generator/generator.py
+++ b/dataclasses_avroschema/model_generator/generator.py
@@ -275,14 +275,16 @@ class ModelGenerator:
             result = templates.field_template.safe_substitute(name=name, type=language_type)
 
         # optional field attribute
-        default = self.get_field_default(field_type=type, default=default, name=name, field_metadata=field_metadata)
+        default_generated = self.get_field_default(
+            field_type=type, default=default, name=name, field_metadata=field_metadata
+        )
 
         has_default = False
-        if default is not dataclasses.MISSING:
-            has_default = True
-
+        if default_generated is not dataclasses.MISSING:
             if type != field_utils.DECIMAL:
-                result += templates.field_default_template.safe_substitute(default=default)
+                result += templates.field_default_template.safe_substitute(default=default_generated)
+            if default is not dataclasses.MISSING:
+                has_default = True
 
         return FieldRepresentation(name=name, string_representation=result, has_default=has_default)
 


Test patch:
diff --git a/tests/model_generator/conftest.py b/tests/model_generator/conftest.py
index 58873a6..35b7af6 100644
--- a/tests/model_generator/conftest.py
+++ b/tests/model_generator/conftest.py
@@ -13,10 +13,10 @@ def schema() -> Dict:
         "fields": [
             {"name": "age", "type": "int"},
             {"name": "money_available", "type": "double"},
+            {"name": "weight", "type": "int", "unit": "kg"},
             {"name": "name", "type": "string", "default": "marcos"},
             {"name": "pet_age", "type": "int", "default": 1},
             {"name": "height", "type": "float", "default": 10.10},
-            {"name": "weight", "type": "int", "unit": "kg"},
             {"name": "expirience", "type": "int", "unit": "years", "default": 10},
             {
                 "name": "is_student",
@@ -502,3 +502,15 @@ def schema_with_pydantic_constrained_fields() -> JsonDict:
             {"pydantic-class": "conint(gt=10, lt=20)", "name": "constrained_int", "type": "int"},
         ],
     }
+
+
+@pytest.fixture
+def with_fields_with_metadata() -> JsonDict:
+    return {
+        "type": "record",
+        "name": "Message",
+        "fields": [
+            {"name": "fieldwithdefault", "type": "string", "default": "some default value"},
+            {"name": "someotherfield", "type": "long", "aliases": ["oldname"], "doc": "test"},
+        ],
+    }
diff --git a/tests/model_generator/test_model_generator.py b/tests/model_generator/test_model_generator.py
index a0a9279..5d01171 100644
--- a/tests/model_generator/test_model_generator.py
+++ b/tests/model_generator/test_model_generator.py
@@ -17,10 +17,10 @@ class User(AvroModel):
     \"""
     age: types.Int32
     money_available: float
+    weight: types.Int32 = dataclasses.field(metadata={'unit': 'kg'})
     name: str = "marcos"
     pet_age: types.Int32 = 1
     height: types.Float32 = 10.1
-    weight: types.Int32 = dataclasses.field(metadata={'unit': 'kg'})
     expirience: types.Int32 = dataclasses.field(metadata={'unit': 'years'}, default=10)
     is_student: bool = True
     encoded: bytes = b"Hi"
@@ -446,10 +446,10 @@ class User(AvroModel):
     \"""
     age: types.Int32
     money_available: float
+    weight: types.Int32 = dataclasses.field(metadata={'unit': 'kg'})
     name: str = "marcos"
     pet_age: types.Int32 = 1
     height: types.Float32 = 10.1
-    weight: types.Int32 = dataclasses.field(metadata={'unit': 'kg'})
     expirience: types.Int32 = dataclasses.field(metadata={'unit': 'years'}, default=10)
     is_student: bool = True
     encoded: bytes = b"Hi"
@@ -471,3 +471,23 @@ class Address(AvroModel):
     model_generator = ModelGenerator()
     result = model_generator.render_module(schemas=[schema, schema_2])
     assert result.strip() == expected_result.strip()
+
+
+def test_model_generator_with_fields_with_metadata(with_fields_with_metadata: types.JsonDict) -> None:
+    expected_result = """
+from dataclasses_avroschema import AvroModel
+import dataclasses
+
+
+@dataclasses.dataclass
+class Message(AvroModel):
+    someotherfield: int = dataclasses.field(metadata={'aliases': ['oldname'], 'doc': 'test'})
+    fieldwithdefault: str = "some default value"
+
+    class Meta:
+        field_order = ['fieldwithdefault', 'someotherfield']
+
+"""
+    model_generator = ModelGenerator()
+    result = model_generator.render(schema=with_fields_with_metadata)
+    assert result.strip() == expected_result.strip()