Repository: marcosschroh/dataclasses-avroschema

Issue:
Incorrect model file when avsc contains ""avro.java.string": "String" metadata
**Describe the bug**
The avsc file should generate valid python class but the generated class is invalid

**To Reproduce**
Sample Code

---Model Generation Code----
```
from dataclasses_avroschema import ModelGenerator, ModelType

model_generator = ModelGenerator()

schema =	{
		"type": "record",
		"name": "RecordBaz",
		"namespace": "com.foo",
		"fields": [{
			"name": "id",
			"type": "long"
		}, {
			"name": "category",
			"type": {
				"type": "string",
				"avro.java.string": "String"
			}
		}, {
			"name": "reason",
			"type": ["null", {
				"type": "string",
				"avro.java.string": "String"
			}],
			"default": None
		}, {
			"name": "owner",
			"type": ["null", {
				"type": "string",
				"avro.java.string": "String"
			}],
			"default": None
		}, {
			"name": "createdAt",
			"type": {
				"type": "record",
				"name": "MyDateTime",
				"namespace": "com.foo",
				"fields": [{
					"name": "year",
					"type": "int"
				}, {
					"name": "monthOfYear",
					"type": "int"
				}, {
					"name": "dayOfMonth",
					"type": "int"
				}, {
					"name": "hourOfDay",
					"type": "int"
				}, {
					"name": "minuteOfHour",
					"type": "int"
				}, {
					"name": "secondOfMinute",
					"type": "int"
				}, {
					"name": "millisOfSecond",
					"type": ["int", "null"],
					"default": 0
				}, {
					"name": "zone",
					"type": {
						"type": "string",
						"avro.java.string": "String"
					},
					"default": "America/New_York"
				}]
			}
		}, {
			"name": "deletedAt",
			"type": ["null", "com.foo.MyDateTime"],
			"default": None
		}, {
			"name": "Offset",
			"type": ["null", {
				"type": "string",
				"avro.java.string": "String"
			}],
			"default": None
		}]
	}



result = model_generator.render(schema=schema, model_type=ModelType.AVRODANTIC.value)
with open("models.py", mode="+w") as f:
    f.write(result)

```

-----models file-----
```
from dataclasses_avroschema import types
from dataclasses_avroschema.pydantic import AvroBaseModel
from pydantic import Field
import typing



class MyDateTime(AvroBaseModel):
    year: types.Int32
    monthOfYear: types.Int32
    dayOfMonth: types.Int32
    hourOfDay: types.Int32
    minuteOfHour: types.Int32
    secondOfMinute: types.Int32
    millisOfSecond: typing.Optional[types.Int32] = 0
    zone: str = Field(metadata={'avro.java.string': 'String'}) = "America/New_York"

    
    class Meta:
        namespace = "com.foo"


class RecordBaz(AvroBaseModel):
    id: int
    category: str = Field(metadata={'avro.java.string': 'String'})
    createdAt: MyDateTime
    reason: typing.Optional[str = Field(metadata={'avro.java.string': 'String'})] = None
    owner: typing.Optional[str = Field(metadata={'avro.java.string': 'String'})] = None
    deletedAt: typing.Optional[MyDateTime] = None
    Offset: typing.Optional[str = Field(metadata={'avro.java.string': 'String'})] = None

    
    class Meta:
        namespace = "com.foo"
        field_order = ['id', 'category', 'reason', 'owner', 'createdAt', 'deletedAt', 'Offset']

```
The above generated class has errors for field reason, owner, deletedAt and zone

**Expected behavior**
It should generated valid python class


Discussion:
github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.4.8
:---:
:rocket: Deployed preview to https://marcosschroh.github.io/dataclasses-avroschema/pr-preview/pr-776/
on branch [`gh-pages`](https://github.com/marcosschroh/dataclasses-avroschema/tree/gh-pages) at 2024-10-16 12:19 UTC
<!-- Sticky Pull Request Commentpr-preview -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/776?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) Report
Attention: Patch coverage is `99.13043%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 99.35%. Comparing base [(`1207e06`)](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/commit/1207e065ee8e5f3ded5f6d1334b06e54b20c5de0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) to head [(`905aa2c`)](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/commit/905aa2c484bbf76c4fe234221b09066a693c1b64?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).

| [Files with missing lines](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/776?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) | Patch % | Lines |
|---|---|---|
| [...ses\_avroschema/model\_generator/lang/python/base.py](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/776?src=pr&el=tree&filepath=dataclasses_avroschema%2Fmodel_generator%2Flang%2Fpython%2Fbase.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh#diff-ZGF0YWNsYXNzZXNfYXZyb3NjaGVtYS9tb2RlbF9nZW5lcmF0b3IvbGFuZy9weXRob24vYmFzZS5weQ==) | 99.09% | [1 Missing :warning: ](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/776?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #776      +/-   ##
==========================================
- Coverage   99.45%   99.35%   -0.10%     
==========================================
  Files          34       34              
  Lines        2003     2013      +10     
==========================================
+ Hits         1992     2000       +8     
- Misses         11       13       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/marcosschroh/dataclasses-avroschema/pull/776?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marcos+Schroh).


Patch:
diff --git a/dataclasses_avroschema/model_generator/lang/python/avrodantic/avrodantic_model_generator.py b/dataclasses_avroschema/model_generator/lang/python/avrodantic/avrodantic_model_generator.py
index 01250a7..0e7fa73 100644
--- a/dataclasses_avroschema/model_generator/lang/python/avrodantic/avrodantic_model_generator.py
+++ b/dataclasses_avroschema/model_generator/lang/python/avrodantic/avrodantic_model_generator.py
@@ -41,13 +41,9 @@ class AvroDanticModelGenerator(BaseGenerator):
         pydantic_class = field.get("pydantic-class")
 
         if pydantic_class is not None:
-            self.imports.add("import pydantic")
             return f"pydantic.{pydantic_class}"
         return None
 
-    def render_dataclass_field(self, properties: str) -> str:
-        self.imports.add("from pydantic import Field")
-        return super().render_dataclass_field(properties=properties)
-
     def add_class_imports(self) -> None:
+        self.imports.add("import pydantic")
         self.imports.add("from dataclasses_avroschema.pydantic import AvroBaseModel")
diff --git a/dataclasses_avroschema/model_generator/lang/python/base.py b/dataclasses_avroschema/model_generator/lang/python/base.py
index 68239c1..7b33e2c 100644
--- a/dataclasses_avroschema/model_generator/lang/python/base.py
+++ b/dataclasses_avroschema/model_generator/lang/python/base.py
@@ -17,10 +17,148 @@ from . import (
 )
 
 
-class FieldRepresentation(typing.NamedTuple):
+@dataclasses.dataclass
+class FieldRepresentation:
     name: str
-    string_representation: str
-    has_default: bool
+    avro_type: typing.Any
+    type_hint: str
+    default: typing.Any
+    template: Template
+    metadata: JsonDict = dataclasses.field(default_factory=dict)
+    is_complex_type: bool = False
+    children: typing.List["FieldRepresentation"] = dataclasses.field(default_factory=list)
+    inner_name: str = ""
+
+    def __post_init__(self) -> None:
+        if isinstance(self.avro_type, list):
+            self.avro_type = self.avro_type[0]
+            for child in self.children:
+                self.metadata.update(child.metadata)
+
+        if isinstance(self.avro_type, dict):
+            inner_type = self.avro_type["type"]
+            self.inner_name = self.avro_type.get("name", "")
+
+            if self.children:
+                self.child_field = self.children[0]
+                self.avro_type = self.child_field.avro_type
+                self.type_hint = self.type_hint or self.child_field.type_hint
+                self.default = self.default if self.default is not dataclasses.MISSING else self.child_field.default
+                self.metadata.update(self.child_field.metadata)
+
+            # Check that internal field name is different to the original name
+            # for example: {"name": "age", "type": { "type": "array", "items": "string", "name": "my_age" }}
+            # in this case my_age is not in age which means that this must be reflected in the model
+            # This does not applu to `enums` and `records`
+            if self.inner_name not in self.name and inner_type not in (
+                field_utils.ENUM,
+                field_utils.RECORD,
+            ):
+                self.metadata["inner_name"] = self.inner_name
+
+    def to_string(self) -> str:
+        """
+        Convert a field to string
+        """
+        if self.is_complex_type or not self.name:
+            # If the field is a complext type or
+            # name is an empty string (it means that the type is a native type
+            # with the form {"type": "a_primitive_type"}, example {"type": "string"})
+            result = self.type_hint
+        else:
+            result = templates.field_type_template.safe_substitute(name=self.name, type=self.type_hint)
+
+        # optional field attribute
+        default_generated = self.get_field_default()
+
+        if (
+            default_generated
+            not in (
+                dataclasses.MISSING,
+                "",
+            )
+            and self.avro_type != field_utils.DECIMAL
+        ):
+            result += templates.field_default_template.safe_substitute(default=default_generated)
+
+        return result
+
+    def get_field_default(self) -> str:
+        """
+        Returns the default value according to the field type
+
+        TODO: docstrings
+
+        Example:
+            If the default is "bond" the method should return '"bond"' so the double quotes
+            won't be scaped during the field render
+        """
+        field_metadata = self.metadata or {}
+        field_metadata_repr = None
+        default_repr = ""
+
+        if self.default is dataclasses.MISSING:
+            ...
+        elif self.avro_type in (
+            field_utils.STRING,
+            field_utils.UUID,
+        ):
+            default_repr = f'"{self.default}"'
+        elif self.avro_type in (
+            field_utils.BYTES,
+            field_utils.FIXED,
+        ):
+            default_repr = f'b"{self.default}"'
+        elif self.avro_type == field_utils.ENUM:
+            enum_name = self.inner_name or self.type_hint
+            default_repr = f"{casefy.pascalcase(enum_name)}.{casefy.uppercase(self.default)}"
+        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:
+                    # Try to get the last part in case that the type is namespaced `types.bus_type.Bus`
+                    field_type = self.avro_type.split(".")[-1]
+                    default_repr = templates.instance_template.safe_substitute(
+                        type=field_type, properties=f"**{self.default}"
+                    )
+                else:
+                    default_repr = str(self.default)
+            else:
+                default_repr = self.default.__class__.__name__
+        elif self.avro_type in avro_to_python_utils.LOGICAL_TYPES_TO_PYTHON:
+            func = avro_to_python_utils.LOGICAL_TYPES_TO_PYTHON[self.avro_type]
+            python_type = func(self.default)
+            template_func = avro_to_python_utils.LOGICAL_TYPE_TEMPLATES[self.avro_type]
+            default_repr = template_func(python_type)
+        else:
+            default_repr = str(self.default)
+
+        dataclass_field_properties = []
+        if field_metadata:
+            field_metadata_repr = f"metadata={field_metadata}"
+
+        if field_metadata_repr or isinstance(self.default, (dict, list)):
+            dataclass_field_properties = [field_metadata_repr]
+
+            if isinstance(self.default, (dict, list)):
+                if self.default:
+                    dataclass_prop = f"default_factory=lambda: {default_repr}"
+                else:
+                    dataclass_prop = f"default_factory={default_repr}"
+
+                dataclass_field_properties.append(dataclass_prop)
+            else:
+                if self.default is not dataclasses.MISSING:
+                    dataclass_field_properties.append(f"default={default_repr}")
+
+            default_repr = self.render_dataclass_field(
+                properties=", ".join([prop for prop in dataclass_field_properties if prop])
+            )
+
+        return default_repr
+
+    def render_dataclass_field(self, properties: str) -> str:
+        return self.template.safe_substitute(properties=properties)
 
 
 @dataclass
@@ -176,16 +314,14 @@ class BaseGenerator:
             self.render_field(field=field, model_name=name) for field in record_fields
         ]
 
-        fields_representation_copy = copy.deepcopy(fields_representation)
-        fields_representation.sort(key=lambda field: 1 if field.has_default else 0)
+        fields_representation_copy = copy.copy(fields_representation)
+        fields_representation.sort(key=lambda field: field.default is not dataclasses.MISSING)
 
         field_order = None
         if fields_representation_copy != fields_representation:
             field_order = [field.name for field in fields_representation_copy]
 
-        rendered_fields_string = self.field_identation.join(
-            field.string_representation for field in fields_representation
-        )
+        rendered_fields_string = self.field_identation.join(field.to_string() for field in fields_representation)
         docstring = self.render_docstring(docstring=schema.get("doc"))
 
         rendered_class = templates.class_template.safe_substitute(
@@ -203,9 +339,6 @@ class BaseGenerator:
 
         return rendered_class
 
-    def render_dataclass_field(self, properties: str) -> str:
-        return self.field_template.safe_substitute(properties=properties)
-
     def render_field(self, field: JsonDict, model_name: str) -> FieldRepresentation:
         """
         Render an avro field.
@@ -221,75 +354,62 @@ class BaseGenerator:
         type: AvroTypeRepr = field["type"]
         default = field.get("default", dataclasses.MISSING)
         field_metadata = self.get_field_metadata(field)
+        children = None
 
         # This flag tells whether the field is array, map, fixed
-        is_complex_type = has_default = False
+        is_complex_type = False
 
         if self.is_logical_type(field=field):
             is_complex_type = True
             # override the type so it can be use to get the default value in case that is needed
             type = field.get("logicalType") or field["type"]["logicalType"]
-            language_type = self.parse_logical_type(field=field)
+            type_hint = self.parse_logical_type(field=field)
         elif isinstance(type, dict):
-            field_representation = self.render_field(field=type, model_name=model_name)
-            language_type = field_representation.string_representation
-            has_default = field_representation.has_default
+            type_hint = ""
+            children = [self.render_field(field=type, model_name=model_name)]
         elif isinstance(type, list):
-            language_type = self.parse_union(field_types=type, model_name=model_name)
+            type_hint, children = self.parse_union(field_types=type, model_name=model_name)
         elif type == field_utils.ARRAY:
             is_complex_type = True
-            language_type = self.parse_array(field=field, model_name=model_name)
+            type_hint = self.parse_array(field=field, model_name=model_name)
         elif type == field_utils.MAP:
             is_complex_type = True
-            language_type = self.parse_map(field=field, model_name=model_name)
+            type_hint = self.parse_map(field=field, model_name=model_name)
         elif type == field_utils.ENUM:
             is_complex_type = True
-            language_type = self.parse_enum(field=field)
+            type_hint = self.parse_enum(field=field)
             # We must set the default Enums type level default to dataclasses.MISSING
             # as it is set in the Meta class.
             # Check https://github.com/marcosschroh/dataclasses-avroschema/issues/665
             default = dataclasses.MISSING
         elif type == field_utils.FIXED:
             is_complex_type = True
-            language_type = self.parse_fixed(field=field)
+            type_hint = self.parse_fixed(field=field)
         elif type == field_utils.RECORD:
             record = f"\n{self.render_class(schema=field)}"
             is_complex_type = True
             self.extras.append(record)
-            language_type = casefy.pascalcase(field["name"])
+            type_hint = casefy.pascalcase(field["name"])
         else:
             # Native field
-            language_type = self.get_language_type(type=type, model_name=model_name)
+            type_hint = self.get_language_type(type=type, model_name=model_name)
 
             # check if the language type must be replaced with some extra class
             # specified in the field metadata only after the native type was resolved
             type_from_metadata = self._resolve_type_from_metadata(field=field)
-            language_type = type_from_metadata or language_type
-
-        if is_complex_type or not name:
-            # If the field is a complext type or
-            # name is an empty string (it means that the type is a native type
-            # with the form {"type": "a_primitive_type"}, example {"type": "string"})
-            result = language_type
-        else:
-            result = templates.field_type_template.safe_substitute(name=name, type=language_type)
+            type_hint = type_from_metadata or type_hint
 
-        # optional field attribute
-        default_generated = self.get_field_default(
-            field_type=type, default=default, name=name, field_metadata=field_metadata
+        return FieldRepresentation(
+            name=name,
+            avro_type=type,
+            type_hint=type_hint,
+            template=self.field_template,
+            is_complex_type=is_complex_type,
+            default=default,
+            metadata=field_metadata,
+            children=children or [],
         )
 
-        if default_generated not in (
-            dataclasses.MISSING,
-            "",
-        ):
-            if type != field_utils.DECIMAL:
-                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)
-
     @staticmethod
     def generate_field_name(field: JsonDict) -> str:
         field_name = field.get("name", "")
@@ -356,7 +476,9 @@ class BaseGenerator:
             field_repr += templates.field_default_template.safe_substitute(default=default)
         return field_repr
 
-    def parse_union(self, *, field_types: typing.List, model_name: str) -> str:
+    def parse_union(
+        self, *, field_types: typing.List, model_name: str
+    ) -> typing.Tuple[str, typing.List[FieldRepresentation]]:
         """
         Parse an Avro union
 
@@ -371,27 +493,33 @@ class BaseGenerator:
         """
 
         # XXX: Maybe more useful in general
-        def render_type(typ: str) -> str:
-            if isinstance(typ, dict):
-                field_representation = self.render_field(field=typ, model_name=model_name)
-                return field_representation.string_representation
-            else:
-                return self.get_language_type(type=typ, model_name=model_name)
+        def render_type(avro_type: typing.Union[str, dict]) -> FieldRepresentation:
+            if isinstance(avro_type, str):
+                avro_type = {"type": avro_type}
+
+            return self.render_field(field=avro_type, model_name=model_name)
 
         if field_utils.NULL in field_types and len(field_types) == 2:
             # It is an optional field, we should include in the imports typing
             # and use the optional Template
             self.imports.add("import typing")
-            (field_type,) = [f for f in field_types if f != field_utils.NULL]
-            language_types = render_type(field_type)
-            return templates.optional_template.safe_substitute(type=language_types)
+            field_type = [f for f in field_types if f != field_utils.NULL]
+            field_representation = render_type(field_type[0])
+            type_hint = templates.optional_template.safe_substitute(type=field_representation.type_hint)
+            return type_hint, [field_representation]
+            # return templates.optional_template.safe_substitute(type=language_types)
         elif len(field_types) >= 2:
             # a union with more than 2 types
             self.imports.add("import typing")
-            language_types_repr = ", ".join(render_type(t) for t in field_types)
-            return templates.union_template.safe_substitute(type=language_types_repr)
+            children = [render_type(t) for t in field_types]
+            language_types_repr = ", ".join(child.type_hint for child in children)
+            type_hint = templates.union_template.safe_substitute(type=language_types_repr)
+            return type_hint, children
         else:
-            return render_type(field_types[0])
+            # This is a weird definitions when an union can have only one type
+            #  {"name": "money_available", "type": ["double"]},
+            field_representation = render_type(field_types[0])
+            return field_representation.type_hint, [field_representation]
 
     def parse_array(self, field: JsonDict, model_name: str) -> str:
         """
@@ -485,9 +613,9 @@ class BaseGenerator:
 
         if isinstance(type, dict):
             field_representation = self.render_field(field=type, model_name=model_name)
-            language_type = field_representation.string_representation
+            language_type = field_representation.to_string()
         elif isinstance(type, list):
-            language_type = self.parse_union(field_types=type, model_name=model_name)
+            language_type, _ = self.parse_union(field_types=type, model_name=model_name)
         else:
             language_type = self.get_language_type(type=type, model_name=model_name)
 
@@ -522,6 +650,8 @@ class BaseGenerator:
             "type",
             "default",
             "pydantic-class",
+            "precision",
+            "scale",
         ]
 
         type = field["type"]
@@ -545,104 +675,6 @@ class BaseGenerator:
 
         return {name: value for name, value in field.items() if name not in keys_to_ignore}
 
-    def get_field_default(
-        self,
-        *,
-        field_type: AvroTypeRepr,
-        default: typing.Any,
-        name: str,
-        field_metadata: typing.Optional[JsonDict] = None,
-    ) -> typing.Any:
-        """
-        Returns the default value according to the field type
-
-        TODO: docstrings
-
-        Example:
-            If the default is "bond" the method should return '"bond"' so the double quotes
-            won't be scaped during the field render
-        """
-        field_metadata = field_metadata or {}
-        field_metadata_repr = None
-        default_repr = ""
-
-        if isinstance(field_type, dict):
-            inner_type = field_type["type"]
-            inner_name = field_type.get("name", "")
-
-            # Check that internal field name is different to the original name
-            # for example: {"name": "age", "type": { "type": "array", "items": "string", "name": "my_age" }}
-            # in this case my_age is not in age which means that this must be reflected in the model
-            # This does not applu to `enums` and `records`
-            if inner_name not in name and inner_type not in (
-                field_utils.ENUM,
-                field_utils.RECORD,
-            ):
-                field_metadata["inner_name"] = inner_name
-
-            inner_default_repr = self.get_field_default(field_type=inner_type, name=inner_name or name, default=default)
-
-            if inner_default_repr not in (None, dataclasses.MISSING):
-                default_repr += inner_default_repr
-        elif default is dataclasses.MISSING:
-            pass
-        elif field_type in (
-            field_utils.STRING,
-            field_utils.UUID,
-        ):
-            default_repr = f'"{default}"'
-        elif field_type in (
-            field_utils.BYTES,
-            field_utils.FIXED,
-        ):
-            default_repr = f'b"{default}"'
-        elif field_type == field_utils.ENUM:
-            default_repr = f"{casefy.pascalcase(name)}.{casefy.uppercase(default)}"
-        elif isinstance(field_type, list):
-            # union type
-            default_repr = self.get_field_default(field_type=field_type[0], default=default, name=name)
-        elif isinstance(default, (dict, list)):
-            # Then is can be a regular dict as default or a record
-            if default:
-                if field_type not in field_utils.AVRO_TYPES:
-                    # Try to get the last part in case that the type is namespaced `types.bus_type.Bus`
-                    field_type = field_type.split(".")[-1]
-                    default = templates.instance_template.safe_substitute(type=field_type, properties=f"**{default}")
-
-                return f"{default}"
-            return default.__class__.__name__
-        elif field_type in avro_to_python_utils.LOGICAL_TYPES_TO_PYTHON:
-            func = avro_to_python_utils.LOGICAL_TYPES_TO_PYTHON[field_type]
-            python_type = func(default)
-            template_func = avro_to_python_utils.LOGICAL_TYPE_TEMPLATES[field_type]
-            default_repr = template_func(python_type)
-        else:
-            default_repr = str(default)
-
-        dataclass_field_properties = []
-        if field_metadata:
-            field_metadata_repr = f"metadata={field_metadata}"
-
-        if field_metadata_repr or isinstance(default, (dict, list)):
-            dataclass_field_properties = [field_metadata_repr]
-
-            if isinstance(default, (dict, list)):
-                if default:
-                    dataclass_prop = f"default_factory=lambda: {default_repr}"
-                else:
-                    dataclass_prop = f"default_factory={default_repr}"
-
-                dataclass_field_properties.append(dataclass_prop)
-            else:
-                if default is not dataclasses.MISSING:
-                    dataclass_field_properties.append(f"default={default_repr}")
-
-            default_repr = self.render_dataclass_field(
-                properties=", ".join([prop for prop in dataclass_field_properties if prop])
-            )
-
-        return default_repr
-
     @staticmethod
     def _add_schema_to_metaclass(schema_template: Template, schema: JsonDict) -> str:
         """
diff --git a/dataclasses_avroschema/model_generator/lang/python/pydantic/pydantic_model_generator.py b/dataclasses_avroschema/model_generator/lang/python/pydantic/pydantic_model_generator.py
index e805884..c747a74 100644
--- a/dataclasses_avroschema/model_generator/lang/python/pydantic/pydantic_model_generator.py
+++ b/dataclasses_avroschema/model_generator/lang/python/pydantic/pydantic_model_generator.py
@@ -11,7 +11,7 @@ class PydanticModelGenerator(BaseGenerator):
     def __post_init__(self) -> None:
         super().__post_init__()
 
-        self.base_class = "BaseModel"
+        self.base_class = "pydantic.BaseModel"
         self.imports_dict = {
             "dataclass_field": "from pydantic import Field",
         }
@@ -41,13 +41,8 @@ class PydanticModelGenerator(BaseGenerator):
         pydantic_class = field.get("pydantic-class")
 
         if pydantic_class is not None:
-            self.imports.add("import pydantic")
             return f"pydantic.{pydantic_class}"
         return None
 
-    def render_dataclass_field(self, properties: str) -> str:
-        self.imports.add("from pydantic import Field")
-        return super().render_dataclass_field(properties=properties)
-
     def add_class_imports(self) -> None:
-        self.imports.add("from pydantic import BaseModel")
+        self.imports.add("import pydantic")
diff --git a/dataclasses_avroschema/model_generator/lang/python/templates.py b/dataclasses_avroschema/model_generator/lang/python/templates.py
index ead7083..170d96c 100644
--- a/dataclasses_avroschema/model_generator/lang/python/templates.py
+++ b/dataclasses_avroschema/model_generator/lang/python/templates.py
@@ -59,7 +59,7 @@ $classes
 """
 
 # Pydanntic specific
-PYDANTIC_FIELD = "Field($properties)"
+PYDANTIC_FIELD = "pydantic.Field($properties)"
 
 field_type_template = Template(FIELD_TYPE_TEMPLATE)
 metaclass_field_template = Template(METACLASS_FIELD_TEMPLATE)


Test patch:
diff --git a/tests/model_generator/conftest.py b/tests/model_generator/conftest.py
index a75825f..bd005e7 100644
--- a/tests/model_generator/conftest.py
+++ b/tests/model_generator/conftest.py
@@ -76,6 +76,8 @@ def schema_primitive_types_as_defined_types() -> Dict:
                 "name": "expirience",
                 "type": {"type": "int", "unit": "years", "default": 10},
             },
+            {"name": "second_street", "type": {"type": "string", "avro.java.string": "String"}, "default": "Batman"},
+            {"name": "reason", "type": ["null", {"type": "string", "avro.java.string": "String"}], "default": None},
         ],
     }
 
@@ -259,6 +261,14 @@ def schema_with_enum_types() -> Dict:
                 ],
                 "default": None,
             },
+            {
+                "name": "limit_type",
+                "type": [
+                    {"type": "enum", "name": "LimitTypes", "symbols": ["MIN_LIMIT", "MAX_LIMIT", "EXACT_LIMIT"]},
+                    "null",
+                ],
+                "default": "MIN_LIMIT",
+            },
         ],
     }
 
@@ -304,6 +314,19 @@ def schema_with_enum_types_with_inner_default() -> Dict:
                 ],
                 "default": None,
             },
+            {
+                "name": "limit_type",
+                "type": [
+                    {
+                        "type": "enum",
+                        "name": "LimitTypes",
+                        "symbols": ["MIN_LIMIT", "MAX_LIMIT", "EXACT_LIMIT"],
+                        "default": "MIN_LIMIT",
+                    },
+                    "null",
+                ],
+                "default": "MIN_LIMIT",
+            },
         ],
     }
 
@@ -558,6 +581,78 @@ def schema_one_to_self_relationship() -> JsonDict:
     }
 
 
+@pytest.fixture
+def schema_with_multiple_levels_of_relationship() -> JsonDict:
+    return {
+        "type": "record",
+        "name": "User",
+        "fields": [
+            {"name": "name", "type": "string"},
+            {"name": "age", "type": "long"},
+            {
+                "name": "addresses",
+                "type": {
+                    "type": "map",
+                    "values": {
+                        "type": "record",
+                        "name": "Address",
+                        "fields": [
+                            {"name": "street", "type": "string"},
+                            {"name": "street_number", "type": "long"},
+                            {
+                                "name": "house_main_event",
+                                "type": {
+                                    "type": "record",
+                                    "name": "HouseMainEvent",
+                                    "fields": [
+                                        {
+                                            "name": "birthday",
+                                            "type": {"type": "int", "logicalType": "date"},
+                                            "default": 18181,
+                                        },
+                                        {
+                                            "name": "meeting_time",
+                                            "type": {"type": "int", "logicalType": "time-millis"},
+                                            "default": 64662000,
+                                        },
+                                        {
+                                            "name": "release_datetime",
+                                            "type": {"type": "long", "logicalType": "timestamp-millis"},
+                                            "default": 1570903062000,
+                                        },
+                                        {
+                                            "name": "event_uuid",
+                                            "type": {"type": "string", "logicalType": "uuid"},
+                                            "default": "09f00184-7721-4266-a955-21048a5cc235",
+                                        },
+                                    ],
+                                },
+                            },
+                        ],
+                        "doc": "An Address",
+                    },
+                    "name": "address",
+                },
+            },
+            {
+                "name": "crazy_union",
+                "type": [
+                    "string",
+                    {"type": "map", "values": "Address", "name": "optional_address"},
+                ],
+            },
+            {
+                "name": "optional_addresses",
+                "type": [
+                    "null",
+                    {"type": "map", "values": "Address", "name": "optional_address"},
+                ],
+                "default": None,
+            },
+        ],
+    }
+
+
 @pytest.fixture
 def schema_with_decimal_field() -> JsonDict:
     return {
@@ -937,12 +1032,14 @@ def with_fields_with_metadata() -> JsonDict:
                 "name": "fieldwithdefault",
                 "type": "string",
                 "default": "some default value",
+                "avro.java.string": "String",
             },
             {
                 "name": "someotherfield",
                 "type": "long",
                 "aliases": ["oldname"],
                 "doc": "test",
+                "avro.java.string": "String",
             },
         ],
     }
diff --git a/tests/model_generator/test_model_generator.py b/tests/model_generator/test_model_generator.py
index 5533a4f..5ede772 100644
--- a/tests/model_generator/test_model_generator.py
+++ b/tests/model_generator/test_model_generator.py
@@ -81,6 +81,8 @@ class Address(AvroModel):
     weight: types.Int32 = dataclasses.field(metadata={'unit': 'kg'})
     pet_age: types.Int32 = 1
     expirience: types.Int32 = dataclasses.field(metadata={'unit': 'years'}, default=10)
+    second_street: str = dataclasses.field(metadata={'avro.java.string': 'String'}, default="Batman")
+    reason: typing.Optional[str] = dataclasses.field(metadata={'avro.java.string': 'String'}, default=None)
 
 """
     model_generator = ModelGenerator()
@@ -231,12 +233,19 @@ class Cars({templates.ENUM_PYTHON_VERSION}):
     DUNA = "duna"
 
 
+class LimitTypes({templates.ENUM_PYTHON_VERSION}):
+    MIN_LIMIT = "MIN_LIMIT"
+    MAX_LIMIT = "MAX_LIMIT"
+    EXACT_LIMIT = "EXACT_LIMIT"
+
+
 @dataclasses.dataclass
 class User(AvroModel):
     favorite_color: FavoriteColor
     primary_color: FavoriteColor
     superheros: Superheros = Superheros.BATMAN
     cars: typing.Optional[Cars] = None
+    limit_type: typing.Optional[LimitTypes] = LimitTypes.MIN_LIMIT
 """
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_with_enum_types)
@@ -283,11 +292,21 @@ class Cars({templates.ENUM_PYTHON_VERSION}):
     class Meta:
         default = "ferrary"
 
+class LimitTypes({templates.ENUM_PYTHON_VERSION}):
+    MIN_LIMIT = "MIN_LIMIT"
+    MAX_LIMIT = "MAX_LIMIT"
+    EXACT_LIMIT = "EXACT_LIMIT"
+
+    {templates.METACLASS_DECORATOR}
+    class Meta:
+        default = "MIN_LIMIT"
+
 @dataclasses.dataclass
 class User(AvroModel):
     favorite_color: FavoriteColor
     superheros: Superheros = Superheros.BATMAN
     cars: typing.Optional[Cars] = None
+    limit_type: typing.Optional[LimitTypes] = LimitTypes.MIN_LIMIT
 """
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_with_enum_types_with_inner_default)
@@ -534,6 +553,48 @@ class User(AvroModel):
     assert result.strip() == expected_result.strip()
 
 
+def test_schema_with_multiple_levels_of_relationship(
+    schema_with_multiple_levels_of_relationship: types.JsonDict,
+) -> None:
+    expected_result = """
+from dataclasses_avroschema import AvroModel
+import dataclasses
+import datetime
+import typing
+import uuid
+
+
+@dataclasses.dataclass
+class HouseMainEvent(AvroModel):
+    birthday: datetime.date = datetime.date(2019, 10, 12)
+    meeting_time: datetime.time = datetime.time(17, 57, 42)
+    release_datetime: datetime.datetime = datetime.datetime(2019, 10, 12, 17, 57, 42, tzinfo=datetime.timezone.utc)
+    event_uuid: uuid.UUID = "09f00184-7721-4266-a955-21048a5cc235"
+
+
+@dataclasses.dataclass
+class Address(AvroModel):
+    \"""
+    An Address
+    \"""
+    street: str
+    street_number: int
+    house_main_event: HouseMainEvent
+
+
+@dataclasses.dataclass
+class User(AvroModel):
+    name: str
+    age: int
+    addresses: typing.Dict[str, Address]
+    crazy_union: typing.Union[str, typing.Dict[str, Address]]
+    optional_addresses: typing.Optional[typing.Dict[str, Address]] = None
+"""
+    model_generator = ModelGenerator()
+    result = model_generator.render(schema=schema_with_multiple_levels_of_relationship)
+    assert result.strip() == expected_result.strip()
+
+
 def test_decimal_field(schema_with_decimal_field: types.JsonDict) -> None:
     expected_result = """
 from dataclasses_avroschema import AvroModel
@@ -717,8 +778,8 @@ import dataclasses
 
 @dataclasses.dataclass
 class Message(AvroModel):
-    someotherfield: int = dataclasses.field(metadata={'aliases': ['oldname'], 'doc': 'test'})
-    fieldwithdefault: str = "some default value"
+    someotherfield: int = dataclasses.field(metadata={'aliases': ['oldname'], 'doc': 'test', 'avro.java.string': 'String'})
+    fieldwithdefault: str = dataclasses.field(metadata={'avro.java.string': 'String'}, default="some default value")
 
     
     class Meta:
diff --git a/tests/model_generator/test_model_pydantic_generator.py b/tests/model_generator/test_model_pydantic_generator.py
index 6b07dc3..244a3cf 100644
--- a/tests/model_generator/test_model_pydantic_generator.py
+++ b/tests/model_generator/test_model_pydantic_generator.py
@@ -7,12 +7,12 @@ from dataclasses_avroschema.model_generator.lang.python.avro_to_python_utils imp
 
 def test_pydantic_model(schema_one_to_many_map_relationship: types.JsonDict) -> None:
     expected_result = """
-from pydantic import BaseModel
+import pydantic
 import typing
 
 
 
-class Address(BaseModel):
+class Address(pydantic.BaseModel):
     \"""
     An Address
     \"""
@@ -21,7 +21,7 @@ class Address(BaseModel):
 
 
 
-class User(BaseModel):
+class User(pydantic.BaseModel):
     name: str
     age: int
     addresses: typing.Dict[str, Address]
@@ -37,18 +37,17 @@ def test_pydantic_model_with_meta_fields(
     schema_one_to_self_relationship: types.JsonDict,
 ) -> None:
     expected_result = """
-from pydantic import BaseModel
-from pydantic import Field
+import pydantic
 import typing
 
 
 
-class User(BaseModel):
+class User(pydantic.BaseModel):
     name: str
     age: int
     friend: typing.Optional["User"] = None
-    relatives: typing.List["User"] = Field(default_factory=list)
-    teammates: typing.Dict[str, "User"] = Field(default_factory=dict)
+    relatives: typing.List["User"] = pydantic.Field(default_factory=list)
+    teammates: typing.Dict[str, "User"] = pydantic.Field(default_factory=dict)
 """
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_one_to_self_relationship, model_type=ModelType.PYDANTIC.value)
@@ -60,6 +59,7 @@ def test_avro_pydantic_model(
 ) -> None:
     expected_result = """
 from dataclasses_avroschema.pydantic import AvroBaseModel
+import pydantic
 import typing
 
 
@@ -88,7 +88,7 @@ class User(AvroBaseModel):
 def test_avro_pydantic_invalid_python_identifiers(schema_with_invalid_python_identifiers: types.JsonDict) -> None:
     expected_result = """
 from dataclasses_avroschema.pydantic import AvroBaseModel
-from pydantic import Field
+import pydantic
 
 
 
@@ -96,10 +96,10 @@ class Address(AvroBaseModel):
     \"""
     An Address
     \"""
-    street_name: str = Field(metadata={'aliases': ['street-name']})
-    street_number: int = Field(metadata={'aliases': ['street-number']})
-    street_zipcode: str = Field(metadata={'aliases': ['zipcode', 'street-zipcode']})
-    city_name: str = Field(metadata={'aliases': ['city-name']})
+    street_name: str = pydantic.Field(metadata={'aliases': ['street-name']})
+    street_number: int = pydantic.Field(metadata={'aliases': ['street-number']})
+    street_zipcode: str = pydantic.Field(metadata={'aliases': ['zipcode', 'street-zipcode']})
+    city_name: str = pydantic.Field(metadata={'aliases': ['city-name']})
     ValidIdentifier: str
     anotherIdentifier: str
     _private: str
@@ -118,7 +118,7 @@ def test_avro_pydantic_model_with_meta_fields(
 ) -> None:
     expected_result = """
 from dataclasses_avroschema.pydantic import AvroBaseModel
-from pydantic import Field
+import pydantic
 import typing
 
 
@@ -127,8 +127,8 @@ class User(AvroBaseModel):
     name: str
     age: int
     friend: typing.Optional["User"] = None
-    relatives: typing.List["User"] = Field(default_factory=list)
-    teammates: typing.Dict[str, "User"] = Field(default_factory=dict)
+    relatives: typing.List["User"] = pydantic.Field(default_factory=list)
+    teammates: typing.Dict[str, "User"] = pydantic.Field(default_factory=dict)
 """
     model_generator = ModelGenerator()
     result = model_generator.render(schema=schema_one_to_self_relationship, model_type=ModelType.AVRODANTIC.value)
@@ -141,7 +141,7 @@ def test_avro_pydantic_primitive_types_as_defined_types(
     expected_result = """
 from dataclasses_avroschema import types
 from dataclasses_avroschema.pydantic import AvroBaseModel
-from pydantic import Field
+import pydantic
 import typing
 
 
@@ -149,9 +149,11 @@ import typing
 class Address(AvroBaseModel):
     street: str
     name: typing.Optional[str]
-    weight: types.Int32 = Field(metadata={'unit': 'kg'})
+    weight: types.Int32 = pydantic.Field(metadata={'unit': 'kg'})
     pet_age: types.Int32 = 1
-    expirience: types.Int32 = Field(metadata={'unit': 'years'}, default=10)
+    expirience: types.Int32 = pydantic.Field(metadata={'unit': 'years'}, default=10)
+    second_street: str = pydantic.Field(metadata={'avro.java.string': 'String'}, default="Batman")
+    reason: typing.Optional[str] = pydantic.Field(metadata={'avro.java.string': 'String'}, default=None)
 
 """
     model_generator = ModelGenerator()
@@ -166,13 +168,13 @@ def test_avro_pydantic_with_fields_with_metadata(
 ) -> None:
     expected_result = """
 from dataclasses_avroschema.pydantic import AvroBaseModel
-from pydantic import Field
+import pydantic
 
 
 
 class Message(AvroBaseModel):
-    someotherfield: int = Field(metadata={'aliases': ['oldname'], 'doc': 'test'})
-    fieldwithdefault: str = "some default value"
+    someotherfield: int = pydantic.Field(metadata={'aliases': ['oldname'], 'doc': 'test', 'avro.java.string': 'String'})
+    fieldwithdefault: str = pydantic.Field(metadata={'avro.java.string': 'String'}, default="some default value")
 
     
     class Meta:
@@ -188,6 +190,7 @@ def test_decimal_field(schema_with_decimal_field: types.JsonDict) -> None:
     expected_result = """
 from dataclasses_avroschema import types
 from dataclasses_avroschema.pydantic import AvroBaseModel
+import pydantic
 
 
 
@@ -212,6 +215,7 @@ from dataclasses_avroschema import types
 from dataclasses_avroschema.pydantic import AvroBaseModel
 import datetime
 import decimal
+import pydantic
 import typing
 import uuid