Repository: pydantic/pydantic

Issue:
Bug: ignore_extra=False and allow_population_by_alias=True do not work together
<!-- Questions, Feature Requests, and Bug Reports are all welcome -->
<!-- please prefix issue titles with "Question: ", "Feature Request: ", or "Bug: " -->

* OS: **macOS**
* Python version `import sys; print(sys.version)`: **3.6.6**
* Pydantic version `import pydantic; print(pydantic.VERSION)`: **0.13**

Code snippet:
```py
from pydantic import BaseModel, Schema
from typing import Optional

class Model(BaseModel):
    last_updated_by: Optional[str] = Schema(None, alias="lastUpdatedBy")

    class Config:
        ignore_extra = False
        allow_population_by_alias = True

m1 = Model(lastUpdatedBy="dmitry") # ok
m2 = Model(last_updated_by="dmitry") # fails

# >
"""
ValidationError: 1 validation error
last_updated_by
  extra fields not permitted (type=value_error.extra)
"""
```
Additional info: read docs, including the warning, but it does not seem like this is an expected behavior.


Discussion:
This is exactly what you would expect unless I'm missing something.
Oh, I missed `allow_population_by_alias`. 

I guess this is wrong but I think that setting is dumb anyway, maybe we shouldn't have included it. 

Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -404,11 +404,15 @@ def validate_model(model, input_data: dict, raise_exc=True):  # noqa: C901 (igno
     """
     values = {}
     errors = []
+    names_used = set()
+    check_extra = (not model.__config__.ignore_extra) or model.__config__.allow_extra
 
     for name, field in model.__fields__.items():
         value = input_data.get(field.alias, _missing)
+        using_name = False
         if value is _missing and model.__config__.allow_population_by_alias and field.alt_alias:
             value = input_data.get(field.name, _missing)
+            using_name = True
 
         if value is _missing:
             if model.__config__.validate_all or field.validate_always:
@@ -419,6 +423,8 @@ def validate_model(model, input_data: dict, raise_exc=True):  # noqa: C901 (igno
                 else:
                     values[name] = deepcopy(field.default)
                 continue
+        elif check_extra:
+            names_used.add(field.name if using_name else field.alias)
 
         v_, errors_ = field.validate(value, values, loc=field.alias, cls=model.__class__)
         if isinstance(errors_, ErrorWrapper):
@@ -428,8 +434,8 @@ def validate_model(model, input_data: dict, raise_exc=True):  # noqa: C901 (igno
         else:
             values[name] = v_
 
-    if (not model.__config__.ignore_extra) or model.__config__.allow_extra:
-        extra = input_data.keys() - {f.alias for f in model.__fields__.values()}
+    if check_extra:
+        extra = input_data.keys() - names_used
         if extra:
             if model.__config__.allow_extra:
                 for field in extra:


Test patch:
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -607,3 +607,21 @@ class Model(BaseModel):
     ]
     assert Model().a is None
     assert Model(a=None).a is None
+
+
+def test_pop_by_alias():
+    class Model(BaseModel):
+        last_updated_by: Optional[str] = None
+
+        class Config:
+            ignore_extra = False
+            allow_population_by_alias = True
+            fields = {'last_updated_by': 'lastUpdatedBy'}
+
+    assert Model(lastUpdatedBy='foo').dict() == {'last_updated_by': 'foo'}
+    assert Model(last_updated_by='foo').dict() == {'last_updated_by': 'foo'}
+    with pytest.raises(ValidationError) as exc_info:
+        Model(lastUpdatedBy='foo', last_updated_by='bar')
+    assert exc_info.value.errors() == [
+        {'loc': ('last_updated_by',), 'msg': 'extra fields not permitted', 'type': 'value_error.extra'}
+    ]