From 99bbd45a99ab39ba0e8800f75ef7991d5f0f2cc0 Mon Sep 17 00:00:00 2001 From: mishamsk Date: Sat, 16 Dec 2023 22:17:43 -0500 Subject: [PATCH 1/3] add support for local types --- mashumaro/core/meta/code/builder.py | 14 +++++++++++++- mashumaro/core/meta/helpers.py | 4 ++++ mashumaro/core/meta/types/common.py | 11 ++++++++--- tests/test_common.py | 14 ++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/mashumaro/core/meta/code/builder.py b/mashumaro/core/meta/code/builder.py index a3daba27..fa7a34d9 100644 --- a/mashumaro/core/meta/code/builder.py +++ b/mashumaro/core/meta/code/builder.py @@ -45,6 +45,7 @@ is_hashable, is_init_var, is_literal, + is_local_type, is_named_tuple, is_optional, is_type_var_any, @@ -52,7 +53,12 @@ substitute_type_params, type_name, ) -from mashumaro.core.meta.types.common import FieldContext, NoneType, ValueSpec +from mashumaro.core.meta.types.common import ( + FieldContext, + NoneType, + ValueSpec, + clean_id, +) from mashumaro.core.meta.types.pack import PackerRegistry from mashumaro.core.meta.types.unpack import ( SubtypeUnpackerBuilder, @@ -306,6 +312,7 @@ def compile(self) -> None: else: print(f"{type_name(self.cls)}:") print(code) + exec(code, self.globals, self.__dict__) def evaluate_forward_ref( @@ -1256,6 +1263,11 @@ def build( fname ), ) + + if is_local_type(ftype): + field_type = clean_id(field_type) + self.ensure_object_imported(ftype, field_type) + could_be_none = ( ftype in (typing.Any, type(None), None) or is_type_var_any(self.parent.get_real_type(fname, ftype)) diff --git a/mashumaro/core/meta/helpers.py b/mashumaro/core/meta/helpers.py index eee4ab8f..5f948f8a 100644 --- a/mashumaro/core/meta/helpers.py +++ b/mashumaro/core/meta/helpers.py @@ -389,6 +389,10 @@ def is_literal(typ: Type) -> bool: return False +def is_local_type(typ: Type) -> bool: + return "" in getattr(typ, "__qualname__", "") + + def not_none_type_arg( type_args: Tuple[Type, ...], resolved_type_params: Optional[Dict[Type, Type]] = None, diff --git a/mashumaro/core/meta/types/common.py b/mashumaro/core/meta/types/common.py index 40f7976b..42245203 100644 --- a/mashumaro/core/meta/types/common.py +++ b/mashumaro/core/meta/types/common.py @@ -1,4 +1,5 @@ import collections.abc +import re import uuid from abc import ABC, abstractmethod from dataclasses import dataclass, field, replace @@ -300,7 +301,11 @@ def random_hex() -> str: return str(uuid.uuid4().hex) +_PY_VALID_ID_RE = re.compile(r"\W|^(?=\d)") + + def clean_id(value: str) -> str: - for c in ".<>": - value = value.replace(c, "_") - return value + if not value: + return "_" + + return _PY_VALID_ID_RE.sub("_", value) diff --git a/tests/test_common.py b/tests/test_common.py index f832cc43..8ac0d290 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -262,3 +262,17 @@ def test_kw_args_when_pos_arg_is_overridden_with_field(): assert loaded.pos2 == 2 assert loaded.pos3 == 3 assert loaded.kw1 == 4 + + +def test_local_type(): + @dataclass + class LocalType: + pass + + @dataclass + class DataClassWithLocalType(DataClassDictMixin): + x: LocalType + + obj = DataClassWithLocalType(LocalType()) + assert obj.to_dict() == {"x": {}} + assert DataClassWithLocalType.from_dict({"x": {}}) == obj From 4998a147da45f85d61e0516674170686618987c3 Mon Sep 17 00:00:00 2001 From: mishamsk Date: Sat, 16 Dec 2023 23:04:56 -0500 Subject: [PATCH 2/3] implement local support for more types, both pack & unpack add more types to test --- mashumaro/core/meta/code/builder.py | 6 +-- mashumaro/core/meta/helpers.py | 4 +- mashumaro/core/meta/types/pack.py | 18 +++++++ mashumaro/core/meta/types/unpack.py | 74 ++++++++++++++++++++++--- tests/test_common.py | 83 ++++++++++++++++++++++++++--- 5 files changed, 165 insertions(+), 20 deletions(-) diff --git a/mashumaro/core/meta/code/builder.py b/mashumaro/core/meta/code/builder.py index fa7a34d9..1aa449c9 100644 --- a/mashumaro/core/meta/code/builder.py +++ b/mashumaro/core/meta/code/builder.py @@ -45,7 +45,7 @@ is_hashable, is_init_var, is_literal, - is_local_type, + is_local_type_name, is_named_tuple, is_optional, is_type_var_any, @@ -1264,9 +1264,9 @@ def build( ), ) - if is_local_type(ftype): + if is_local_type_name(field_type): field_type = clean_id(field_type) - self.ensure_object_imported(ftype, field_type) + self.parent.ensure_object_imported(ftype, field_type) could_be_none = ( ftype in (typing.Any, type(None), None) diff --git a/mashumaro/core/meta/helpers.py b/mashumaro/core/meta/helpers.py index 5f948f8a..d73d696b 100644 --- a/mashumaro/core/meta/helpers.py +++ b/mashumaro/core/meta/helpers.py @@ -389,8 +389,8 @@ def is_literal(typ: Type) -> bool: return False -def is_local_type(typ: Type) -> bool: - return "" in getattr(typ, "__qualname__", "") +def is_local_type_name(type_name: str) -> bool: + return "" in type_name def not_none_type_arg( diff --git a/mashumaro/core/meta/types/pack.py b/mashumaro/core/meta/types/pack.py index a7fc4ed0..cdbb5feb 100644 --- a/mashumaro/core/meta/types/pack.py +++ b/mashumaro/core/meta/types/pack.py @@ -32,6 +32,7 @@ is_final, is_generic, is_literal, + is_local_type_name, is_named_tuple, is_new_type, is_not_required, @@ -306,6 +307,11 @@ def pack_union( spec.field_ctx.name ), ) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + if spec.builder.is_nailed: lines.append( "raise InvalidFieldValue(" @@ -359,6 +365,13 @@ def pack_literal(spec: ValueSpec) -> Expression: typ=value_type, resolved_type_params=resolved_type_params, ) + + if is_local_type_name(enum_type_name): + enum_type_name = clean_id(enum_type_name) + spec.builder.ensure_object_imported( + value_type, enum_type_name + ) + with lines.indent( f"if value == {enum_type_name}.{literal_value.name}:" ): @@ -373,6 +386,11 @@ def pack_literal(spec: ValueSpec) -> Expression: typ=spec.type, resolved_type_params=resolved_type_params, ) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + if spec.builder.is_nailed: lines.append( f"raise InvalidFieldValue('{spec.field_ctx.name}'," diff --git a/mashumaro/core/meta/types/unpack.py b/mashumaro/core/meta/types/unpack.py index b979d2a3..32ac6d8e 100644 --- a/mashumaro/core/meta/types/unpack.py +++ b/mashumaro/core/meta/types/unpack.py @@ -39,6 +39,7 @@ is_final, is_generic, is_literal, + is_local_type_name, is_named_tuple, is_new_type, is_not_required, @@ -180,6 +181,10 @@ def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: ), ) if spec.builder.is_nailed: + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + lines.append( "raise InvalidFieldValue(" f"'{spec.field_ctx.name}',{field_type},value,cls)" @@ -203,7 +208,15 @@ def get_method_prefix(self) -> str: def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: for literal_value in get_literal_values(spec.type): if isinstance(literal_value, enum.Enum): - enum_type_name = type_name(type(literal_value)) + lit_type = type(literal_value) + enum_type_name = type_name(lit_type) + + if is_local_type_name(enum_type_name): + enum_type_name = clean_id(enum_type_name) + spec.builder.ensure_object_imported( + lit_type, enum_type_name + ) + with lines.indent( f"if value == {enum_type_name}.{literal_value.name}.value:" ): @@ -300,6 +313,10 @@ def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: variants = self._get_variant_names_iterable(spec) variants_type_expr = type_name(spec.type) + if is_local_type_name(variants_type_expr): + variants_type_expr = clean_id(variants_type_expr) + spec.builder.ensure_object_imported(spec.type, variants_type_expr) + if variants_attr not in variants_attr_holder.__dict__: setattr(variants_attr_holder, variants_attr, {}) variant_method_name = spec.builder.get_unpack_method_name( @@ -565,7 +582,14 @@ def _unpack_annotated_serializable_type( ], ) unpacker = UnpackerRegistry.get(spec.copy(type=value_type)) - return f"{type_name(spec.type)}._deserialize({unpacker})" + + field_type = type_name(spec.type) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + return f"{field_type}._deserialize({unpacker})" @register @@ -578,7 +602,12 @@ def unpack_serializable_type(spec: ValueSpec) -> Optional[Expression]: if spec.origin_type.__use_annotations__: return _unpack_annotated_serializable_type(spec) else: - return f"{type_name(spec.type)}._deserialize({spec.expression})" + field_type = type_name(spec.type) + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + return f"{field_type}._deserialize({spec.expression})" @register @@ -588,8 +617,14 @@ def unpack_generic_serializable_type(spec: ValueSpec) -> Optional[Expression]: type_arg_names = ", ".join( list(map(type_name, get_args(spec.type))) ) + field_type = type_name(spec.origin_type) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + return ( - f"{type_name(spec.type)}._deserialize({spec.expression}, " + f"{field_type}._deserialize({spec.expression}, " f"[{type_arg_names}])" ) @@ -990,7 +1025,12 @@ def unpack_named_tuple(spec: ValueSpec) -> Expression: unpackers.append(unpacker) if not defaults: - return f"{type_name(spec.type)}({', '.join(unpackers)})" + field_type = type_name(spec.type) + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + return f"{field_type}({', '.join(unpackers)})" lines = CodeLines() method_name = ( @@ -1015,7 +1055,13 @@ def unpack_named_tuple(spec: ValueSpec) -> Expression: lines.append(f"fields.append({unpacker})") with lines.indent("except IndexError:"): lines.append("pass") - lines.append(f"return {type_name(spec.type)}(*fields)") + + field_type = type_name(spec.type) + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + lines.append(f"return {field_type}(*fields)") lines.append( f"setattr({spec.cls_attrs_name}, '{method_name}', {method_name})" ) @@ -1194,10 +1240,22 @@ def unpack_pathlike(spec: ValueSpec) -> Optional[Expression]: spec.builder.ensure_module_imported(pathlib) return f"{type_name(pathlib.PurePath)}({spec.expression})" elif issubclass(spec.origin_type, os.PathLike): - return f"{type_name(spec.origin_type)}({spec.expression})" + field_type = type_name(spec.origin_type) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + return f"{field_type}({spec.expression})" @register def unpack_enum(spec: ValueSpec) -> Optional[Expression]: if issubclass(spec.origin_type, enum.Enum): - return f"{type_name(spec.origin_type)}({spec.expression})" + field_type = type_name(spec.origin_type) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + spec.builder.ensure_object_imported(spec.type, field_type) + + return f"{field_type}({spec.expression})" diff --git a/tests/test_common.py b/tests/test_common.py index 8ac0d290..d62f15fa 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,5 +1,8 @@ import dataclasses from dataclasses import dataclass, field +from enum import Enum +from pathlib import PurePosixPath +from typing import Any, Literal, NamedTuple import msgpack import pytest @@ -264,15 +267,81 @@ def test_kw_args_when_pos_arg_is_overridden_with_field(): assert loaded.kw1 == 4 -def test_local_type(): +def test_local_types(): @dataclass - class LocalType: + class LocalDataclassType: + foo: int + + class LocalNamedTupleType(NamedTuple): + foo: int + + class LocalPathLike(PurePosixPath): pass + class LocalEnumType(Enum): + FOO = "foo" + + class LocalSerializableType(SerializableType): + @classmethod + def _deserialize(self, value): + return LocalSerializableType() + + def _serialize(self) -> Any: + return {} + + def __eq__(self, __value: object) -> bool: + return isinstance(__value, LocalSerializableType) + + class LocalGenericSerializableType(GenericSerializableType): + @classmethod + def _deserialize(self, value, types): + return LocalGenericSerializableType() + + def _serialize(self, types) -> Any: + return {} + + def __eq__(self, __value: object) -> bool: + return isinstance(__value, LocalGenericSerializableType) + @dataclass class DataClassWithLocalType(DataClassDictMixin): - x: LocalType - - obj = DataClassWithLocalType(LocalType()) - assert obj.to_dict() == {"x": {}} - assert DataClassWithLocalType.from_dict({"x": {}}) == obj + x1: LocalDataclassType + x2: LocalNamedTupleType + x3: LocalPathLike + x4: LocalEnumType + x4_1: Literal[LocalEnumType.FOO] + x5: LocalSerializableType + x6: LocalGenericSerializableType + + obj = DataClassWithLocalType( + x1=LocalDataclassType(foo=0), + x2=LocalNamedTupleType(foo=0), + x3=LocalPathLike("path/to/file"), + x4=LocalEnumType.FOO, + x4_1=LocalEnumType.FOO, + x5=LocalSerializableType(), + x6=LocalGenericSerializableType(), + ) + assert obj.to_dict() == { + "x1": {"foo": 0}, + "x2": [0], + "x3": "path/to/file", + "x4": "foo", + "x4_1": "foo", + "x5": {}, + "x6": {}, + } + assert ( + DataClassWithLocalType.from_dict( + { + "x1": {"foo": 0}, + "x2": [0], + "x3": "path/to/file", + "x4": "foo", + "x4_1": "foo", + "x5": {}, + "x6": {}, + } + ) + == obj + ) From f33a5289db22bdd2e06ff1cc89a729313aefe979 Mon Sep 17 00:00:00 2001 From: mishamsk Date: Fri, 22 Dec 2023 20:55:27 -0500 Subject: [PATCH 3/3] use builder method to get safe type identifier --- mashumaro/core/meta/code/builder.py | 21 +++++++--- mashumaro/core/meta/types/pack.py | 21 ++-------- mashumaro/core/meta/types/unpack.py | 64 +++++++---------------------- 3 files changed, 33 insertions(+), 73 deletions(-) diff --git a/mashumaro/core/meta/code/builder.py b/mashumaro/core/meta/code/builder.py index 1aa449c9..4e65ae5f 100644 --- a/mashumaro/core/meta/code/builder.py +++ b/mashumaro/core/meta/code/builder.py @@ -216,6 +216,21 @@ def get_field_types( ) -> typing.Dict[str, typing.Any]: return self.__get_field_types(include_extras=include_extras) + def get_type_name_identifier( + self, + typ: typing.Optional[typing.Type], + resolved_type_params: typing.Optional[ + typing.Dict[typing.Type, typing.Type] + ] = None, + ) -> str: + field_type = type_name(typ, resolved_type_params=resolved_type_params) + + if is_local_type_name(field_type): + field_type = clean_id(field_type) + self.ensure_object_imported(typ, field_type) + + return field_type + @property # type: ignore @lru_cache() def dataclass_fields(self) -> typing.Dict[str, Field]: @@ -1257,17 +1272,13 @@ def build( ) -> FieldUnpackerCodeBlock: default = self.parent.get_field_default(fname) has_default = default is not MISSING - field_type = type_name( + field_type = self.parent.get_type_name_identifier( ftype, resolved_type_params=self.parent.get_field_resolved_type_params( fname ), ) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - self.parent.ensure_object_imported(ftype, field_type) - could_be_none = ( ftype in (typing.Any, type(None), None) or is_type_var_any(self.parent.get_real_type(fname, ftype)) diff --git a/mashumaro/core/meta/types/pack.py b/mashumaro/core/meta/types/pack.py index cdbb5feb..1e181ae3 100644 --- a/mashumaro/core/meta/types/pack.py +++ b/mashumaro/core/meta/types/pack.py @@ -32,7 +32,6 @@ is_final, is_generic, is_literal, - is_local_type_name, is_named_tuple, is_new_type, is_not_required, @@ -301,17 +300,13 @@ def pack_union( with lines.indent("try:"): lines.append(f"return {packer}") lines.append("except Exception: pass") - field_type = type_name( + field_type = spec.builder.get_type_name_identifier( spec.type, resolved_type_params=spec.builder.get_field_resolved_type_params( spec.field_ctx.name ), ) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) - if spec.builder.is_nailed: lines.append( "raise InvalidFieldValue(" @@ -361,17 +356,11 @@ def pack_literal(spec: ValueSpec) -> Expression: spec.copy(type=value_type, expression="value") ) if isinstance(literal_value, enum.Enum): - enum_type_name = type_name( + enum_type_name = spec.builder.get_type_name_identifier( typ=value_type, resolved_type_params=resolved_type_params, ) - if is_local_type_name(enum_type_name): - enum_type_name = clean_id(enum_type_name) - spec.builder.ensure_object_imported( - value_type, enum_type_name - ) - with lines.indent( f"if value == {enum_type_name}.{literal_value.name}:" ): @@ -382,15 +371,11 @@ def pack_literal(spec: ValueSpec) -> Expression: ): with lines.indent(f"if value == {literal_value!r}:"): lines.append(f"return {packer}") - field_type = type_name( + field_type = spec.builder.get_type_name_identifier( typ=spec.type, resolved_type_params=resolved_type_params, ) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) - if spec.builder.is_nailed: lines.append( f"raise InvalidFieldValue('{spec.field_ctx.name}'," diff --git a/mashumaro/core/meta/types/unpack.py b/mashumaro/core/meta/types/unpack.py index 32ac6d8e..976cb0a2 100644 --- a/mashumaro/core/meta/types/unpack.py +++ b/mashumaro/core/meta/types/unpack.py @@ -39,7 +39,6 @@ is_final, is_generic, is_literal, - is_local_type_name, is_named_tuple, is_new_type, is_not_required, @@ -174,17 +173,13 @@ def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: with lines.indent("try:"): lines.append(f"return {unpacker}") lines.append("except Exception: pass") - field_type = type_name( + field_type = spec.builder.get_type_name_identifier( spec.type, resolved_type_params=spec.builder.get_field_resolved_type_params( spec.field_ctx.name ), ) if spec.builder.is_nailed: - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) - lines.append( "raise InvalidFieldValue(" f"'{spec.field_ctx.name}',{field_type},value,cls)" @@ -209,13 +204,9 @@ def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: for literal_value in get_literal_values(spec.type): if isinstance(literal_value, enum.Enum): lit_type = type(literal_value) - enum_type_name = type_name(lit_type) - - if is_local_type_name(enum_type_name): - enum_type_name = clean_id(enum_type_name) - spec.builder.ensure_object_imported( - lit_type, enum_type_name - ) + enum_type_name = spec.builder.get_type_name_identifier( + lit_type + ) with lines.indent( f"if value == {enum_type_name}.{literal_value.name}.value:" @@ -311,11 +302,7 @@ def _add_body(self, spec: ValueSpec, lines: CodeLines) -> None: variants_map = self._get_variants_map(spec) variants_attr_holder = self._get_variants_attr_holder(spec) variants = self._get_variant_names_iterable(spec) - variants_type_expr = type_name(spec.type) - - if is_local_type_name(variants_type_expr): - variants_type_expr = clean_id(variants_type_expr) - spec.builder.ensure_object_imported(spec.type, variants_type_expr) + variants_type_expr = spec.builder.get_type_name_identifier(spec.type) if variants_attr not in variants_attr_holder.__dict__: setattr(variants_attr_holder, variants_attr, {}) @@ -583,11 +570,7 @@ def _unpack_annotated_serializable_type( ) unpacker = UnpackerRegistry.get(spec.copy(type=value_type)) - field_type = type_name(spec.type) - - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.type) return f"{field_type}._deserialize({unpacker})" @@ -602,10 +585,7 @@ def unpack_serializable_type(spec: ValueSpec) -> Optional[Expression]: if spec.origin_type.__use_annotations__: return _unpack_annotated_serializable_type(spec) else: - field_type = type_name(spec.type) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.type) return f"{field_type}._deserialize({spec.expression})" @@ -617,11 +597,9 @@ def unpack_generic_serializable_type(spec: ValueSpec) -> Optional[Expression]: type_arg_names = ", ".join( list(map(type_name, get_args(spec.type))) ) - field_type = type_name(spec.origin_type) - - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier( + spec.origin_type + ) return ( f"{field_type}._deserialize({spec.expression}, " @@ -1025,10 +1003,7 @@ def unpack_named_tuple(spec: ValueSpec) -> Expression: unpackers.append(unpacker) if not defaults: - field_type = type_name(spec.type) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.type) return f"{field_type}({', '.join(unpackers)})" @@ -1056,10 +1031,7 @@ def unpack_named_tuple(spec: ValueSpec) -> Expression: with lines.indent("except IndexError:"): lines.append("pass") - field_type = type_name(spec.type) - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.type) lines.append(f"return {field_type}(*fields)") lines.append( @@ -1240,11 +1212,7 @@ def unpack_pathlike(spec: ValueSpec) -> Optional[Expression]: spec.builder.ensure_module_imported(pathlib) return f"{type_name(pathlib.PurePath)}({spec.expression})" elif issubclass(spec.origin_type, os.PathLike): - field_type = type_name(spec.origin_type) - - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.origin_type) return f"{field_type}({spec.expression})" @@ -1252,10 +1220,6 @@ def unpack_pathlike(spec: ValueSpec) -> Optional[Expression]: @register def unpack_enum(spec: ValueSpec) -> Optional[Expression]: if issubclass(spec.origin_type, enum.Enum): - field_type = type_name(spec.origin_type) - - if is_local_type_name(field_type): - field_type = clean_id(field_type) - spec.builder.ensure_object_imported(spec.type, field_type) + field_type = spec.builder.get_type_name_identifier(spec.origin_type) return f"{field_type}({spec.expression})"