Repository: marcosschroh/dataclasses-avroschema

Issue:
Wrong code generation for default fields with custom type
**Describe the bug**
Wrong default field value generation for custom field record

**To Reproduce**
Code generation code for this schema:
```
{
  "type": "record",
  "name": "Test",
  "fields": [
    {
      "name": "info",
      "type": {
        "type": "record",
        "name": "InfoField",
        "fields": [
          {
            "name": "vbool",
            "type": "boolean",
            "default": true
          },
          {
            "name": "vstr",
            "type": "string",
            "default": ""
          },
          {
            "name": "vint",
            "type": "int",
            "default": 0
          }
        ]
      },
      "default": {
        "vbool": true,
        "vstr": "",
        "vint": 0
      }
    }
  ]
}
```
Produce this classes:
```
@dataclasses.dataclass
class InfoField(AvroModel):
    vbool: bool = True
    vstr: str = ""
    vint: types.Int32 = 0


@dataclasses.dataclass
class Test(AvroModel):
    info: InfoField = dataclasses.field(default_factory=lambda: {'vbool': True, 'vstr': '', 'vint': 0})
```
That give a error
```Invalid default type <class 'dict'> for field "info". Default should be <class 'src.modules.avro_dataclasses.generated_dataclasses.InfoField'>```

**Expected behavior**
Default value must be an instance of custom field type:
```
@dataclasses.dataclass
class Test(AvroModel):
    info: InfoField = dataclasses.field(default_factory=lambda: InfoField(vbool=True, vstr='', vint=0))
```

Discussion:
github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.6.0
:---:
| <p></p> :rocket: View preview at <br> https://marcosschroh.github.io/dataclasses-avroschema/pr-preview/pr-835/ <br><br>
| <h6>Built to branch [`gh-pages`](https://github.com/marcosschroh/dataclasses-avroschema/tree/gh-pages) at 2025-02-04 12:23 UTC. <br> Preview will be ready when the [GitHub Pages deployment](https://github.com/marcosschroh/dataclasses-avroschema/deployments) is complete. <br><br> </h6>
<!-- Sticky Pull Request Commentpr-preview -->

Fix (patch):
diff --git a/dataclasses_avroschema/model_generator/lang/python/base.py b/dataclasses_avroschema/model_generator/lang/python/base.py
index 8916a2d..b33a4dc 100644
--- a/dataclasses_avroschema/model_generator/lang/python/base.py
+++ b/dataclasses_avroschema/model_generator/lang/python/base.py
@@ -111,9 +111,14 @@ class FieldRepresentation:
         elif isinstance(self.default, (dict, list)):
             # Then is can be a regular dict as default or a record
             if self.default:
-                if self.avro_type not in field_utils.AVRO_TYPES:
+                if self.avro_type not in field_utils.AVRO_TYPES or self.avro_type == field_utils.RECORD:
                     # Try to get the last part in case that the type is namespaced `types.bus_type.Bus`
                     field_type = self.avro_type.split(".")[-1]
+
+                    if field_type == field_utils.RECORD:
+                        # Then it is a record defined by the first time with a default value
+                        field_type = self.type_hint
+
                     default_repr = templates.instance_template.safe_substitute(
                         type=field_type, properties=f"**{self.default}"
                     )
diff --git a/dataclasses_avroschema/utils.py b/dataclasses_avroschema/utils.py
index d4fd92f..350a8d0 100644
--- a/dataclasses_avroschema/utils.py
+++ b/dataclasses_avroschema/utils.py
@@ -28,7 +28,7 @@ def _get_typing_objects_by_name_of(name: str) -> tuple[typing.Any, ...]:
     """Get the member named `name` from both `typing` and `typing-extensions` (if it exists)."""
     result = tuple(getattr(module, name) for module in (typing, typing_extensions) if hasattr(module, name))
     if not result:
-        raise ValueError(f'Neither `typing` nor `typing_extensions` has an object called {name!r}')
+        raise ValueError(f"Neither `typing` nor `typing_extensions` has an object called {name!r}")
     return result
 
 


Test changes:
diff --git a/tests/model_generator/conftest.py b/tests/model_generator/conftest.py
index 9c03bbf..8265394 100644
--- a/tests/model_generator/conftest.py
+++ b/tests/model_generator/conftest.py
@@ -459,6 +459,7 @@ def schema_one_to_one_relationship() -> JsonDict:
                     ],
                     "doc": "An Address",
                 },
+                "default": {"street": "Main Street", "street_number": 10},
             },
             {
                 "name": "extra_address",
@@ -833,6 +834,11 @@ def schema_one_to_many_map_relationship() -> JsonDict:
                     {"type": "map", "values": "Address", "name": "optional_address"},
                 ],
             },
+            {
+                "name": "default_address",
+                "type": "Address",
+                "default": {"street": "Main Street", "street_number": 10},
+            },
             {
                 "name": "optional_addresses",
                 "type": [
diff --git a/tests/model_generator/test_model_generator.py b/tests/model_generator/test_model_generator.py
index 86bcae7..103a7b6 100644
--- a/tests/model_generator/test_model_generator.py
+++ b/tests/model_generator/test_model_generator.py
@@ -460,10 +460,15 @@ class Address(AvroModel):
 class User(AvroModel):
     name: str
     age: int
-    address: Address
     extra_address: Address
     crazy_union: typing.Union[str, Address]
+    address: Address = dataclasses.field(default_factory=lambda: Address(**{'street': 'Main Street', 'street_number': 10}))
     optional_address: typing.Optional[Address] = None
+
+    
+    class Meta:
+        field_order = ['name', 'age', 'address', 'extra_address', 'crazy_union', 'optional_address']
+  
 """
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_one_to_one_relationship)
@@ -720,6 +725,7 @@ class User(AvroModel):
     age: int
     addresses: typing.Dict[str, Address]
     crazy_union: typing.Union[str, typing.Dict[str, Address]]
+    default_address: Address = dataclasses.field(default_factory=lambda: Address(**{'street': 'Main Street', 'street_number': 10}))
     optional_addresses: typing.Optional[typing.Dict[str, Address]] = None
 """
     model_generator = ModelGenerator()
diff --git a/tests/model_generator/test_model_pydantic_generator.py b/tests/model_generator/test_model_pydantic_generator.py
index 2be53c9..8af1067 100644
--- a/tests/model_generator/test_model_pydantic_generator.py
+++ b/tests/model_generator/test_model_pydantic_generator.py
@@ -26,8 +26,9 @@ class User(pydantic.BaseModel):
     age: int
     addresses: typing.Dict[str, Address]
     crazy_union: typing.Union[str, typing.Dict[str, Address]]
+    default_address: Address = pydantic.Field(default_factory=lambda: Address(**{'street': 'Main Street', 'street_number': 10}))
     optional_addresses: typing.Optional[typing.Dict[str, Address]] = None
-"""
+"""  # noqa: E501
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_one_to_many_map_relationship, model_type=ModelType.PYDANTIC.value)
     assert result.strip() == expected_result.strip()
@@ -78,8 +79,9 @@ class User(AvroBaseModel):
     age: int
     addresses: typing.Dict[str, Address]
     crazy_union: typing.Union[str, typing.Dict[str, Address]]
+    default_address: Address = pydantic.Field(default_factory=lambda: Address(**{'street': 'Main Street', 'street_number': 10}))
     optional_addresses: typing.Optional[typing.Dict[str, Address]] = None
-"""
+"""  # noqa: E501
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_one_to_many_map_relationship, model_type=ModelType.AVRODANTIC.value)
     assert result.strip() == expected_result.strip()