Repository: pydantic/pydantic

Issue:
Check on duplicate validators breaks validators in jupyter notebooks
Check on duplicate validators breaks validators in jupyter notebooks if a cell is executed more than once.

```
from pydantic import BaseModel, validator
import yaml
from ipaddress import ip_address

class Config(BaseModel):
    ip: str
    name: str
    port: int
        
    @validator("ip")
    def check_ip(cls, value):
        return ip_address(value)            
            

with open("config") as f:
    d = yaml.safe_load(f)
    c = Config(**d)
    print(c)
```
Returns

```
Config ip=IPv4Address('123.0.0.200') name='asdf' port=2392
when the cell is called first and raises

ConfigError Traceback (most recent call last)
in ()
3 from ipaddress import ip_address
4
----> 5 class Config(BaseModel):
6 ip: str
7 name: str

in Config()
8 port: int
9
---> 10 @validator("ip")
11 def check_ip(cls, value):
12 return ip_address(value)

~/anaconda3/lib/python3.7/site-packages/pydantic/main.py in dec(f)
449 ref = f.module + '.' + f.qualname
450 if ref in _FUNCS:
--> 451 raise ConfigError(f'duplicate validator function "{ref}"')
452 _FUNCS.add(ref)
453 f_cls = classmethod(f)

ConfigError: duplicate validator function "main.Config.check_ip"
```

The second time the same cell is called.

I was somewhat surprised by this behaviour and when I checked the code, I didn't really get why this exception is raised since we're dealing with a set here, so behaviour shouldn't change.
I would suggest to replace this exception with a logger message, warning level, which won't mess with people's code and is well supported by jupyter.


Discussion:
Would be great if you could format code or output like this to make it more readable.

I see what you mean. What about an environment variable and/or some check if we're in the jupyter before doing the duplicate check?
I understand what you mean about warnings, but this catches a lot of potential problems for me alone, and probably for others too.

I'd rather keep it as an error but allow a workaround.
I am having this problem as well, and I do not really understand what kind of behavior this is supposed to prevent. Could you please explain and perhaps I can re-write my code better?
The test for this behavior demonstrates what it's there to avoid:

https://github.com/samuelcolvin/pydantic/blob/2512f25df7e423de2da87718427d4a0d8ee33855/tests/test_validators.py#L181-L191

If you setup multiple validators with the same name on a class you may get unexpected behaviour when the second validator overwrites the first. The error is there to avoid this behavour.

I often get this myself when I have two similar validators, and create the second by copy and pasting the first and forget to change the function name.

I'll try to fix for jupyter now.

Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -15,7 +15,7 @@
 from .parse import Protocol, load_file, load_str_bytes
 from .schema import model_schema
 from .types import StrBytes
-from .utils import truncate, validate_field_name
+from .utils import in_ipython, truncate, validate_field_name
 from .validators import dict_validator
 
 
@@ -470,10 +470,13 @@ def validator(*fields, pre: bool = False, whole: bool = False, always: bool = Fa
         )
 
     def dec(f):
-        ref = f.__module__ + '.' + f.__qualname__
-        if ref in _FUNCS:
-            raise ConfigError(f'duplicate validator function "{ref}"')
-        _FUNCS.add(ref)
+        # avoid validators with duplicated names since without this validators can be overwritten silently
+        # which generally isn't the intended behaviour, don't run in ipython - see #312
+        if not in_ipython():  # pragma: no branch
+            ref = f.__module__ + '.' + f.__qualname__
+            if ref in _FUNCS:
+                raise ConfigError(f'duplicate validator function "{ref}"')
+            _FUNCS.add(ref)
         f_cls = classmethod(f)
         f_cls.__validator_config = fields, Validator(f, pre, whole, always, check_fields)
         return f_cls
diff --git a/pydantic/utils.py b/pydantic/utils.py
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -205,3 +205,15 @@ def url_regex_generator(*, relative: bool, require_tld: bool) -> Pattern:
 
 def lenient_issubclass(cls, class_or_tuple):
     return isinstance(cls, type) and issubclass(cls, class_or_tuple)
+
+
+def in_ipython():
+    """
+    Check whether we're in an ipython environment, including jupyter notebooks.
+    """
+    try:
+        __IPYTHON__
+    except NameError:
+        return False
+    else:  # pragma: no cover
+        return True


Test patch: