Repository: pydantic/pydantic
Pull request: Feature/dataclass schema defaults
## Change Summary
* Make use of default_factory lambda when creating pydantic fields
* Add separate test for this kind of default
* Add field for this kind of default in test_schema

## Related issue number
Fixes #968 
If fine and complete, lets discuss docs

## Checklist

* [X] Unit tests for the changes exist
* [X] Tests pass on CI and coverage remains
* [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:
dataclass field doesn't change required schema
# Bug

Please complete:
* OS: Mac OS
* Python version `import sys; print(sys.version)`: **3.6.7** with dataclasses backport
* Pydantic version `import pydantic; print(pydantic.VERSION)`: **1.0**

It is known that `@pydantic.dataclasses.dataclass` doesn't fully mimic the `BaseModel`. However, we'd like use `.schema()` representation faithfully. I'm wondering what's the best workaround.

Adding a `Config` for `b` with `required=False` doesn't carry over to `.schema()` either.

```py
from typing import Dict

from dataclasses import field
from pydantic import Field, ValidationError
from pydantic.dataclasses import dataclass

@dataclass
class D1:
    a: int
    # b: Dict[str, int] = {"x":10} # mutable default, not allowed
@dataclass
class D2:
    a: int
    b: Dict[str, int] = Field({"x":10}) # pydantic.Field, unsafe and invalid default
@dataclass        
class D3:
    a: int
    b: Dict[str, int] = field(default_factory=lambda: {"x":10}) # std lib, safe and invalid default
        
class C:
    fields = {"b": { "required": False}} # doesn't carry over!!

@dataclass(config=C)        
class D4:
    a: int
    b: Dict[str, int] = field(default_factory=lambda: {"x":10})

print(D1(10))
try:
    print(D2(10))
except ValidationError as e:
    print(e) # pydantic/dataclasses.py:77

print(D3(10))
req = D3.__pydantic_model__.schema()["required"]
if req != ["a"]:
    print('only ["a"] should be required, is:', req)
    
print()
print(D4.__pydantic_model__.schema()["required"]) # same as D3
```

Output
```
D1(a=10)
1 validation error for D2
b
  value is not a valid dict (type=type_error.dict)
D3(a=10, b={'x': 10})
only ["a"] should be required, is: ['a', 'b']

['a', 'b']
```

Hints from issue discussion:
This isn't a bug, but a feature request.

```py
class Config:
    fields = {"b": { "required": False}}
```

Wouldn't work on a normal model.

I think what you want is better support for the `field()` function from dataclasses. If so, please describe exactly what parts of `field()` usage you'd like to support in pydantic. Is it just the default in schema?
Thanks for clarifying!
Only handling `default*` in schema is what we need to support. However, it would be nice to have a stable interface for `.__pydantic_model__`. I'm not sure if this might change. 
`__pydantic_model__` won't change, we should document it.

It would be wonderful if you could create a PR for that. :smile: 
i'm going for it

Commit message: add docs for dataclass default factory and basemodel access

Patch to review:
diff --git a/changes/968-ahirner.md b/changes/968-ahirner.md
new file mode 100644
index 00000000000..921f08c0539
--- /dev/null
+++ b/changes/968-ahirner.md
@@ -0,0 +1 @@
+Add support for dataclasses default factory
diff --git a/docs/examples/dataclasses_default_schema.py b/docs/examples/dataclasses_default_schema.py
new file mode 100644
index 00000000000..e7ca1800f15
--- /dev/null
+++ b/docs/examples/dataclasses_default_schema.py
@@ -0,0 +1,12 @@
+import dataclasses
+from typing import List
+from pydantic.dataclasses import dataclass
+
+@dataclass
+class User:
+    id: int
+    name: str = 'John Doe'
+    friends: List[int] = dataclasses.field(default_factory=lambda: [0])
+
+user = User(id='42')
+print(user.__pydantic_model__.schema()['properties']['friends'])
diff --git a/docs/usage/dataclasses.md b/docs/usage/dataclasses.md
index 342bfa3caf4..3f3c183a6ca 100644
--- a/docs/usage/dataclasses.md
+++ b/docs/usage/dataclasses.md
@@ -19,6 +19,14 @@ _(This script is complete, it should run "as is")_
 You can use all the standard pydantic field types, and the resulting dataclass will be identical to the one
 created by the standard library `dataclass` decorator.
 
+The underlying model and its schema can be accessed through `__pydantic_model__`.
+Also, fields that require a `default_factory` can be specified by a `dataclasses.field`.
+
+```py
+{!.tmp_examples/dataclasses_default_schema.py!}
+```
+_(This script is complete, it should run "as is")_
+
 `pydantic.dataclasses.dataclass`'s arguments are the same as the standard decorator, except one extra
 keyword argument `config` which has the same meaning as [Config](model_config.md).
 
diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py
index 313603005af..41b1556e562 100644
--- a/pydantic/dataclasses.py
+++ b/pydantic/dataclasses.py
@@ -83,10 +83,18 @@ def _pydantic_post_init(self: 'DataclassType', *initvars: Any) -> None:
     _cls.__post_init__ = _pydantic_post_init
     cls = dataclasses._process_class(_cls, init, repr, eq, order, unsafe_hash, frozen)  # type: ignore
 
-    fields: Dict[str, Any] = {
-        field.name: (field.type, field.default if field.default != dataclasses.MISSING else Required)
-        for field in dataclasses.fields(cls)
-    }
+    fields: Dict[str, Any] = {}
+    for field in dataclasses.fields(cls):
+
+        if field.default != dataclasses.MISSING:
+            field_value = field.default
+        # mypy issue 7020 and 708
+        elif field.default_factory != dataclasses.MISSING:  # type: ignore
+            field_value = field.default_factory()  # type: ignore
+        else:
+            field_value = Required
+
+        fields[field.name] = (field.type, field_value)
 
     validators = gather_all_validators(cls)
     cls.__pydantic_model__ = create_model(
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index 2bc6401b125..a26f084df58 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -1,7 +1,7 @@
 import dataclasses
 from datetime import datetime
 from pathlib import Path
-from typing import ClassVar, FrozenSet, Optional
+from typing import ClassVar, Dict, FrozenSet, Optional
 
 import pytest
 
@@ -403,11 +403,28 @@ class User:
     assert fields['signup_ts'].default is None
 
 
+def test_default_factory_field():
+    @pydantic.dataclasses.dataclass
+    class User:
+        id: int
+        aliases: Dict[str, str] = dataclasses.field(default_factory=lambda: {'John': 'Joey'})
+
+    user = User(id=123)
+    fields = user.__pydantic_model__.__fields__
+
+    assert fields['id'].required is True
+    assert fields['id'].default is None
+
+    assert fields['aliases'].required is False
+    assert fields['aliases'].default == {'John': 'Joey'}
+
+
 def test_schema():
     @pydantic.dataclasses.dataclass
     class User:
         id: int
         name: str = 'John Doe'
+        aliases: Dict[str, str] = dataclasses.field(default_factory=lambda: {'John': 'Joey'})
         signup_ts: datetime = None
 
     user = User(id=123)
@@ -417,6 +434,12 @@ class User:
         'properties': {
             'id': {'title': 'Id', 'type': 'integer'},
             'name': {'title': 'Name', 'default': 'John Doe', 'type': 'string'},
+            'aliases': {
+                'title': 'Aliases',
+                'default': {'John': 'Joey'},
+                'type': 'object',
+                'additionalProperties': {'type': 'string'},
+            },
             'signup_ts': {'title': 'Signup Ts', 'type': 'string', 'format': 'date-time'},
         },
         'required': ['id'],


Code review comments:
diff_hunk: @@ -0,0 +1,12 @@
+import dataclasses
+from typing import List
+from pydantic.dataclasses import dataclass
+
+@dataclass
+class User:
+    id: int
+    name: str = 'John Doe'
+    friends: List[int] = dataclasses.field(default_factory=lambda: [0])
+
+user = User(id='42')
+print(user.__pydantic_model__.schema()['properties']['friends'])
path: docs/examples/dataclasses_default_schema.py
text: @user1:
```suggestion
print(user.__pydantic_model__.schema())
```
Is probably enough, best to keep documentation as simple as possible while demonstrating the point.

@author:
I had this at first then thought the prints use up too much space. So you prefer the complete schema for sure?

@user1:
yes please.