Repository: pydantic/pydantic
Pull request: Fix NameEmail equality comparison
## Change Summary

Introducing an `__eq__` for `networks.NameEmail` as two similar instances were not being correctly evaluated as equal.

```python
import pydantic

# previously this would raise an AssertionError
assert pydantic.NameEmail("test", "test@example.com") == pydantic.NameEmail("test", "test@example.com")1
```

## Related issue number

closes #1514

## Checklist

* [x] Unit tests for the changes exist
* [x] Tests pass on CI and coverage remains at 100%
* [x] Documentation reflects the changes where applicable
* [x] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)


Problem statement:
Same NameEmail field instances are not equal
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.5.1
            pydantic compiled: True
                 install path: /Users/***/.local/share/virtualenvs/tempenv-42b2286175873/lib/python3.7/site-packages/pydantic
               python version: 3.7.4 (default, Jul 22 2019, 11:21:52)  [Clang 10.0.1 (clang-1001.0.46.4)]
                     platform: Darwin-18.7.0-x86_64-i386-64bit
     optional deps. installed: ['email-validator']
```

I'm assuming that equality should be enforced on field types such as `NameEmail`. At the moment it seems that instances that appear to be equal are generating different hashes. Unsure if this is desired, but I would argue it shouldn't be.

```py
import pydantic

# will raise an AssertionError
assert pydantic.NameEmail("test", "test@test.com") == pydantic.NameEmail("test", "test@test.com")
```

Hints from issue discussion:
Thanks for reporting.

Happy to accept a PR to add a proper `__eq__` method to `NameEmail`

Commit message: Adding change doc for NameEmail.__eq__

Signed-off-by: Stephen Bunn <stephen@bunn.io>

Patch to review:
diff --git a/changes/1514-stephen-bunn.md b/changes/1514-stephen-bunn.md
new file mode 100644
index 00000000000..47f99d1cf5b
--- /dev/null
+++ b/changes/1514-stephen-bunn.md
@@ -0,0 +1 @@
+Add `NameEmail.__eq__` so duplicate `NameEmail` instances are evaluated as equal.
\ No newline at end of file
diff --git a/pydantic/networks.py b/pydantic/networks.py
index 9f9aca253a1..d46eec97fea 100644
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -329,6 +329,12 @@ def __init__(self, name: str, email: str):
         self.name = name
         self.email = email
 
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, NameEmail):
+            return (self.name, self.email,) == (other.name, other.email,)
+        else:
+            return other == self
+
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
         field_schema.update(type='string', format='name-email')
diff --git a/tests/test_networks.py b/tests/test_networks.py
index 16245a9dd5e..ac947590237 100644
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -453,3 +453,5 @@ class Model(BaseModel):
 
     assert str(Model(v=NameEmail('foo bar', 'foobaR@example.com')).v) == 'foo bar <foobaR@example.com>'
     assert str(Model(v='foo bar  <foobaR@example.com>').v) == 'foo bar <foobaR@example.com>'
+    assert Model(v=NameEmail('foo bar', 'foobaR@example.com')) == Model(v=NameEmail('foo bar', 'foobaR@example.com'))
+    assert Model(v=NameEmail('foo bar', 'foobaR@example.com')) != 'foo bar <foobaR@example.com>'


Code review comments:
diff_hunk: @@ -329,6 +329,12 @@ def __init__(self, name: str, email: str):
         self.name = name
         self.email = email
 
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, NameEmail):
+            return (self.name, self.email,) == (other.name, other.email,)
+        else:
+            return other == self
path: pydantic/networks.py
text: @user1:
```suggestion
        return isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email)
```
?

@user2:
Or should this also compare to the string representation?
```py
return (isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email)) or str(self) == other
```
?

@user1:
surely too much magic?

@author:
Yeah, not a fan of that magic. Changes applied.

diff_hunk: @@ -453,3 +453,5 @@ class Model(BaseModel):
 
     assert str(Model(v=NameEmail('foo bar', 'foobaR@example.com')).v) == 'foo bar <foobaR@example.com>'
     assert str(Model(v='foo bar  <foobaR@example.com>').v) == 'foo bar <foobaR@example.com>'
+    assert Model(v=NameEmail('foo bar', 'foobaR@example.com')) == Model(v=NameEmail('foo bar', 'foobaR@example.com'))
+    assert Model(v=NameEmail('foo bar', 'foobaR@example.com')) != 'foo bar <foobaR@example.com>'
path: tests/test_networks.py
text: @user2:
```suggestion
        assert NameEmail('foo bar', 'foobaR@user1.com') == NameEmail('foo bar', 'foobaR@user1.com')
        assert NameEmail('foo bar', 'foobaR@user1.com') != NameEmail('foo bar', 'different@user1.com')
```
Your first assertion was more complicated than necessary, the second I'm afraid didn't make any sense since you're comparing a model to a string.

@author:
Originally wanted a test case to hit that alternate branch, but with the above suggestions not really necessary. Changes applied