Repository: samuelcolvin/pydantic
Pull request #2046 (2020-10-27 03:01:10)

Prevent fields from being deleted from BaseModel.__dict__ when a field validator raises an unexpected exception
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->
<!-- See https://pydantic-docs.helpmanual.io/contributing/ for help on Contributing -->

## Change Summary

Addressing https://github.com/samuelcolvin/pydantic/issues/2044

<!-- Please give a short summary of the changes. -->

## Related issue number

fix https://github.com/samuelcolvin/pydantic/issues/2044

<!-- Are there any issues opened that will be resolved by merging this change? -->

## 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)


codecov[bot]: # [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=h1) Report
> Merging [#2046](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=desc) into [master](https://codecov.io/gh/samuelcolvin/pydantic/commit/95435de4526b609ee904b7a27478cec2e32f8fe8?el=desc) will **decrease** coverage by `3.07%`.
> The diff coverage is `n/a`.


```diff
@@             Coverage Diff             @@
##            master    #2046      +/-   ##
===========================================
- Coverage   100.00%   96.92%   -3.08%     
===========================================
  Files           21       21              
  Lines         4094     4094              
  Branches       821      821              
===========================================
- Hits          4094     3968     -126     
- Misses           0      112     +112     
- Partials         0       14      +14     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/generics.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvZ2VuZXJpY3MucHk=) | `41.30% <0.00%> (-58.70%)` | :arrow_down: |
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `73.98% <0.00%> (-26.02%)` | :arrow_down: |
| [pydantic/decorator.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvZGVjb3JhdG9yLnB5) | `87.42% <0.00%> (-12.58%)` | :arrow_down: |
| [pydantic/version.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvdmVyc2lvbi5weQ==) | `88.23% <0.00%> (-11.77%)` | :arrow_down: |
| [pydantic/fields.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvZmllbGRzLnB5) | `97.89% <0.00%> (-2.11%)` | :arrow_down: |
| [pydantic/env\_settings.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvZW52X3NldHRpbmdzLnB5) | `97.93% <0.00%> (-2.07%)` | :arrow_down: |
| [pydantic/error\_wrappers.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvZXJyb3Jfd3JhcHBlcnMucHk=) | `98.86% <0.00%> (-1.14%)` | :arrow_down: |
| [pydantic/networks.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvbmV0d29ya3MucHk=) | `99.22% <0.00%> (-0.78%)` | :arrow_down: |
| [pydantic/utils.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvdXRpbHMucHk=) | `99.29% <0.00%> (-0.71%)` | :arrow_down: |
| [pydantic/types.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwZXMucHk=) | `99.58% <0.00%> (-0.42%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=footer). Last update [95435de...cc8ee19](https://codecov.io/gh/samuelcolvin/pydantic/pull/2046?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

johnsabath: I could add some additional unit tests for this edge case as well, just let me know how you'd like to handle it.
samuelcolvin: what would be wrong with

```py
                try:
                    value, error_ = known_field.validate(value, self.__dict__, loc=name, cls=self.__class__)
                    if error_:
                        raise ValidationError([error_], self.__class__)
                except Exception:
                    self.__dict__[name] = original_value
                    raise
                else:
                    new_values[name] = value
```
?
johnsabath: With @PrettyWood's additional context on why the field should still be removed from __dict__ before calling the validators, that seems like a good compromise to me @samuelcolvin 

Patch:
diff --git a/Makefile b/Makefile
index 8aa686f4b..cc5d66bcb 100644
--- a/Makefile
+++ b/Makefile
@@ -134,5 +134,5 @@ docs-serve:
 .PHONY: publish-docs
 publish-docs:
 	zip -r site.zip site
-	@curl -f -H "Content-Type: application/zip" -H "Authorization: Bearer ${NETLIFY}" \
+	@curl -H "Content-Type: application/zip" -H "Authorization: Bearer ${NETLIFY}" \
 	      --data-binary "@site.zip" https://api.netlify.com/api/v1/sites/pydantic-docs.netlify.com/deploys
diff --git a/changes/2042-PrettyWood.md b/changes/2042-PrettyWood.md
new file mode 100644
index 000000000..27a874c39
--- /dev/null
+++ b/changes/2042-PrettyWood.md
@@ -0,0 +1,2 @@
+fix: _pydantic_ `dataclass` can inherit from stdlib `dataclass`
+and `Config.arbitrary_types_allowed` is supported
\ No newline at end of file
diff --git a/changes/2044-johnsabath.md b/changes/2044-johnsabath.md
new file mode 100644
index 000000000..8072cb908
--- /dev/null
+++ b/changes/2044-johnsabath.md
@@ -0,0 +1,1 @@
+Fixed regression introduced in v1.7 involving exception handling in field validators when `validate_assignment=True`
\ No newline at end of file
diff --git a/changes/2047-samuelcolvin.md b/changes/2047-samuelcolvin.md
new file mode 100644
index 000000000..6c4c31cf3
--- /dev/null
+++ b/changes/2047-samuelcolvin.md
@@ -0,0 +1,1 @@
+fix `underscore_attrs_are_private` causing `TypeError` when overriding `__init__`
diff --git a/changes/2048-aphedges.md b/changes/2048-aphedges.md
new file mode 100644
index 000000000..0407f2367
--- /dev/null
+++ b/changes/2048-aphedges.md
@@ -0,0 +1,1 @@
+Fix mypy assignment error when using `PrivateAttr`
diff --git a/changes/2055-layday.md b/changes/2055-layday.md
new file mode 100644
index 000000000..047960511
--- /dev/null
+++ b/changes/2055-layday.md
@@ -0,0 +1,1 @@
+fix annotation of `validate_arguments` when passing configuration as argument
diff --git a/docs/datamodel_code_generator.md b/docs/datamodel_code_generator.md
index d96a11c28..dec9cbcb5 100644
--- a/docs/datamodel_code_generator.md
+++ b/docs/datamodel_code_generator.md
@@ -1,18 +1,18 @@
-[datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator/) is a command to generate pydantic models from other data types.
+The [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator/) project is a library and command-line utility to generate pydantic models from just about any data source, including:
 
+* OpenAPI 3 (YAML/JSON)
+* JSON Schema
+* JSON/YAML Data (which will converted to JSON Schema)
 
-* Supported source types
-    * OpenAPI 3 (YAML/JSON)
-    * JSON Schema
-    * JSON/YAML Data (It will be converted to JSON Schema)
+Whenever you find yourself with any data convertible JSON but without pydantic models, this tool will allow you to generate type-safe model hierarchies on demand.
 
-## Install
+## Installation
 ```bash
 pip install datamodel-code-generator
 ```
 
 ## Example
-In this case, The datamodel-code-generator creates pydantic models from JSON Schema.
+In this case, datamodel-code-generator creates pydantic models from a JSON Schema file.
 ```bash
 datamodel-codegen  --input person.json --input-file-type jsonschema --output model.py
 ```
diff --git a/docs/examples/dataclasses_arbitrary_types_allowed.py b/docs/examples/dataclasses_arbitrary_types_allowed.py
new file mode 100644
index 000000000..8a4f5597a
--- /dev/null
+++ b/docs/examples/dataclasses_arbitrary_types_allowed.py
@@ -0,0 +1,42 @@
+import dataclasses
+
+import pydantic
+
+
+class ArbitraryType:
+    def __init__(self, value):
+        self.value = value
+
+    def __repr__(self):
+        return f'ArbitraryType(value={self.value!r})'
+
+
+@dataclasses.dataclass
+class DC:
+    a: ArbitraryType
+    b: str
+
+
+# valid as it is a builtin dataclass without validation
+my_dc = DC(a=ArbitraryType(value=3), b='qwe')
+
+try:
+    class Model(pydantic.BaseModel):
+        dc: DC
+        other: str
+
+    Model(dc=my_dc, other='other')
+except RuntimeError as e:  # invalid as it is now a pydantic dataclass
+    print(e)
+
+
+class Model(pydantic.BaseModel):
+    dc: DC
+    other: str
+
+    class Config:
+        arbitrary_types_allowed = True
+
+
+m = Model(dc=my_dc, other='other')
+print(repr(m))
diff --git a/docs/examples/dataclasses_stdlib_inheritance.py b/docs/examples/dataclasses_stdlib_inheritance.py
new file mode 100644
index 000000000..0409b4696
--- /dev/null
+++ b/docs/examples/dataclasses_stdlib_inheritance.py
@@ -0,0 +1,27 @@
+import dataclasses
+
+import pydantic
+
+
+@dataclasses.dataclass
+class Z:
+    z: int
+
+
+@dataclasses.dataclass
+class Y(Z):
+    y: int = 0
+
+
+@pydantic.dataclasses.dataclass
+class X(Y):
+    x: int = 0
+
+
+foo = X(x=b'1', y='2', z='3')
+print(foo)
+
+try:
+    X(z='pika')
+except pydantic.ValidationError as e:
+    print(e)
diff --git a/docs/usage/dataclasses.md b/docs/usage/dataclasses.md
index 5f56b5882..54dbcbbf3 100644
--- a/docs/usage/dataclasses.md
+++ b/docs/usage/dataclasses.md
@@ -49,6 +49,8 @@ Dataclasses attributes can be populated by tuples, dictionaries or instances of
 
 ## Stdlib dataclasses and _pydantic_ dataclasses
 
+### Convert stdlib dataclasses into _pydantic_ dataclasses
+
 Stdlib dataclasses (nested or not) can be easily converted into _pydantic_ dataclasses by just decorating
 them with `pydantic.dataclasses.dataclass`.
 
@@ -57,6 +59,18 @@ them with `pydantic.dataclasses.dataclass`.
 ```
 _(This script is complete, it should run "as is")_
 
+### Inherit from stdlib dataclasses
+
+Stdlib dataclasses (nested or not) can also be inherited and _pydantic_ will automatically validate
+all the inherited fields.
+
+```py
+{!.tmp_examples/dataclasses_stdlib_inheritance.py!}
+```
+_(This script is complete, it should run "as is")_
+
+### Use of stdlib dataclasses with `BaseModel`
+
 Bear in mind that stdlib dataclasses (nested or not) are **automatically converted** into _pydantic_ dataclasses
 when mixed with `BaseModel`!
 
@@ -65,6 +79,17 @@ when mixed with `BaseModel`!
 ```
 _(This script is complete, it should run "as is")_
 
+### Use custom types
+
+Since stdlib dataclasses are automatically converted to add validation using
+custom types may cause some unexpected behaviour.
+In this case you can simply add `arbitrary_types_allowed` in the config!
+
+```py
+{!.tmp_examples/dataclasses_arbitrary_types_allowed.py!}
+```
+_(This script is complete, it should run "as is")_
+
 ## Initialize hooks
 
 When you initialize a dataclass, it is possible to execute code *after* validation
diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py
index 4fbdd63fc..28e2f9509 100644
--- a/pydantic/dataclasses.py
+++ b/pydantic/dataclasses.py
@@ -8,7 +8,7 @@ from .main import create_model, validate_model
 from .utils import ClassAttribute
 
 if TYPE_CHECKING:
-    from .main import BaseModel  # noqa: F401
+    from .main import BaseConfig, BaseModel  # noqa: F401
     from .typing import CallableGenerator
 
     DataclassT = TypeVar('DataclassT', bound='Dataclass')
@@ -120,7 +120,9 @@ def _process_class(
     # ```
     # with the exact same fields as the base dataclass
     if is_builtin_dataclass(_cls):
-        _cls = type(_cls.__name__, (_cls,), {'__post_init__': _pydantic_post_init})
+        _cls = type(
+            _cls.__name__, (_cls,), {'__annotations__': _cls.__annotations__, '__post_init__': _pydantic_post_init}
+        )
     else:
         _cls.__post_init__ = _pydantic_post_init
     cls: Type['Dataclass'] = dataclasses.dataclass(  # type: ignore
@@ -214,10 +216,10 @@ def dataclass(
     return wrap(_cls)
 
 
-def make_dataclass_validator(_cls: Type[Any], **kwargs: Any) -> 'CallableGenerator':
+def make_dataclass_validator(_cls: Type[Any], config: Type['BaseConfig']) -> 'CallableGenerator':
     """
     Create a pydantic.dataclass from a builtin dataclass to add type validation
     and yield the validators
     """
-    cls = dataclass(_cls, **kwargs)
+    cls = dataclass(_cls, config=config)
     yield from _get_validators(cls)
diff --git a/pydantic/decorator.py b/pydantic/decorator.py
index 589baaaca..0d7ca8822 100644
--- a/pydantic/decorator.py
+++ b/pydantic/decorator.py
@@ -1,5 +1,19 @@
 from functools import wraps
-from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Tuple, Type, TypeVar, Union, cast, get_type_hints
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+    get_type_hints,
+    overload,
+)
 
 from . import validator
 from .errors import ConfigError
@@ -11,16 +25,26 @@ __all__ = ('validate_arguments',)
 if TYPE_CHECKING:
     from .typing import AnyCallable
 
-    Callable = TypeVar('Callable', bound=AnyCallable)
+    AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable)
     ConfigType = Union[None, Type[Any], Dict[str, Any]]
 
 
-def validate_arguments(func: 'Callable' = None, *, config: 'ConfigType' = None) -> 'Callable':
+@overload
+def validate_arguments(func: None = None, *, config: 'ConfigType' = None) -> Callable[['AnyCallableT'], 'AnyCallableT']:
+    ...
+
+
+@overload
+def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT':
+    ...
+
+
+def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'ConfigType' = None) -> Any:
     """
     Decorator to validate the arguments passed to a function.
     """
 
-    def validate(_func: 'Callable') -> 'Callable':
+    def validate(_func: 'AnyCallable') -> 'AnyCallable':
         vd = ValidatedFunction(_func, config)
 
         @wraps(_func)
@@ -30,12 +54,12 @@ def validate_arguments(func: 'Callable' = None, *, config: 'ConfigType' = None)
         wrapper_function.vd = vd  # type: ignore
         wrapper_function.raw_function = vd.raw_function  # type: ignore
         wrapper_function.model = vd.model  # type: ignore
-        return cast('Callable', wrapper_function)
+        return wrapper_function
 
     if func:
         return validate(func)
     else:
-        return cast('Callable', validate)
+        return validate
 
 
 ALT_V_ARGS = 'v__args'
@@ -44,7 +68,7 @@ V_POSITIONAL_ONLY_NAME = 'v__positional_only'
 
 
 class ValidatedFunction:
-    def __init__(self, function: 'Callable', config: 'ConfigType'):  # noqa C901
+    def __init__(self, function: 'AnyCallableT', config: 'ConfigType'):  # noqa C901
         from inspect import Parameter, signature
 
         parameters: Mapping[str, Parameter] = signature(function).parameters
diff --git a/pydantic/fields.py b/pydantic/fields.py
index c451770c0..36ea690a2 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -799,21 +799,10 @@ class ModelField(Representation):
         return args
 
 
-class PrivateAttr(Representation):
-    """
-    Indicates that attribute is only used internally and never mixed with regular fields.
-
-    Types or values of private attrs are not checked by pydantic and it's up to you to keep them relevant.
-
-    Private attrs are stored in model __slots__.
-    """
-
+class ModelPrivateAttr(Representation):
     __slots__ = ('default', 'default_factory')
 
     def __init__(self, default: Any = Undefined, *, default_factory: Optional[NoArgAnyCallable] = None) -> None:
-        if default is not Undefined and default_factory is not None:
-            raise TypeError('default and default_factory args can not be used together')
-
         self.default = default
         self.default_factory = default_factory
 
@@ -825,3 +814,28 @@ class PrivateAttr(Representation):
             other.default,
             other.default_factory,
         )
+
+
+def PrivateAttr(
+    default: Any = Undefined,
+    *,
+    default_factory: Optional[NoArgAnyCallable] = None,
+) -> Any:
+    """
+    Indicates that attribute is only used internally and never mixed with regular fields.
+
+    Types or values of private attrs are not checked by pydantic and it's up to you to keep them relevant.
+
+    Private attrs are stored in model __slots__.
+
+    :param default: the attribute’s default value
+    :param default_factory: callable that will be called when a default value is needed for this attribute
+      If both `default` and `default_factory` are set, an error is raised.
+    """
+    if default is not Undefined and default_factory is not None:
+        raise ValueError('cannot specify both default and default_factory')
+
+    return ModelPrivateAttr(
+        default,
+        default_factory=default_factory,
+    )
diff --git a/pydantic/main.py b/pydantic/main.py
index bc5444a70..22a18dd10 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -29,7 +29,7 @@ from typing import (
 from .class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators
 from .error_wrappers import ErrorWrapper, ValidationError
 from .errors import ConfigError, DictError, ExtraError, MissingError
-from .fields import SHAPE_MAPPING, ModelField, PrivateAttr, Undefined
+from .fields import SHAPE_MAPPING, ModelField, ModelPrivateAttr, PrivateAttr, Undefined
 from .json import custom_pydantic_encoder, pydantic_encoder
 from .parse import Protocol, load_file, load_str_bytes
 from .schema import default_ref_template, model_schema
@@ -215,7 +215,7 @@ class ModelMetaclass(ABCMeta):
         validators: 'ValidatorListDict' = {}
 
         pre_root_validators, post_root_validators = [], []
-        private_attributes: Dict[str, PrivateAttr] = {}
+        private_attributes: Dict[str, ModelPrivateAttr] = {}
         slots: Set[str] = namespace.get('__slots__', ())
         slots = {slots} if isinstance(slots, str) else set(slots)
 
@@ -271,7 +271,7 @@ class ModelMetaclass(ABCMeta):
 
             for var_name, value in namespace.items():
                 can_be_changed = var_name not in class_vars and not isinstance(value, untouched_types)
-                if isinstance(value, PrivateAttr):
+                if isinstance(value, ModelPrivateAttr):
                     if not is_valid_private_name(var_name):
                         raise NameError(
                             f'Private attributes "{var_name}" must not be a valid field name; '
@@ -385,11 +385,15 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
             known_field = self.__fields__.get(name, None)
             if known_field:
                 original_value = self.__dict__.pop(name)
-                value, error_ = known_field.validate(value, self.__dict__, loc=name, cls=self.__class__)
-                if error_:
+                try:
+                    value, error_ = known_field.validate(value, self.__dict__, loc=name, cls=self.__class__)
+                    if error_:
+                        raise ValidationError([error_], self.__class__)
+                except Exception:
                     self.__dict__[name] = original_value
-                    raise ValidationError([error_], self.__class__)
-                new_values[name] = value
+                    raise
+                else:
+                    new_values[name] = value
 
             errors = []
             for skip_on_failure, validator in self.__post_root_validators__:
diff --git a/pydantic/utils.py b/pydantic/utils.py
index 96b792fbe..4592223d1 100644
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -631,4 +631,4 @@ def is_valid_field(name: str) -> bool:
 
 
 def is_valid_private_name(name: str) -> bool:
-    return not is_valid_field(name) and name not in {'__annotations__', '__module__', '__annotations__', '__qualname__'}
+    return not is_valid_field(name) and name not in {'__annotations__', '__classcell__', '__module__', '__qualname__'}
diff --git a/pydantic/validators.py b/pydantic/validators.py
index 3fc9e4377..5beb4ea7e 100644
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -593,7 +593,7 @@ def find_validators(  # noqa: C901 (ignore complexity)
         yield make_literal_validator(type_)
         return
     if is_builtin_dataclass(type_):
-        yield from make_dataclass_validator(type_)
+        yield from make_dataclass_validator(type_, config)
         return
     if type_ is Enum:
         yield enum_validator


Test patch:
diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py
index 202184c93..35c2ba3c2 100644
--- a/tests/mypy/modules/success.py
+++ b/tests/mypy/modules/success.py
@@ -9,7 +9,7 @@ from datetime import date, datetime
 from typing import Any, Dict, Generic, List, Optional, TypeVar
 
 from pydantic import BaseModel, NoneStr, PyObject, StrictBool, root_validator, validate_arguments, validator
-from pydantic.fields import Field
+from pydantic.fields import Field, PrivateAttr
 from pydantic.generics import GenericModel
 from pydantic.typing import ForwardRef
 
@@ -121,6 +121,7 @@ class WithField(BaseModel):
     first_name: str = Field('John', const=True)
 
 
+# simple decorator
 @validate_arguments
 def foo(a: int, *, c: str = 'x') -> str:
     return c * a
@@ -130,6 +131,16 @@ foo(1, c='thing')
 foo(1)
 
 
+# nested decorator should not produce an error
+@validate_arguments(config={'arbitrary_types_allowed': True})
+def bar(a: int, *, c: str = 'x') -> str:
+    return c * a
+
+
+bar(1, c='thing')
+bar(1)
+
+
 class Foo(BaseModel):
     a: int
 
@@ -145,3 +156,7 @@ class MyConf(BaseModel):
 conf = MyConf()
 var1: date = conf.str_pyobject(2020, 12, 20)
 var2: date = conf.callable_pyobject(2111, 1, 1)
+
+
+class MyPrivateAttr(BaseModel):
+    _private_field: str = PrivateAttr()
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index 6ad604d48..061b73d09 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -2,7 +2,7 @@ import dataclasses
 from collections.abc import Hashable
 from datetime import datetime
 from pathlib import Path
-from typing import ClassVar, Dict, FrozenSet, Optional
+from typing import ClassVar, Dict, FrozenSet, List, Optional
 
 import pytest
 
@@ -738,3 +738,42 @@ def test_override_builtin_dataclass_nested_schema():
         'title': 'File',
         'type': 'object',
     }
+
+
+def test_inherit_builtin_dataclass():
+    @dataclasses.dataclass
+    class Z:
+        z: int
+
+    @dataclasses.dataclass
+    class Y(Z):
+        y: int
+
+    @pydantic.dataclasses.dataclass
+    class X(Y):
+        x: int
+
+    pika = X(x='2', y='4', z='3')
+    assert pika.x == 2
+    assert pika.y == 4
+    assert pika.z == 3
+
+
+def test_dataclass_arbitrary():
+    class ArbitraryType:
+        def __init__(self):
+            ...
+
+    @dataclasses.dataclass
+    class Test:
+        foo: ArbitraryType
+        bar: List[ArbitraryType]
+
+    class TestModel(BaseModel):
+        a: ArbitraryType
+        b: Test
+
+        class Config:
+            arbitrary_types_allowed = True
+
+    TestModel(a=ArbitraryType(), b=(ArbitraryType(), [ArbitraryType()]))
diff --git a/tests/test_private_attributes.py b/tests/test_private_attributes.py
index 07f702c7f..48be872e2 100644
--- a/tests/test_private_attributes.py
+++ b/tests/test_private_attributes.py
@@ -158,5 +158,22 @@ def test_slots_are_ignored():
 
 
 def test_default_and_default_factory_used_error():
-    with pytest.raises(TypeError, match='default and default_factory args can not be used together'):
+    with pytest.raises(ValueError, match='cannot specify both default and default_factory'):
         PrivateAttr(default=123, default_factory=lambda: 321)
+
+
+def test_config_override_init():
+    class MyModel(BaseModel):
+        x: str
+        _private_attr: int
+
+        def __init__(self, **data) -> None:
+            super().__init__(**data)
+            self._private_attr = 123
+
+        class Config:
+            underscore_attrs_are_private = True
+
+    m = MyModel(x='hello')
+    assert m.dict() == {'x': 'hello'}
+    assert m._private_attr == 123
diff --git a/tests/test_validators.py b/tests/test_validators.py
index 44ca23c47..9097c17d2 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1155,3 +1155,43 @@ def test_nested_literal_validator():
             'ctx': {'given': 'nope', 'permitted': ('foo', 'bar')},
         }
     ]
+
+
+def test_field_that_is_being_validated_is_excluded_from_validator_values(mocker):
+    check_values = mocker.MagicMock()
+
+    class Model(BaseModel):
+        foo: str
+        bar: str
+
+        class Config:
+            validate_assignment = True
+
+        @validator('foo')
+        def validate_foo(cls, v, values):
+            check_values({**values})
+
+    model = Model(foo='foo_value', bar='bar_value')
+    check_values.reset_mock()
+
+    model.foo = 'new_foo_value'
+    check_values.assert_called_once_with({'bar': 'bar_value'})
+
+
+def test_exceptions_in_field_validators_restore_original_field_value():
+    class Model(BaseModel):
+        foo: str
+
+        class Config:
+            validate_assignment = True
+
+        @validator('foo')
+        def validate_foo(cls, v):
+            if v == 'raise_exception':
+                raise RuntimeError('test error')
+            return v
+
+    model = Model(foo='foo')
+    with pytest.raises(RuntimeError, match='test error'):
+        model.foo = 'raise_exception'
+    assert model.foo == 'foo'