Repository: pydantic/pydantic

Issue:
.dict(skip_defaults=True) skips fields populated by alias
<!-- Questions, Feature Requests, and Bug Reports are all welcome -->
<!-- delete as applicable: -->
# Bug

For bugs/questions:
* OS: Linux
* Python version `import sys; print(sys.version)`: `3.7.3 (default, Mar 26 2019, 21:43:19) [GCC 8.2.1 20181127]`

* Pydantic version `import pydantic; print(pydantic.VERSION)`: 0.25

When using `.dict(skip_defaults=True)` with an aliased field, if that field was populated by the alias (i.e. `Foo(alias_name=...)` instead of `Foo(field_name=...)`) then it is always skipped. Happens with `by_alias` set to `True` and `False` as well.

```py
from pydantic import BaseModel, Schema


class Foo(BaseModel):
    class Config:
        allow_population_by_alias = True
    
    a: int = Schema(1, alias="b")


# Correct
print(Foo())  # Foo a=1
print(Foo().dict(by_alias=False, skip_defaults=False))  # {'a': 1}
print(Foo().dict(by_alias=True, skip_defaults=False))  # {'b': 1}
print(Foo().dict(by_alias=False, skip_defaults=True))  # {}
print(Foo().dict(by_alias=True, skip_defaults=True))  # {}

# Correct
print(Foo(a=2))  # Foo a=2
print(Foo(a=2).dict(by_alias=False, skip_defaults=False))  # {'a': 2}
print(Foo(a=2).dict(by_alias=True, skip_defaults=False))  # {'b': 2}
print(Foo(a=2).dict(by_alias=False, skip_defaults=True))  # {'a': 2}
print(Foo(a=2).dict(by_alias=True, skip_defaults=True))  # {'b': 2}

# Correct
print(Foo(b=2))  # Foo a=2
print(Foo(b=2).dict(by_alias=False, skip_defaults=False))  # {'a': 2}
print(Foo(b=2).dict(by_alias=True, skip_defaults=False))  # {'b': 2}
# Incorrect
print(Foo(b=2).dict(by_alias=False, skip_defaults=True))  # {}
print(Foo(b=2).dict(by_alias=True, skip_defaults=True))  # {}
```



Discussion:
After looking into it a bit, it looks like `.dict()` get's the values of the set fields from `__fields_set__`. `__fields_set__` doesn't contain the name of the field if it was set by the alias. E.g:

```py
print(Foo().__values__)  # {'a': 1}
print(Foo().__fields_set__)  # set()
print(Foo(a=2).__values__)  # {'a': 2}
print(Foo(a=2).__fields_set__)  # {'a'}
print(Foo(b=2).__values__)  # {'a': 2}
print(Foo(b=2).__fields_set__)  # set()
```

Is the behaviour for `__fields_set__` wrong or should `.dict()` be using something else to get the fields?

Resolving patch:
diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py
--- a/pydantic/dataclasses.py
+++ b/pydantic/dataclasses.py
@@ -25,7 +25,7 @@ def __validate__(cls, v: Any) -> 'DataclassType':
 
 
 def _pydantic_post_init(self: 'DataclassType') -> None:
-    d = validate_model(self.__pydantic_model__, self.__dict__, cls=self.__class__)
+    d = validate_model(self.__pydantic_model__, self.__dict__, cls=self.__class__)[0]
     object.__setattr__(self, '__dict__', d)
     object.__setattr__(self, '__initialised__', True)
     if self.__post_init_original__:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -232,11 +232,8 @@ def __init__(self, **data: Any) -> None:
         if TYPE_CHECKING:  # pragma: no cover
             self.__values__: Dict[str, Any] = {}
             self.__fields_set__: 'SetStr' = set()
-        object.__setattr__(self, '__values__', self._process_values(data))
-        if self.__config__.extra is Extra.allow:
-            fields_set = set(data.keys())
-        else:
-            fields_set = data.keys() & self.__values__.keys()  # type: ignore
+        values, fields_set, _ = validate_model(self, data)
+        object.__setattr__(self, '__values__', values)
         object.__setattr__(self, '__fields_set__', fields_set)
 
     @no_type_check
@@ -433,10 +430,6 @@ def validate(cls: Type['Model'], value: Union['DictStrAny', 'Model']) -> 'Model'
             with change_exception(DictError, TypeError, ValueError):
                 return cls(**dict(value))  # type: ignore
 
-    def _process_values(self, input_data: Any) -> 'DictStrAny':
-        # (casting here is slow so use ignore)
-        return validate_model(self, input_data)  # type: ignore
-
     @classmethod
     def _get_value(cls, v: Any, by_alias: bool, skip_defaults: bool) -> Any:
         if isinstance(v, BaseModel):
@@ -576,13 +569,16 @@ def create_model(  # noqa: C901 (ignore complexity)
 
 def validate_model(  # noqa: C901 (ignore complexity)
     model: Union[BaseModel, Type[BaseModel]], input_data: 'DictStrAny', raise_exc: bool = True, cls: 'ModelOrDc' = None
-) -> Union['DictStrAny', Tuple['DictStrAny', Optional[ValidationError]]]:
+) -> Tuple['DictStrAny', 'SetStr', Optional[ValidationError]]:
     """
     validate data against a model.
     """
     values = {}
     errors = []
+    # input_data names, possibly alias
     names_used = set()
+    # field names, never aliases
+    fields_set = set()
     config = model.__config__
     check_extra = config.extra is not Extra.ignore
 
@@ -607,8 +603,10 @@ def validate_model(  # noqa: C901 (ignore complexity)
             if not model.__config__.validate_all and not field.validate_always:
                 values[name] = value
                 continue
-        elif check_extra:
-            names_used.add(field.name if using_name else field.alias)
+        else:
+            fields_set.add(name)
+            if check_extra:
+                names_used.add(field.name if using_name else field.alias)
 
         v_, errors_ = field.validate(value, values, loc=field.alias, cls=cls or model.__class__)  # type: ignore
         if isinstance(errors_, ErrorWrapper):
@@ -621,6 +619,7 @@ def validate_model(  # noqa: C901 (ignore complexity)
     if check_extra:
         extra = input_data.keys() - names_used
         if extra:
+            fields_set |= extra
             if config.extra is Extra.allow:
                 for f in extra:
                     values[f] = input_data[f]
@@ -629,8 +628,8 @@ def validate_model(  # noqa: C901 (ignore complexity)
                     errors.append(ErrorWrapper(ExtraError(), loc=f, config=config))
 
     if not raise_exc:
-        return values, ValidationError(errors) if errors else None
+        return values, fields_set, ValidationError(errors) if errors else None
 
     if errors:
         raise ValidationError(errors)
-    return values
+    return values, fields_set, None


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
@@ -601,9 +601,14 @@ class Model(BaseModel):
         foo: int
         bar: List[int]
 
-    assert validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}) == {'foo': 123, 'bar': [1, 2, 3]}
-    d, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}, False)
+    assert validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}) == (
+        {'foo': 123, 'bar': [1, 2, 3]},
+        {'foo', 'bar'},
+        None,
+    )
+    d, f, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}, False)
     assert d == {'foo': 123, 'bar': [1, 2, 3]}
+    assert f == {'foo', 'bar'}
     assert e is None
 
 
@@ -612,12 +617,14 @@ class Model(BaseModel):
         foo: int
         bar: List[int]
 
-    d, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 'x')}, False)
+    d, f, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 'x')}, False)
     assert d == {'foo': 123}
+    assert f == {'foo', 'bar'}
     assert e.errors() == [{'loc': ('bar', 2), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}]
 
-    d, e = validate_model(Model, {'bar': (1, 2, 3)}, False)
+    d, f, e = validate_model(Model, {'bar': (1, 2, 3)}, False)
     assert d == {'bar': [1, 2, 3]}
+    assert f == {'bar'}
     assert e.errors() == [{'loc': ('foo',), 'msg': 'field required', 'type': 'value_error.missing'}]
 
 
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -598,6 +598,34 @@ class ModelB(BaseModel):
     assert dict(m) == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}
 
 
+def test_dict_skip_defaults_populated_by_alias():
+    class MyModel(BaseModel):
+        a: str = Schema('default', alias='alias_a')
+        b: str = Schema('default', alias='alias_b')
+
+        class Config:
+            allow_population_by_alias = True
+
+    m = MyModel(alias_a='a')
+
+    assert m.dict(skip_defaults=True) == {'a': 'a'}
+    assert m.dict(skip_defaults=True, by_alias=True) == {'alias_a': 'a'}
+
+
+def test_dict_skip_defaults_populated_by_alias_with_extra():
+    class MyModel(BaseModel):
+        a: str = Schema('default', alias='alias_a')
+        b: str = Schema('default', alias='alias_b')
+
+        class Config:
+            extra = 'allow'
+
+    m = MyModel(alias_a='a', c='c')
+
+    assert m.dict(skip_defaults=True) == {'a': 'a', 'c': 'c'}
+    assert m.dict(skip_defaults=True, by_alias=True) == {'alias_a': 'a', 'c': 'c'}
+
+
 def test_dir_fields():
     class MyModel(BaseModel):
         attribute_a: int