Repository: pydantic/pydantic
Pull request: Fix `strict` application to `function-after` with `use_enum_values`
Fix https://github.com/pydantic/pydantic/issues/9257

Selected Reviewer: @samuelcolvin

Problem statement:
Error: RuntimeError: Unable to apply constraint strict to schema function-after
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

The following code, error reported after execution：

`
Traceback (most recent call last):
  File "E:\store\Testing\testing\test8.py", line 10, in <module>
    class Foo(BaseModel):
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_model_construction.py", line 202, in __new__
    complete_model_class(
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_model_construction.py", line 539, in complete_model_class
    schema = cls.__get_pydantic_core_schema__(cls, handler)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\main.py", line 607, in __get_pydantic_core_schema__
    return handler(source)
           ^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_schema_generation_shared.py", line 82, in __call__
    schema = self._handler(source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 499, in generate_schema
    schema = self._generate_schema_inner(obj)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 750, in _generate_schema_inner
    return self._model_schema(obj)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 577, in _model_schema
    {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 913, in _generate_md_field_schema
    common_field = self._common_field_schema(name, field_info, decorators)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1078, in _common_field_schema
    schema = self._apply_annotations(
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1817, in _apply_annotations
    schema = get_inner_schema(source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_schema_generation_shared.py", line 82, in __call__
    schema = self._handler(source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1900, in new_handler
    schema = self._apply_single_annotation(schema, annotation)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1859, in _apply_single_annotation
    maybe_updated_schema = _known_annotated_metadata.apply_known_metadata(metadata, schema.copy())
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic\_internal\_known_annotated_metadata.py", line 298, in apply_known_metadata
    raise RuntimeError(f'Unable to apply constraint {constraint} to schema {schema_type}')
RuntimeError: Unable to apply constraint strict to schema function-after
`

### Example Code

```Python
from enum import Enum
from typing import Annotated
from pydantic import ConfigDict, Strict, BaseModel


class SomeEnum(int, Enum):
    SOME_KEY = 1


class Foo(BaseModel):
    model_config = ConfigDict(strict=True, use_enum_values=True)
    foo: Annotated[SomeEnum, Strict(strict=False)]


f = Foo(foo=SomeEnum.SOME_KEY)
print(f.foo)
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.7.0
        pydantic-core version: 2.18.1
          pydantic-core build: profile=release pgo=true
                 install path: C:\Users\16699\.virtualenvs\Testing-fdkT3PoQ\Lib\site-packages\pydantic
               python version: 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]
                     platform: Windows-11-10.0.22631-SP0
             related packages: fastapi-0.110.1 typing_extensions-4.11.0
                       commit: unknown
```

Hints from issue discussion:
@Ryaningli,

Ah, thanks for the report. Definitely looks like a bug, likely related to the migration of our enum validation to `rust`. Will look into a fix for this for a patch release!
Looks like it's only a problem when we have `use_enum_values` in the config.

Commit message: return instead of continue

Patch to review:
diff --git a/pydantic/_internal/_known_annotated_metadata.py b/pydantic/_internal/_known_annotated_metadata.py
index ee7f7b248b2..4897183fd2c 100644
--- a/pydantic/_internal/_known_annotated_metadata.py
+++ b/pydantic/_internal/_known_annotated_metadata.py
@@ -195,6 +195,12 @@ def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | No
             raise ValueError(f'Unknown constraint {constraint}')
         allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint]
 
+        # if it becomes necessary to handle more than one constraint
+        # in this recursive case with function-after or function-wrap, we should refactor
+        if schema_type in ['function-before', 'function-wrap', 'function-after'] and constraint == 'strict':
+            schema['schema'] = apply_known_metadata(annotation, schema['schema'])  # type: ignore  # schema is function-after schema
+            return schema
+
         if schema_type in allowed_schemas:
             if constraint == 'union_mode' and schema_type == 'union':
                 schema['mode'] = value  # type: ignore  # schema is UnionSchema
diff --git a/tests/test_types.py b/tests/test_types.py
index da1903b575e..8bfadd19c6b 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -6658,3 +6658,19 @@ def test_can_serialize_deque_passed_to_sequence() -> None:
 
     assert ta.dump_python(my_dec) == my_dec
     assert ta.dump_json(my_dec) == b'[1,2,3]'
+
+
+def test_strict_enum_with_use_enum_values() -> None:
+    class SomeEnum(int, Enum):
+        SOME_KEY = 1
+
+    class Foo(BaseModel):
+        model_config = ConfigDict(strict=False, use_enum_values=True)
+        foo: Annotated[SomeEnum, Strict(strict=True)]
+
+    f = Foo(foo=SomeEnum.SOME_KEY)
+    assert f.foo == 1
+
+    # validation error raised bc foo field uses strict mode
+    with pytest.raises(ValidationError):
+        Foo(foo='1')


Code review comments:
diff_hunk: @@ -195,6 +195,12 @@ def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | No
             raise ValueError(f'Unknown constraint {constraint}')
         allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint]
 
+        # if it becomes necessary to handle more than one constraint
+        # in this recursive case with function-after or function-wrap, we should refactor
+        if schema_type in ['function-before', 'function-wrap', 'function-after'] and constraint == 'strict':
path: pydantic/_internal/_known_annotated_metadata.py
text: @user1:
Better to use a set here, and possibly store it out of line. I’d be okay with just making it a set though