Repository: pydantic/pydantic

Issue:
Pydantic does not accept pre-compiled Pattern objects
# Bug

Pydantic does not accept pre-compiled regular expression `Pattern` objects as valid values for `Pattern`-typed fields. Linked to issue #1211 . I'll create a pull request.

By the way, I've just discovered Pydantic and it blows my mind. Incredible work, thank you!

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.4
            pydantic compiled: False
                 install path: C:\Users\agregory\.virtualenvs\Diasemi\Lib\site-packages\pydantic
               python version: 3.8.1 (tags/v3.8.1:1b293b6, Dec 18 2019, 23:11:46) [MSC v.1916 64 bit (AMD64)]
                     platform: Windows-10-10.0.17134-SP0
     optional deps. installed: []

```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your bug hasn't already been reported. -->

<!-- Where possible please include a self-contained code snippet describing your bug: -->

```py
import pydantic
import re
import typing

class Amodel(pydantic.BaseModel):
    name: typing.Pattern

    @pydantic.validator('name', pre=True)
    def compile_insensitive(cls, value: str) -> typing.Pattern:
        return re.compile(value, re.IGNORECASE)

x = Amodel(name='Something')
```



Discussion:

Resolving patch:
diff --git a/pydantic/validators.py b/pydantic/validators.py
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -14,7 +14,6 @@
     FrozenSet,
     Generator,
     List,
-    Optional,
     Pattern,
     Set,
     Tuple,
@@ -40,7 +39,7 @@
     StrBytes = Union[str, bytes]
 
 
-def str_validator(v: Any) -> Optional[str]:
+def str_validator(v: Any) -> Union[str]:
     if isinstance(v, str):
         if isinstance(v, Enum):
             return v.value
@@ -463,8 +462,13 @@ def any_class_validator(v: Any) -> Type[T]:
 
 
 def pattern_validator(v: Any) -> Pattern[str]:
+    if isinstance(v, Pattern):
+        return v
+
+    str_value = str_validator(v)
+
     try:
-        return re.compile(v)
+        return re.compile(str_value)
     except re.error:
         raise errors.PatternError()
 
@@ -478,7 +482,6 @@ def check(self, config: Type['BaseConfig']) -> bool:
         return any(getattr(config, name) not in {None, False} for name in self.config_attr_names)
 
 
-pattern_validators = [str_validator, pattern_validator]
 # order is important here, for example: bool is a subclass of int so has to come first, datetime before date same,
 # IPv4Interface before IPv4Address, etc
 _VALIDATORS: List[Tuple[AnyType, List[Any]]] = [
@@ -534,7 +537,7 @@ def find_validators(  # noqa: C901 (ignore complexity)
     if type_type == ForwardRef or type_type == TypeVar:
         return
     if type_ is Pattern:
-        yield from pattern_validators
+        yield pattern_validator
         return
     if is_callable_type(type_):
         yield callable_validator


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1,5 +1,6 @@
 import itertools
 import os
+import re
 import sys
 import uuid
 from collections import OrderedDict
@@ -1837,6 +1838,11 @@ class Foobar(BaseModel):
     assert f.pattern.match('whatever1')
     assert not f.pattern.match(' whatever1')
 
+    # Check that pre-compiled patterns are accepted unchanged
+    p = re.compile(r'^whatev.r\d$')
+    f2 = Foobar(pattern=p)
+    assert f2.pattern is p
+
 
 def test_pattern_error():
     class Foobar(BaseModel):