Repository: pydantic/pydantic
Pull request: Respect use_enum_values on Literal types
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->

## Change Summary
Following the logic for enum schema generation, I added `no_info_after_validator_function` to the literal schema when `use_enum_values` is set.

To be fair, I don't really have a clue what I'm doing, so I haven't fixed the test yet. Would like to know if this change makes sense and if I should continue this way. Thanks! 

<!-- Please give a short summary of the changes. -->

## Related issue number
fix #9738 
<!-- WARNING: please use "fix #123" style references so the issue is closed when this PR is merged. -->

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [x] Unit tests for the changes exist
* [x] Tests pass on CI
* [x] Documentation reflects the changes where applicable
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


Selected Reviewer: @Kludex

Problem statement:
validate_default has no effect when attribute type is Literal
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

When an field has an Enum value as its default value, then it is translated to a str when both `use_enum_values` and `validate_default` are set True, as is expected from the documentation.

However, when the type of the Field is a Literal of that enum, the validate_default doesn't seem to work. See code below.

The same happens when the type is `Literal[MyEnum.FOO, MyEnum.BAR]`

### Example Code

```Python
from enum import StrEnum, auto
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field


class MyEnum(StrEnum):
    FOO = auto()
    BAR = auto()


class MyModel(BaseModel):
    model_config = ConfigDict(use_enum_values=True)
    enum_field: MyEnum = Field(default=MyEnum.FOO, validate_default=True)


class MyModelLiteral(BaseModel):
    model_config = ConfigDict(use_enum_values=True)
    enum_field: Literal[MyEnum.FOO] = Field(default=MyEnum.FOO, validate_default=True)
                                                     # default="foo" gives same result


print(type(MyModel().enum_field))  # <class 'str'>
print(type(MyModelLiteral().enum_field))  # <enum 'MyEnum'>
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.7.4
        pydantic-core version: 2.18.4
          pydantic-core build: profile=release pgo=true
                 install path: /home/kwint/.cache/pypoetry/virtualenvs/material-library-8A638HCG-py3.11/lib/python3.11/site-packages/pydantic
               python version: 3.11.8 (main, Feb 25 2024, 16:39:33) [GCC 11.4.0]
                     platform: Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.35
             related packages: typing_extensions-4.12.2 mypy-1.10.0
                       commit: unknown
```

Hints from issue discussion:
@kwint,

Thanks for reporting this. Definitely looks like a bug. Going to mark this as a good first issue - should be fixable within `pydantic` alone.

If anyone is interested in taking a stab at this, feel free to ping me when you'd like a review or if you have questions!
Hi @sydney-runkle,   I'd be happy to tackle this issue! 
@akshatvishu,

Go for it!!
This is blocking me a bit so I gave it shot my self. Hope I didn't step on your toes @akshatvishu

Commit message: Added tests

Patch to review:
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
index b81de006d00..b519ffa9bec 100644
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -1248,7 +1248,14 @@ def _literal_schema(self, literal_type: Any) -> CoreSchema:
         """Generate schema for a Literal."""
         expected = _typing_extra.all_literal_values(literal_type)
         assert expected, f'literal "expected" cannot be empty, obj={literal_type}'
-        return core_schema.literal_schema(expected)
+        schema = core_schema.literal_schema(expected)
+
+        if self._config_wrapper.use_enum_values:
+            schema = core_schema.no_info_after_validator_function(
+                lambda v: v.value if isinstance(v, Enum) else v, schema
+            )
+
+        return schema
 
     def _typed_dict_schema(self, typed_dict_cls: Any, origin: Any) -> core_schema.CoreSchema:
         """Generate schema for a TypedDict.
diff --git a/tests/test_main.py b/tests/test_main.py
index e55a61f56cd..ba57728e5d0 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -747,11 +747,11 @@ def test_validating_assignment_fail(ValidateAssignmentModel):
 
 
 class Foo(Enum):
-    foo = 'foo'
-    bar = 'bar'
+    FOO = 'foo'
+    BAR = 'bar'
 
 
-@pytest.mark.parametrize('value', [Foo.foo, Foo.foo.value, 'foo'])
+@pytest.mark.parametrize('value', [Foo.FOO, Foo.FOO.value, 'foo'])
 def test_enum_values(value: Any) -> None:
     class Model(BaseModel):
         foo: Foo
@@ -768,33 +768,59 @@ class Model(BaseModel):
     assert foo == 'foo'
 
 
-def test_literal_enum_values():
-    FooEnum = Enum('FooEnum', {'foo': 'foo_value', 'bar': 'bar_value'})
+class StrFoo(str, Enum):
+    FOO = 'foo'
+    BAR = 'bar'
 
+
+def test_literal_use_enum_values():
     class Model(BaseModel):
-        baz: Literal[FooEnum.foo]
+        baz: Literal[StrFoo.FOO]
         boo: str = 'hoo'
         model_config = ConfigDict(use_enum_values=True)
 
-    m = Model(baz=FooEnum.foo)
-    assert m.model_dump() == {'baz': FooEnum.foo, 'boo': 'hoo'}
-    assert m.model_dump(mode='json') == {'baz': 'foo_value', 'boo': 'hoo'}
-    assert m.baz.value == 'foo_value'
+    m = Model(baz=StrFoo.FOO)
+    assert isinstance(m.baz, str)
+    assert m.model_dump() == {'baz': 'foo', 'boo': 'hoo'}
+    assert m.model_dump(mode='json') == {'baz': 'foo', 'boo': 'hoo'}
 
     with pytest.raises(ValidationError) as exc_info:
-        Model(baz=FooEnum.bar)
+        Model(baz=StrFoo.BAR)
 
     # insert_assert(exc_info.value.errors(include_url=False))
     assert exc_info.value.errors(include_url=False) == [
         {
             'type': 'literal_error',
             'loc': ('baz',),
-            'msg': "Input should be <FooEnum.foo: 'foo_value'>",
-            'input': FooEnum.bar,
-            'ctx': {'expected': "<FooEnum.foo: 'foo_value'>"},
+            'msg': "Input should be <StrFoo.FOO: 'foo'>",
+            'input': StrFoo.BAR,
+            'ctx': {'expected': "<StrFoo.FOO: 'foo'>"},
         }
     ]
 
+    validated = Model.model_validate_json('{"baz": "foo"}')
+    assert isinstance(validated.baz, str)
+
+    validated = Model.model_validate({'baz': StrFoo.FOO})
+    assert isinstance(validated.baz, str)
+
+
+@pytest.mark.parametrize('value', [StrFoo.FOO, StrFoo.FOO.value, 'foo', 'hello'])
+def test_literal_use_enum_values_multi_type(value):
+    class Model(BaseModel):
+        baz: Literal[StrFoo.FOO, 'hello']
+        model_config = ConfigDict(use_enum_values=True)
+
+    assert isinstance(Model(baz=value).baz, str)
+
+
+def test_literal_use_enum_values_with_default():
+    class Model(BaseModel):
+        baz: Literal[StrFoo.FOO] = Field(default=StrFoo.FOO)
+        model_config = ConfigDict(use_enum_values=True)
+
+    assert isinstance(Model().baz, str)
+
 
 def test_strict_enum_values():
     class MyEnum(Enum):


Code review comments:
diff_hunk: @@ -1248,7 +1248,14 @@ def _literal_schema(self, literal_type: Any) -> CoreSchema:
         """Generate schema for a Literal."""
         expected = _typing_extra.all_literal_values(literal_type)
         assert expected, f'literal "expected" cannot be empty, obj={literal_type}'
-        return core_schema.literal_schema(expected)
+        schema = core_schema.literal_schema(expected)
+
+        if self._config_wrapper.use_enum_values:
path: pydantic/_internal/_generate_schema.py
text: @user1:
Let's add one more check, where we only apply this change if there's one or more `Enum`s in `expected.

@author:
Done!