Repository: pydantic/pydantic

Issue:
UUID type `Field` doesn't support `strict` specification
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

This seems to be closely related to https://github.com/pydantic/pydantic/issues/7759 which is marked fixed, but if I installed the current `main` correctly it doesn't seem to be fixed there.

I would like to make a class that is, in general, strict, but where that strictness is relaxed for `UUID` fields so I can get conversions from strings rather than have to pre-convert those:

```python
class UnstrictExample(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(strict=True)
    id: uuid.UUID = pydantic.Field(strict=False)
    val: int
```

With Pydantic 2.1, this works:

```
>>> import pydantic
>>> import uuid
>>> class UnstrictExample(pydantic.BaseModel):
...     model_config = pydantic.ConfigDict(strict=True)
...     id: uuid.UUID = pydantic.Field(strict=False)
...     val: int
... 
>>> UnstrictExample(id="e9dbca76-6adb-4e1c-af87-ac7b9e24fded", val=123)
UnstrictExample(id=UUID('e9dbca76-6adb-4e1c-af87-ac7b9e24fded'), val=123)
>>> pydantic.version.VERSION
'2.1.1'
```

However, upgrade to 2.2, and it does not:

```
>>> class UnstrictExample(pydantic.BaseModel):
...     model_config = pydantic.ConfigDict(strict=True)
...     id: uuid.UUID = pydantic.Field(strict=False)
...     val: int
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 178, in __new__
    complete_model_class(
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 468, in complete_model_class
    schema = cls.__get_pydantic_core_schema__(cls, handler)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1644, in _apply_single_annotation
    maybe_updated_schema = _known_annotated_metadata.apply_known_metadata(metadata, schema.copy())
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_known_annotated_metadata.py", line 239, in apply_known_metadata
    raise RuntimeError(f'Unable to apply constraint {constraint} to schema {schema_type}')
RuntimeError: Unable to apply constraint strict to schema uuid
>>> pydantic.version.VERSION
'2.2.1'
```

and this remains true if I `pip install 'git+https://github.com/pydantic/pydantic@main#egg=pydantic'` (though it may be installing `pydantic-core` from somewhere else...? I'm not sure I'm checking current trunk correctly; sorry if this is fixed after all.)

### Example Code

```Python
import pydantic
import uuid
class UnstrictExample(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(strict=True)
    id: uuid.UUID = pydantic.Field(strict=False)
    val: int
```


### Python, Pydantic & OS Version

Running from `python:3.11-slim-bookworm` Docker container, but I get similar with 3.8 in a local venv. Here's the version information for 2.2:

```Text
             pydantic version: 2.2.1
        pydantic-core version: 2.6.1
          pydantic-core build: profile=release pgo=true
                 install path: /usr/local/lib/python3.11/site-packages/pydantic
               python version: 3.11.4 (main, Jul 28 2023, 05:12:06) [GCC 12.2.0]
                     platform: Linux-5.4.0-150-generic-x86_64-with-glibc2.36
     optional deps. installed: ['typing-extensions']
```
```



Discussion:
Actually... this is a somewhat different problem than I thought, and got a slightly more simple example. Turns out the class-level config doesn't matter, and even this is sufficient:

```python
>>> class UnstrictExample(pydantic.BaseModel):
...     id: uuid.UUID = pydantic.Field(strict=False)
...     val: int
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 178, in __new__
    complete_model_class(
...
  File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_known_annotated_metadata.py", line 239, in apply_known_metadata
    raise RuntimeError(f'Unable to apply constraint {constraint} to schema {schema_type}')
RuntimeError: Unable to apply constraint strict to schema uuid
```


@EvanED,

Thanks for reporting this. This indeed looks like a bug 🐛. I'll work to get a fix out for this soon 👍.
Appreciated; thanks a bundle!

Resolving patch:
diff --git a/pydantic/_internal/_known_annotated_metadata.py b/pydantic/_internal/_known_annotated_metadata.py
--- a/pydantic/_internal/_known_annotated_metadata.py
+++ b/pydantic/_internal/_known_annotated_metadata.py
@@ -33,11 +33,12 @@
 FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
 INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
 BOOL_CONSTRAINTS = STRICT
+UUID_CONSTRAINTS = STRICT
 
 DATE_TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
 TIMEDELTA_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
 TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
-LAX_OR_STRICT_CONSTRAINTS = {*STRICT}
+LAX_OR_STRICT_CONSTRAINTS = STRICT
 
 UNION_CONSTRAINTS = {'union_mode'}
 URL_CONSTRAINTS = {
@@ -86,6 +87,8 @@
     CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('url', 'multi-host-url'))
 for constraint in BOOL_CONSTRAINTS:
     CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('bool',))
+for constraint in UUID_CONSTRAINTS:
+    CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('uuid',))
 for constraint in LAX_OR_STRICT_CONSTRAINTS:
     CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('lax-or-strict',))
 
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -1071,7 +1071,15 @@ def __get_pydantic_json_schema__(
         return field_schema
 
     def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
-        return core_schema.uuid_schema(version=self.uuid_version)
+        if isinstance(self, source):
+            # used directly as a type
+            return core_schema.uuid_schema(version=self.uuid_version)
+        else:
+            # update existing schema with self.uuid_version
+            schema = handler(source)
+            _check_annotated_type(schema['type'], 'uuid', self.__class__.__name__)
+            schema['version'] = self.uuid_version  # type: ignore
+            return schema
 
     def __hash__(self) -> int:
         return hash(type(self.uuid_version))


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -2861,22 +2861,29 @@ class UUIDModel(BaseModel):
 
 
 def test_uuid_strict() -> None:
-    class UUIDModel(BaseModel):
+    class StrictByConfig(BaseModel):
         a: UUID1
         b: UUID3
         c: UUID4
         d: UUID5
+        e: uuid.UUID
 
         model_config = ConfigDict(strict=True)
 
+    class StrictByField(BaseModel):
+        a: UUID1 = Field(..., strict=True)
+        b: UUID3 = Field(..., strict=True)
+        c: UUID4 = Field(..., strict=True)
+        d: UUID5 = Field(..., strict=True)
+        e: uuid.UUID = Field(..., strict=True)
+
     a = uuid.UUID('7fb48116-ca6b-11ed-a439-3274d3adddac')  # uuid1
     b = uuid.UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')  # uuid3
     c = uuid.UUID('260d1600-3680-4f4f-a968-f6fa622ffd8d')  # uuid4
     d = uuid.UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')  # uuid5
+    e = uuid.UUID('7fb48116-ca6b-11ed-a439-3274d3adddac')  # any uuid
 
-    with pytest.raises(ValidationError) as exc_info:
-        UUIDModel(a=str(a), b=str(b), c=str(c), d=str(d))
-    assert exc_info.value.errors(include_url=False) == [
+    strict_errors = [
         {
             'type': 'is_instance_of',
             'loc': ('a',),
@@ -2905,13 +2912,26 @@ class UUIDModel(BaseModel):
             'input': '886313e1-3b8a-5372-9b90-0c9aee199e5d',
             'ctx': {'class': 'UUID'},
         },
+        {
+            'type': 'is_instance_of',
+            'loc': ('e',),
+            'msg': 'Input should be an instance of UUID',
+            'input': '7fb48116-ca6b-11ed-a439-3274d3adddac',
+            'ctx': {'class': 'UUID'},
+        },
     ]
 
-    m = UUIDModel(a=a, b=b, c=c, d=d)
-    assert isinstance(m.a, type(a)) and m.a == a
-    assert isinstance(m.b, type(b)) and m.b == b
-    assert isinstance(m.c, type(c)) and m.c == c
-    assert isinstance(m.d, type(d)) and m.d == d
+    for model in [StrictByConfig, StrictByField]:
+        with pytest.raises(ValidationError) as exc_info:
+            model(a=str(a), b=str(b), c=str(c), d=str(d), e=str(e))
+        assert exc_info.value.errors(include_url=False) == strict_errors
+
+        m = model(a=a, b=b, c=c, d=d, e=e)
+        assert isinstance(m.a, type(a)) and m.a == a
+        assert isinstance(m.b, type(b)) and m.b == b
+        assert isinstance(m.c, type(c)) and m.c == c
+        assert isinstance(m.d, type(d)) and m.d == d
+        assert isinstance(m.e, type(e)) and m.e == e
 
 
 @pytest.mark.parametrize(