Repository: pydantic/pydantic
Pull request: Dataclasses
Most of the changes in pydantic-core are in https://github.com/pydantic/pydantic-core/pull/428

TODO:
* [x] prevent modifying input dc
* [ ] validate assignment
* [x] `field()`
* [x] `InitVar`
* [ ] `validate_on_init` or remove?
* [x] dataclasses as types
* [ ] serialization
* [x] #5118

fix #2816.


Problem statement:
`BaseModel` runtime-warns about private class vars from an arbitrary type annotation
I tried to find something similar to this in the issue set but figured it was worth a new one:

This code triggers an odd `RuntimeWarning` at import time:

```python
from dataclasses import dataclass

from pydantic import BaseModel


@dataclass
class C:
    _private: str = 'yo'


class D(BaseModel):
    c: C = None
    
    class Config:
        arbitrary_types_allowed = True
        validate_all = False
        keep_untouched = (C,)
        underscore_attrs_are_private = False

```
running this code gives:
```
[nav] In [9]: run snippets/pydantic_warning.py
/home/goodboy/repos/piker/snippets/pydantic_warning.py:11: RuntimeWarning: fields may not start with an underscore, ignoring "_private"
  class D(BaseModel):
```
It doesn't seem to matter what `Config` settings I use; the warning stays.

For me this was quite odd since in my real use case, my annotation type was from another lib using a plain stdlib `@dataclass` and `pydantic` was reporting it as though those attrs were local to the class `D` here; so I was pretty confused tracking down the source. If I convert `c` -> `_c` the warning doesn't show up.

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->


Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
(3.9)  >>> python -c "import pydantic.utils; print(pydantic.utils.version_info())"
             pydantic version: 1.8.2
            pydantic compiled: True
                 install path: /home/goodboy/repos/piker/3.9/lib/python3.9/site-packages/pydantic
               python version: 3.9.5 (default, May 12 2021, 17:14:51)  [GCC 10.2.0]
                     platform: Linux-5.12.4-arch1-2-x86_64-with-glibc2.33
     optional deps. installed: ['typing-extensions']
```

Hopefully this isn't just noise 😂

Hints from issue discussion:
Hi @goodboy and thanks for reporting.
Yes it's actually "normal" because when you use a stdlib `dataclass` inside a `BaseModel`, _pydantic_ will convert you stdlib `dataclass` into a _pydantic_ `dataclass` to validate (and coerce if needed) input data.
Behind the scenes it creates a `BaseModel` based on your stdlib `dataclass` and will raise a `RunWarning` at this moment.

@PrettyWood cool.

So the right way to mask this warning is to make the attr private then or does this mean you have to leak `pydantic` config stuff to stdlib dataclasses as a user?
Honestly I reckon we should probably just remove this warning since creating a new class that inherits from `BaseModel` does not raise anything but using `create_model` does
+1 to remove the warning
Currently hitting this as well! Do ya'll know of a clean way to suppress this message in the short term?

Commit message: move inner schema generation

Patch to review:
diff --git a/pydantic/_internal/_dataclasses.py b/pydantic/_internal/_dataclasses.py
new file mode 100644
index 00000000000..9d30662e6b9
--- /dev/null
+++ b/pydantic/_internal/_dataclasses.py
@@ -0,0 +1,157 @@
+"""
+Private logic for creating pydantic datacalsses.
+"""
+from __future__ import annotations as _annotations
+
+import typing
+import warnings
+from typing import Any, Callable, ClassVar
+
+from pydantic_core import ArgsKwargs, SchemaSerializer, SchemaValidator, core_schema
+
+from ..errors import PydanticUndefinedAnnotation
+from ..fields import FieldInfo
+from ._decorators import SerializationFunctions, ValidationFunctions
+from ._fields import collect_fields
+from ._forward_ref import PydanticForwardRef
+from ._generate_schema import dataclass_fields_schema, generate_config, get_dc_self_schema
+from ._model_construction import MockValidator, object_setattr
+
+__all__ = 'StandardDataclass', 'PydanticDataclass', 'prepare_dataclass'
+
+if typing.TYPE_CHECKING:
+    from ..config import BaseConfig
+
+    class StandardDataclass(typing.Protocol):
+        __dataclass_fields__: ClassVar[dict[str, Any]]
+        __dataclass_params__: ClassVar[Any]  # in reality `dataclasses._DataclassParams`
+        __post_init__: ClassVar[Callable[..., None]]
+
+        def __init__(self, *args: object, **kwargs: object) -> None:
+            pass
+
+    class PydanticDataclass(StandardDataclass):
+        __pydantic_validator__: typing.ClassVar[SchemaValidator]
+        __pydantic_core_schema__: typing.ClassVar[core_schema.CoreSchema]
+        __pydantic_serializer__: typing.ClassVar[SchemaSerializer]
+        __pydantic_validator_functions__: typing.ClassVar[ValidationFunctions]
+        __pydantic_serializer_functions__: typing.ClassVar[SerializationFunctions]
+        __pydantic_fields__: typing.ClassVar[dict[str, FieldInfo]]
+
+
+def prepare_dataclass(
+    cls: type[Any],
+    config: type[BaseConfig],
+    kw_only: bool,
+    *,
+    raise_errors: bool = True,
+    types_namespace: dict[str, Any] | None = None,
+) -> bool:
+    """
+    Prepare a raw class to become a pydantic dataclass.
+
+    Returns `True` if the validation construction is successfully completed, else `False`.
+
+    This logic is called on a class which is yet to be wrapped in `dataclasses.dataclass()`.
+    """
+    if hasattr(cls, '__post_init_post_parse__'):
+        warnings.warn(
+            'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
+        )
+
+    name = cls.__name__
+    bases = cls.__bases__
+    # added for compatibility with code that expects a model
+    # TODO actually support generic dataclasses
+    cls.__pydantic_generic_typevars_map__ = None
+    cls.__pydantic_generic_args__ = None
+    cls.__pydantic_generic_origin__ = None
+
+    self_schema, model_ref = get_dc_self_schema(cls)
+    types_namespace = {**(types_namespace or {}), name: PydanticForwardRef(self_schema, cls)}
+    try:
+        fields = collect_fields(cls, bases, types_namespace, is_dataclass=True, dc_kw_only=kw_only)
+    except PydanticUndefinedAnnotation as e:
+        if raise_errors:
+            raise
+        warning_string = f'`{name}` is not fully defined, you should define `{e}`, then call `{name}.model_rebuild()`'
+        if config.undefined_types_warning:
+            raise UserWarning(warning_string)
+        cls.__pydantic_validator__ = MockValidator(warning_string)
+        return False
+
+    cls.__pydantic_validator_functions__ = validator_functions = ValidationFunctions(bases)
+    cls.__pydantic_serializer_functions__ = serializer_functions = SerializationFunctions(bases)
+
+    for var_name, value in vars(cls).items():
+        found_validator = validator_functions.extract_decorator(var_name, value)
+        if not found_validator:
+            serializer_functions.extract_decorator(var_name, value)
+
+    validator_functions.set_bound_functions(cls)
+    serializer_functions.set_bound_functions(cls)
+
+    fields_schema = dataclass_fields_schema(
+        name,
+        model_ref,
+        fields,
+        hasattr(cls, '__post_init__'),
+        validator_functions,
+        serializer_functions,
+        config['arbitrary_types_allowed'],
+        types_namespace,
+    )
+    validator_functions.check_for_unused()
+    serializer_functions.check_for_unused()
+
+    core_config = generate_config(config, cls)
+    cls.__pydantic_fields__ = fields
+    cls.__pydantic_validator__ = SchemaValidator(fields_schema, core_config)
+
+    # dataclass.__init__ must be defined here so its `__qualname__` can be changed.
+
+    def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None:
+        __tracebackhide__ = True
+        dc_dict, init_vars = __dataclass_self__.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs))
+        object_setattr(__dataclass_self__, '__dict__', dc_dict)
+        if init_vars is not None:
+            __dataclass_self__.__post_init__(*init_vars)
+
+    __init__.__qualname__ = f'{cls.__qualname__}.__init__'
+    cls.__init__ = __init__
+    # this works because cls has been transformed into a dataclass by the time "cls" is called
+    cls.__pydantic_core_schema__ = core_schema.dataclass_schema(cls, fields_schema)
+    # cls.__pydantic_serializer__ = SchemaSerializer(outer_schema, core_config)
+
+    return True
+
+
+def is_builtin_dataclass(_cls: type[Any]) -> bool:
+    """
+    Whether a class is a stdlib dataclass
+    (useful to discriminated a pydantic dataclass that is actually a wrapper around a stdlib dataclass)
+
+    we check that
+    - `_cls` is a dataclass
+    - `_cls` is not a processed pydantic dataclass (with a basemodel attached)
+    - `_cls` is not a pydantic dataclass inheriting directly from a stdlib dataclass
+    e.g.
+    ```py
+    @dataclasses.dataclass
+    class A:
+        x: int
+
+    @pydantic.dataclasses.dataclass
+    class B(A):
+        y: int
+    ```
+    In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
+    which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
+    """
+    import dataclasses
+
+    return (
+        dataclasses.is_dataclass(_cls)
+        and not hasattr(_cls, '__pydantic_validator__')
+        and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
+    )
diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py
index 42f03c87a35..2ad8ebc4a2f 100644
--- a/pydantic/_internal/_fields.py
+++ b/pydantic/_internal/_fields.py
@@ -3,12 +3,23 @@
 """
 from __future__ import annotations as _annotations
 
+import dataclasses
+import re
+import sys
 from abc import ABC, abstractmethod
-from typing import Any
+from copy import copy
+from typing import TYPE_CHECKING, Any, ForwardRef
 
 from pydantic_core import core_schema
 
+from ..errors import PydanticUndefinedAnnotation
+from ._forward_ref import PydanticForwardRef
+from ._generics import replace_types
 from ._repr import Representation
+from ._typing_extra import get_type_hints, is_classvar
+
+if TYPE_CHECKING:
+    from ..fields import FieldInfo
 
 
 class _UndefinedType:
@@ -79,3 +90,162 @@ def _update_attrs(self, constraints: dict[str, Any], attrs: set[str] | None = No
             if k not in attrs:
                 raise TypeError(f'{self.__class__.__name__} has no attribute {k!r}')
             setattr(self, k, v)
+
+
+def collect_fields(  # noqa: C901
+    cls: type[Any],
+    bases: tuple[type[Any], ...],
+    types_namespace: dict[str, Any] | None,
+    *,
+    typevars_map: dict[str, Any] | None = None,
+    is_dataclass: bool = False,
+    dc_kw_only: bool | None = None,
+) -> dict[str, FieldInfo]:
+    """
+    Collect the fields of a pydantic model or dataclass, also returns the model/class ref to use in the
+    core-schema.
+
+    :param cls: BaseModel or dataclass
+    :param bases: parents of the class, generally `cls.__bases__`
+    :param types_namespace: optional extra namespace to look for types in
+    :param typevars_map: TODO ???
+    :param is_dataclass: whether the class is a dataclass, used to decide about kw_only setting
+    :param dc_kw_only: whether the whole dataclass is kw_only
+    """
+    from ..fields import FieldInfo
+
+    module_name = getattr(cls, '__module__', None)
+    global_ns: dict[str, Any] | None = None
+    if module_name:
+        try:
+            global_ns = sys.modules[module_name].__dict__
+        except KeyError:
+            # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
+            pass
+
+    # get type hints and raise a PydanticUndefinedAnnotation if any types are undefined
+    try:
+        type_hints = get_type_hints(cls, global_ns, types_namespace, include_extras=True)
+    except NameError as e:
+        try:
+            name = e.name
+        except AttributeError:
+            m = re.search(r".*'(.+?)'", str(e))
+            if m:
+                name = m.group(1)
+            else:
+                # should never happen
+                raise
+        raise PydanticUndefinedAnnotation(name) from e
+
+    # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
+    # annotations is only used for finding fields in parent classes
+    annotations = cls.__dict__.get('__annotations__', {})
+    fields: dict[str, FieldInfo] = {}
+
+    # FIXME (dataclasses) what are we doing here?
+    # cls_fields: dict[str, FieldInfo] = getattr(cls, 'model_fields', None) or getattr(cls, '__pydantic_fields__', {})
+
+    # currently just used for `init=False` dataclass fields, but could be used more
+    omitted_fields: set[str] | None = getattr(cls, '__pydantic_omitted_fields__', None)
+
+    for ann_name, ann_type in type_hints.items():
+        if ann_name.startswith('_') or is_classvar(ann_type) or (omitted_fields and ann_name in omitted_fields):
+            continue
+
+        # raise a PydanticUndefinedAnnotation if type is undefined
+        if isinstance(ann_type, ForwardRef):
+            raise PydanticUndefinedAnnotation(ann_type.__forward_arg__)
+
+        # FIXME (dataclasses) what are we doing here?
+        pydantic_generic_typevars_map = getattr(cls, '__pydantic_generic_typevars_map__', None)
+        if pydantic_generic_typevars_map:
+            ann_type = replace_types(ann_type, pydantic_generic_typevars_map)
+
+        if ann_type is dataclasses.KW_ONLY:
+            # all field fields will be kw_only
+            dc_kw_only = True
+            continue
+        kw_only = dc_kw_only
+
+        init_var = False
+        if ann_type is dataclasses.InitVar:
+            init_var = True
+            ann_type = Any
+        elif isinstance(ann_type, dataclasses.InitVar):
+            init_var = True
+            ann_type = ann_type.type
+
+        # FIXME (dataclasses) what are we doing here?
+        generic_origin = getattr(cls, '__pydantic_generic_origin__', ())
+        for base in bases:
+            if hasattr(base, ann_name):
+                if base is generic_origin:
+                    # Don't error when "shadowing" of attributes in parametrized generics
+                    continue
+                if is_dataclass and dataclasses.is_dataclass(base):
+                    # Don't error when shadowing a field in a parent dataclass
+                    continue
+                raise NameError(
+                    f'Field name "{ann_name}" shadows an attribute in parent "{base.__qualname__}"; '
+                    f'you might want to use a different field name with "alias=\'{ann_name}\'".'
+                )
+
+        try:
+            default = getattr(cls, ann_name, Undefined)
+            if default is Undefined and generic_origin:
+                default = (generic_origin.__pydantic_generic_defaults__ or {}).get(ann_name, Undefined)
+            if default is Undefined:
+                raise AttributeError
+        except AttributeError:
+            if ann_name in annotations or isinstance(ann_type, PydanticForwardRef):
+                field_info = FieldInfo.from_annotation(ann_type)
+            else:
+                # # if field has no default value and is not in __annotations__ this means that it is
+                # # defined in a base class and we can take it from there
+                # fields[ann_name] = cls_fields[ann_name]
+                model_fields_lookup: dict[str, FieldInfo] = {}
+                for x in cls.__bases__[::-1]:
+                    model_fields_lookup.update(getattr(x, 'model_fields', {}))
+                if ann_name in model_fields_lookup:
+                    # The field was present on one of the (possibly multiple) base classes
+                    # copy the field to make sure typevar substitutions don't cause issues with the base classes
+                    field_info = copy(model_fields_lookup[ann_name])
+                else:
+                    # The field was not found on any base classes; this seems to be caused by fields not getting
+                    # generated thanks to models not being fully defined while initializing recursive models.
+                    # Nothing stops us from just creating a new FieldInfo for this type hint, so we do this.
+                    field_info = FieldInfo.from_annotation(ann_type)
+        else:
+            if isinstance(default, dataclasses.Field):
+                if not default.init:
+                    # dataclasses.Field with init=False are not fields
+                    continue
+                if default.kw_only is True:
+                    kw_only = True
+
+            field_info = FieldInfo.from_annotated_attribute(ann_type, default)
+            # attributes which are fields are removed from the class namespace:
+            # 1. To match the behaviour of annotation-only fields
+            # 2. To avoid false positives in the NameError check above
+            try:
+                delattr(cls, ann_name)
+                if cls.__pydantic_generic_parameters__:  # model can be parametrized
+                    assert cls.__pydantic_generic_defaults__ is not None
+                    cls.__pydantic_generic_defaults__[ann_name] = default
+            except AttributeError:
+                pass  # indicates the attribute was on a parent class
+
+        if init_var:
+            field_info.init_var = True
+        if kw_only is not None:
+            field_info.kw_only = kw_only
+        fields[ann_name] = field_info
+
+    # FIXME (dataclasses) what are we doing here?
+    typevars_map = getattr(cls, '__pydantic_generic_typevars_map__', None) or typevars_map
+    if typevars_map:
+        for field in fields.values():
+            field.annotation = replace_types(field.annotation, typevars_map)
+
+    return fields
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
index c7a66297adb..dec046ed3f7 100644
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -18,17 +18,20 @@
 from ..errors import PydanticSchemaGenerationError, PydanticUserError
 from ..fields import FieldInfo
 from ..json_schema import JsonSchemaMetadata, JsonSchemaValue
-from . import _discriminated_union, _fields, _typing_extra
+from . import _discriminated_union, _typing_extra
 from ._core_metadata import CoreMetadataHandler, build_metadata_dict
 from ._core_utils import get_type_ref
 from ._decorators import SerializationFunctions, Serializer, ValidationFunctions, Validator
+from ._fields import PydanticGeneralMetadata, PydanticMetadata, Undefined, collect_fields
 from ._forward_ref import PydanticForwardRef
 from ._generics import replace_types
 
 if TYPE_CHECKING:
+    from ..config import ConfigDict
     from ..main import BaseModel
+    from ._dataclasses import StandardDataclass
 
-__all__ = 'model_fields_schema', 'GenerateSchema', 'generate_config'
+__all__ = 'model_fields_schema', 'GenerateSchema', 'generate_config', 'get_model_self_schema'
 
 
 _SUPPORTS_TYPEDDICT = sys.version_info >= (3, 11)
@@ -38,19 +41,20 @@ def model_fields_schema(
     model_ref: str,
     fields: dict[str, FieldInfo],
     validator_functions: ValidationFunctions,
-    serialization_functions: SerializationFunctions,
+    serializer_functions: SerializationFunctions,
     arbitrary_types: bool,
     types_namespace: dict[str, Any] | None,
     typevars_map: dict[Any, Any] | None,
 ) -> core_schema.CoreSchema:
     """
-    Generate schema for the fields of a pydantic model, this is slightly different to the schema for the model itself,
-    since this is typed_dict schema which is used to create the model.
+    Generate schema for the fields of a pydantic model, this is slightly different to the schema for the model itself.
+
+    This is typed_dict schema which is used to create the model.
     """
     schema_generator = GenerateSchema(arbitrary_types, types_namespace, typevars_map)
     schema: core_schema.CoreSchema = core_schema.typed_dict_schema(
         {
-            k: schema_generator.generate_field_schema(k, v, validator_functions, serialization_functions)
+            k: schema_generator.generate_td_field_schema(k, v, validator_functions, serializer_functions)
             for k, v in fields.items()
         },
         ref=model_ref,
@@ -60,12 +64,37 @@ def model_fields_schema(
     return schema
 
 
-def generate_config(cls: type[BaseModel]) -> core_schema.CoreConfig:
+def dataclass_fields_schema(
+    dataclass_name: str,
+    ref: str,
+    fields: dict[str, FieldInfo],
+    has_post_init: bool,
+    validator_functions: ValidationFunctions,
+    serializer_functions: SerializationFunctions,
+    arbitrary_types: bool,
+    types_namespace: dict[str, Any] | None,
+) -> core_schema.CoreSchema:
+    """
+    Generate schema for the fields of a dataclass, using `dataclass_args_schema`.
+    """
+    # TODO add typevars_map argument when we support generic dataclasses
+    schema_generator = GenerateSchema(arbitrary_types, types_namespace, None)
+    args = [
+        schema_generator.generate_dc_field_schema(k, v, validator_functions, serializer_functions)
+        for k, v in fields.items()
+    ]
+    schema: core_schema.CoreSchema = core_schema.dataclass_args_schema(
+        dataclass_name, args, collect_init_only=has_post_init, ref=ref
+    )
+    schema = apply_validators(schema, validator_functions.get_root_decorators())
+    return schema
+
+
+def generate_config(config: ConfigDict, cls: type[Any]) -> core_schema.CoreConfig:
     """
     Create a pydantic-core config from a pydantic config.
     """
-    config = cls.model_config
-    return core_schema.CoreConfig(
+    core_config = core_schema.CoreConfig(
         title=config['title'] or cls.__name__,
         typed_dict_extra_behavior=config['extra'].value,
         allow_inf_nan=config['allow_inf_nan'],
@@ -77,6 +106,14 @@ def generate_config(cls: type[BaseModel]) -> core_schema.CoreConfig:
         ser_json_timedelta=config['ser_json_timedelta'],
         ser_json_bytes=config['ser_json_bytes'],
     )
+    str_max_length = config.get('str_max_length')
+    if str_max_length is not None:
+        core_config['str_max_length'] = str_max_length
+    str_min_length = config.get('str_min_length')
+    if str_min_length is not None:
+        core_config['str_min_length'] = str_min_length
+
+    return core_config
 
 
 class GenerateSchema:
@@ -150,7 +187,7 @@ def _generate_schema(self, obj: Any) -> core_schema.CoreSchema:  # noqa: C901
                     metadata={'invalid': True, 'pydantic_debug_self_schema': resolved_model.schema}
                 )
             else:
-                return get_model_self_schema(resolved_model)
+                return get_model_self_schema(resolved_model)[0]
 
         try:
             if obj in {bool, int, float, str, bytes, list, set, frozenset, tuple, dict}:
@@ -246,7 +283,7 @@ def _generate_schema(self, obj: Any) -> core_schema.CoreSchema:  # noqa: C901
                     f'Setting `arbitrary_types_allowed=True` in the model_config may prevent this error.'
                 )
 
-    def generate_field_schema(
+    def generate_td_field_schema(
         self,
         name: str,
         field_info: FieldInfo,
@@ -289,6 +326,36 @@ def generate_field_schema(
             field_schema['serialization_exclude'] = True
         return field_schema
 
+    def generate_dc_field_schema(
+        self,
+        name: str,
+        field_info: FieldInfo,
+        validator_functions: ValidationFunctions,
+        serializer_functions: SerializationFunctions,
+    ) -> core_schema.DataclassField:
+        """
+        Prepare a DataclassField to represent the parameter/field, of a dataclass
+        """
+        assert field_info.annotation is not None, 'field.annotation should not be None when generating a schema'
+        schema = self.generate_schema(field_info.annotation)
+        schema = apply_annotations(schema, field_info.metadata)
+
+        if not field_info.is_required():
+            schema = wrap_default(field_info, schema)
+
+        schema = apply_validators(schema, validator_functions.get_field_decorators(name))
+        schema = apply_serializers(schema, serializer_functions.get_field_decorators(name))
+        # use `or None` to so the core schema is minimal
+        return core_schema.dataclass_field(
+            name,
+            schema,
+            init_only=field_info.init_var or None,
+            kw_only=None if field_info.kw_only else False,
+            serialization_exclude=field_info.exclude or None,
+            validation_alias=field_info.alias,
+            serialization_alias=field_info.alias,
+        )
+
     def _union_schema(self, union_type: Any) -> core_schema.CoreSchema:
         """
         Generate schema for a Union.
@@ -351,8 +418,8 @@ def _typed_dict_schema(
         required_keys: typing.FrozenSet[str] = typed_dict_cls.__required_keys__
 
         fields: typing.Dict[str, core_schema.TypedDictField] = {}
-        validation_functions = ValidationFunctions(())
-        serialization_functions = SerializationFunctions(())
+        validator_functions = ValidationFunctions(())
+        serializer_functions = SerializationFunctions(())
 
         obj_ref = f'{typed_dict_cls.__module__}.{typed_dict_cls.__qualname__}:{id(typed_dict_cls)}'
         if obj_ref in self._recursion_cache:
@@ -375,8 +442,8 @@ def _typed_dict_schema(
                 annotation = replace_types(annotation, self.typevars_map)
 
             field_info = FieldInfo.from_annotation(annotation)
-            fields[field_name] = self.generate_field_schema(
-                field_name, field_info, validation_functions, serialization_functions, required=required
+            fields[field_name] = self.generate_td_field_schema(
+                field_name, field_info, validator_functions, serializer_functions, required=required
             )
 
         typed_dict_ref = get_type_ref(typed_dict_cls)
@@ -627,6 +694,8 @@ def _std_types_schema(self, obj: Any) -> core_schema.CoreSchema | None:
             return None
 
         # Import here to avoid the extra import time earlier since _std_validators imports lots of things globally
+        import dataclasses
+
         from ._std_types_schema import SCHEMA_LOOKUP
 
         # instead of iterating over a list and calling is_instance, this should be somewhat faster,
@@ -638,8 +707,31 @@ def _std_types_schema(self, obj: Any) -> core_schema.CoreSchema | None:
             except KeyError:
                 continue
             return encoder(self, obj)
+        if dataclasses.is_dataclass(obj):
+            return self._dataclass_schema(obj)
         return None
 
+    def _dataclass_schema(self, dataclass: type[StandardDataclass]) -> core_schema.CoreSchema:
+        """
+        Generate schema for a dataclass.
+        """
+        # FIXME we need a way to make sure kw_only info is propagated through to fields
+        fields = collect_fields(
+            dataclass, dataclass.__bases__, self.types_namespace, dc_kw_only=True, is_dataclass=True
+        )
+
+        fields_schema = dataclass_fields_schema(
+            dataclass.__name__,
+            get_type_ref(dataclass),
+            fields,
+            hasattr(dataclass, '__post_init__'),
+            ValidationFunctions(()),
+            SerializationFunctions(()),
+            self.arbitrary_types,
+            self.types_namespace,
+        )
+        return core_schema.dataclass_schema(dataclass, fields_schema)
+
     def _unsubstituted_typevar_schema(self, typevar: typing.TypeVar) -> core_schema.CoreSchema:
         assert isinstance(typevar, typing.TypeVar)
         if typevar.__bound__:
@@ -728,11 +820,11 @@ def apply_single_annotation(schema: core_schema.CoreSchema, metadata: Any) -> co
         # TODO setting a default here needs to be tested
         return wrap_default(metadata, schema)
 
-    if isinstance(metadata, _fields.PydanticGeneralMetadata):
+    if isinstance(metadata, PydanticGeneralMetadata):
         metadata_dict = metadata.__dict__
-    elif isinstance(metadata, (BaseMetadata, _fields.PydanticMetadata)):
+    elif isinstance(metadata, (BaseMetadata, PydanticMetadata)):
         metadata_dict = dataclasses.asdict(metadata)
-    elif isinstance(metadata, type) and issubclass(metadata, _fields.PydanticMetadata):
+    elif isinstance(metadata, type) and issubclass(metadata, PydanticMetadata):
         # also support PydanticMetadata classes being used without initialisation,
         # e.g. `Annotated[int, Strict]` as well as `Annotated[int, Strict()]`
         metadata_dict = {k: v for k, v in vars(metadata).items() if not k.startswith('_')}
@@ -772,7 +864,7 @@ def apply_single_annotation(schema: core_schema.CoreSchema, metadata: Any) -> co
 def wrap_default(field_info: FieldInfo, schema: core_schema.CoreSchema) -> core_schema.CoreSchema:
     if field_info.default_factory:
         return core_schema.with_default_schema(schema, default_factory=field_info.default_factory)
-    elif field_info.default is not _fields.Undefined:
+    elif field_info.default is not Undefined:
         return core_schema.with_default_schema(schema, default=field_info.default)
     else:
         return schema
@@ -801,11 +893,25 @@ def _get_pydantic_modify_json_schema(obj: Any) -> typing.Callable[[JsonSchemaVal
     return modify_js_function
 
 
-def get_model_self_schema(cls: type[BaseModel]) -> core_schema.ModelSchema:
+def get_model_self_schema(cls: type[BaseModel]) -> tuple[core_schema.ModelSchema, str]:
     model_ref = get_type_ref(cls)
-    model_js_metadata = cls.model_json_schema_metadata()
-    return core_schema.model_schema(
+    try:
+        model_js_metadata = cls.model_json_schema_metadata()
+    except AttributeError:
+        model_js_metadata = None
+    schema = core_schema.model_schema(
         cls,
         core_schema.definition_reference_schema(model_ref),
         metadata=build_metadata_dict(js_metadata=model_js_metadata),
     )
+    return schema, model_ref
+
+
+def get_dc_self_schema(cls: type[StandardDataclass]) -> tuple[core_schema.DataclassSchema, str]:
+    dataclass_ref = get_type_ref(cls)
+    # TODO js_metadata
+    schema = core_schema.dataclass_schema(
+        cls,
+        core_schema.definition_reference_schema(dataclass_ref),
+    )
+    return schema, dataclass_ref
diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py
index c48cfbff1fd..b3f5fac99e4 100644
--- a/pydantic/_internal/_generics.py
+++ b/pydantic/_internal/_generics.py
@@ -238,17 +238,19 @@ def generic_recursion_self_type(origin: type[BaseModel], args: tuple[Any, ...])
     else:
         token = None
 
-    type_ref = get_type_ref(origin, args_override=args)
-    if visit_counts_by_ref[type_ref] >= 2:
-        self_type = PydanticForwardRef(
-            core_schema.definition_reference_schema(type_ref), origin, ({'kind': 'class_getitem', 'item': args},)
-        )
-        yield self_type
-    else:
-        visit_counts_by_ref[type_ref] += 1
-        yield None
-    if token:
-        _visit_counts_context.reset(token)
+    try:
+        type_ref = get_type_ref(origin, args_override=args)
+        if visit_counts_by_ref[type_ref] >= 2:
+            self_type = PydanticForwardRef(
+                core_schema.definition_reference_schema(type_ref), origin, ({'kind': 'class_getitem', 'item': args},)
+            )
+            yield self_type
+        else:
+            visit_counts_by_ref[type_ref] += 1
+            yield None
+    finally:
+        if token:
+            _visit_counts_context.reset(token)
 
 
 def recursively_defined_type_refs() -> set[str]:
diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py
index bc32a711a0e..a289abbbb0f 100644
--- a/pydantic/_internal/_model_construction.py
+++ b/pydantic/_internal/_model_construction.py
@@ -3,11 +3,8 @@
 """
 from __future__ import annotations as _annotations
 
-import re
-import sys
 import typing
 import warnings
-from copy import copy
 from functools import partial
 from types import FunctionType
 from typing import Any, Callable
@@ -16,14 +13,12 @@
 
 from ..errors import PydanticUndefinedAnnotation, PydanticUserError
 from ..fields import FieldInfo, ModelPrivateAttr, PrivateAttr
-from . import _typing_extra
 from ._core_metadata import build_metadata_dict
 from ._core_utils import consolidate_refs, define_expected_missing_refs
-from ._decorators import SerializationFunctions, ValidationFunctions
-from ._fields import Undefined
+from ._fields import Undefined, collect_fields
 from ._forward_ref import PydanticForwardRef
 from ._generate_schema import generate_config, get_model_self_schema, model_fields_schema
-from ._generics import recursively_defined_type_refs, replace_types
+from ._generics import recursively_defined_type_refs
 from ._utils import ClassAttribute, is_valid_identifier
 
 if typing.TYPE_CHECKING:
@@ -32,7 +27,7 @@
     from ..config import ConfigDict
     from ..main import BaseModel
 
-__all__ = 'object_setattr', 'init_private_attributes', 'inspect_namespace', 'complete_model_class'
+__all__ = 'object_setattr', 'init_private_attributes', 'inspect_namespace', 'complete_model_class', 'MockValidator'
 
 
 IGNORED_TYPES: tuple[Any, ...] = (FunctionType, property, type, classmethod, staticmethod)
@@ -104,17 +99,22 @@ def deferred_model_get_pydantic_validation_schema(
     the validator and set `__pydantic_validator__` as that would fail in some cases - e.g. mutually referencing
     models.
     """
-    inner_schema, fields = build_inner_schema(
-        cls,
-        cls.__name__,
+    self_schema, model_ref = get_model_self_schema(cls)
+    types_namespace = {**(types_namespace or {}), cls.__name__: PydanticForwardRef(self_schema, cls)}
+    fields = collect_fields(cls, cls.__bases__, types_namespace)
+
+    model_config = cls.model_config
+    inner_schema = model_fields_schema(
+        model_ref,
+        fields,
         cls.__pydantic_validator_functions__,
         cls.__pydantic_serializer_functions__,
-        cls.__bases__,
+        model_config['arbitrary_types_allowed'],
         types_namespace,
         typevars_map,
     )
 
-    core_config = generate_config(cls)
+    core_config = generate_config(model_config, cls)
     # we have to set model_fields as otherwise `repr` on the model will fail
     cls.model_fields = fields
     model_post_init = '__pydantic_post_init__' if hasattr(cls, '__pydantic_post_init__') else None
@@ -131,8 +131,6 @@ def deferred_model_get_pydantic_validation_schema(
 def complete_model_class(
     cls: type[BaseModel],
     name: str,
-    validator_functions: ValidationFunctions,
-    serialization_functions: SerializationFunctions,
     bases: tuple[type[Any], ...],
     *,
     raise_errors: bool = True,
@@ -147,12 +145,23 @@ def complete_model_class(
     This logic must be called after class has been created since validation functions must be bound
     and `get_type_hints` requires a class object.
     """
+    validator_functions = cls.__pydantic_validator_functions__
+    serializer_functions = cls.__pydantic_serializer_functions__
     validator_functions.set_bound_functions(cls)
-    serialization_functions.set_bound_functions(cls)
+    serializer_functions.set_bound_functions(cls)
 
+    self_schema, model_ref = get_model_self_schema(cls)
+    types_namespace = {**(types_namespace or {}), cls.__name__: PydanticForwardRef(self_schema, cls)}
     try:
-        inner_schema, fields = build_inner_schema(
-            cls, name, validator_functions, serialization_functions, bases, types_namespace, typevars_map
+        fields = collect_fields(cls, bases, types_namespace, typevars_map=typevars_map)
+        inner_schema = model_fields_schema(
+            model_ref,
+            fields,
+            validator_functions,
+            serializer_functions,
+            cls.model_config['arbitrary_types_allowed'],
+            types_namespace,
+            typevars_map,
         )
     except PydanticUndefinedAnnotation as e:
         if raise_errors:
@@ -174,13 +183,14 @@ def complete_model_class(
             deferred_model_get_pydantic_validation_schema, cls, typevars_map=typevars_map
         )
         return False
+
     inner_schema = consolidate_refs(inner_schema)
     inner_schema = define_expected_missing_refs(inner_schema, recursively_defined_type_refs())
 
     validator_functions.check_for_unused()
-    serialization_functions.check_for_unused()
+    serializer_functions.check_for_unused()
 
-    core_config = generate_config(cls)
+    core_config = generate_config(cls.model_config, cls)
     cls.model_fields = fields
     cls.__pydantic_validator__ = SchemaValidator(inner_schema, core_config)
     model_post_init = '__pydantic_post_init__' if hasattr(cls, '__pydantic_post_init__') else None
@@ -202,126 +212,6 @@ def complete_model_class(
     return True
 
 
-def build_inner_schema(  # noqa: C901
-    cls: type[BaseModel],
-    name: str,
-    validator_functions: ValidationFunctions,
-    serialization_functions: SerializationFunctions,
-    bases: tuple[type[Any], ...],
-    types_namespace: dict[str, Any] | None = None,
-    typevars_map: dict[Any, Any] = None,
-) -> tuple[core_schema.CoreSchema, dict[str, FieldInfo]]:
-    module_name = getattr(cls, '__module__', None)
-    global_ns: dict[str, Any] | None = None
-    if module_name:
-        try:
-            module = sys.modules[module_name]
-        except KeyError:
-            # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
-            pass
-        else:
-            if types_namespace:
-                global_ns = {**module.__dict__, **types_namespace}
-            else:
-                global_ns = module.__dict__
-
-    self_schema = get_model_self_schema(cls)
-    local_ns = {**(types_namespace or {}), name: PydanticForwardRef(self_schema, cls)}
-
-    # get type hints and raise a PydanticUndefinedAnnotation if any types are undefined
-    try:
-        type_hints = _typing_extra.get_type_hints(cls, global_ns, local_ns, include_extras=True)
-    except NameError as e:
-        try:
-            name = e.name
-        except AttributeError:
-            m = re.search(r".*'(.+?)'", str(e))
-            if m:
-                name = m.group(1)
-            else:
-                # should never happen
-                raise
-        raise PydanticUndefinedAnnotation(name) from e
-
-    # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
-    # annotations is only used for finding fields in parent classes
-    annotations = cls.__dict__.get('__annotations__', {})
-    fields: dict[str, FieldInfo] = {}
-    for ann_name, ann_type in type_hints.items():
-        if ann_name.startswith('_') or _typing_extra.is_classvar(ann_type):
-            continue
-
-        # raise a PydanticUndefinedAnnotation if type is undefined
-        if isinstance(ann_type, typing.ForwardRef):
-            raise PydanticUndefinedAnnotation(ann_type.__forward_arg__)
-
-        if cls.__pydantic_generic_typevars_map__:
-            ann_type = replace_types(ann_type, cls.__pydantic_generic_typevars_map__)
-
-        generic_origin = cls.__pydantic_generic_origin__
-        for base in bases:
-            if hasattr(base, ann_name):
-                if base is not generic_origin:  # Don't warn about "shadowing" of attributes in parametrized generics
-                    raise NameError(
-                        f'Field name "{ann_name}" shadows an attribute in parent "{base.__qualname__}"; '
-                        f'you might want to use a different field name with "alias=\'{ann_name}\'".'
-                    )
-
-        try:
-            default = getattr(cls, ann_name, Undefined)
-            if default is Undefined and generic_origin:
-                default = (generic_origin.__pydantic_generic_defaults__ or {}).get(ann_name, Undefined)
-            if default is Undefined:
-                raise AttributeError
-        except AttributeError:
-            # if field has no default value and is not in __annotations__ this usually means that it is
-            # defined in a base class and we can take it from there
-            if ann_name in annotations or isinstance(ann_type, PydanticForwardRef):
-                fields[ann_name] = FieldInfo.from_annotation(ann_type)
-            else:
-                model_fields_lookup: dict[str, FieldInfo] = {}
-                for x in cls.__bases__[::-1]:
-                    model_fields_lookup.update(getattr(x, 'model_fields', {}))
-                if ann_name in model_fields_lookup:
-                    # The field was present on one of the (possibly multiple) base classes
-                    # copy the field to make sure typevar substitutions don't cause issues with the base classes
-                    field = copy(model_fields_lookup[ann_name])
-                else:
-                    # The field was not found on any base classes; this seems to be caused by fields not getting
-                    # generated thanks to models not being fully defined while initializing recursive models.
-                    # Nothing stops us from just creating a new FieldInfo for this type hint, so we do this.
-                    field = FieldInfo.from_annotation(ann_type)
-                fields[ann_name] = field
-        else:
-            fields[ann_name] = FieldInfo.from_annotated_attribute(ann_type, default)
-            # attributes which are fields are removed from the class namespace:
-            # 1. To match the behaviour of annotation-only fields
-            # 2. To avoid false positives in the NameError check above
-            try:
-                delattr(cls, ann_name)
-                if cls.__pydantic_generic_parameters__:  # model can be parametrized
-                    assert cls.__pydantic_generic_defaults__ is not None
-                    cls.__pydantic_generic_defaults__[ann_name] = default
-            except AttributeError:
-                pass  # indicates the attribute was on a parent class
-
-    typevars_map = cls.__pydantic_generic_typevars_map__ or typevars_map
-    if typevars_map:
-        for field in fields.values():
-            field.annotation = replace_types(field.annotation, typevars_map)
-    model_ref = self_schema['schema']['schema_ref']
-    schema = model_fields_schema(
-        model_ref,
-        fields,
-        validator_functions,
-        serialization_functions,
-        cls.model_config['arbitrary_types_allowed'],
-        local_ns,
-        typevars_map,
-    )
-    return schema, fields
-
-
 def generate_model_signature(init: Callable[..., None], fields: dict[str, FieldInfo], config: ConfigDict) -> Signature:
     """
     Generate signature for model based on its fields
diff --git a/pydantic/_internal/_utils.py b/pydantic/_internal/_utils.py
index ad6c757603f..7d22c71615b 100644
--- a/pydantic/_internal/_utils.py
+++ b/pydantic/_internal/_utils.py
@@ -19,11 +19,9 @@
 from . import _repr, _typing_extra
 
 if typing.TYPE_CHECKING:
-    from ..dataclasses import Dataclass
-    from ..main import BaseModel
-
     MappingIntStrAny = typing.Mapping[int | str, Any]
     AbstractSetIntStr = typing.AbstractSet[int | str]
+    from ..main import BaseModel
 
 __all__ = (
     'sequence_like',
@@ -33,7 +31,6 @@
     'deep_update',
     'update_not_none',
     'almost_equal_floats',
-    'get_model',
     'to_camel',
     'smart_deepcopy',
     'ValueItems',
@@ -154,19 +151,6 @@ def almost_equal_floats(value_1: float, value_2: float, *, delta: float = 1e-8)
     return abs(value_1 - value_2) <= delta
 
 
-def get_model(obj: type[BaseModel] | type[Dataclass]) -> type[BaseModel]:
-    from ..main import BaseModel
-
-    try:
-        model_cls = obj.__pydantic_model__  # type: ignore
-    except AttributeError:
-        model_cls = obj
-
-    if not issubclass(model_cls, BaseModel):
-        raise TypeError('Unsupported type, must be either BaseModel or dataclass')
-    return model_cls
-
-
 def to_camel(string: str) -> str:
     return ''.join(word.capitalize() for word in string.split('_'))
 
diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py
index 50ca32eb0a7..ee5141c7f1e 100644
--- a/pydantic/dataclasses.py
+++ b/pydantic/dataclasses.py
@@ -1,106 +1,23 @@
 """
-The main purpose is to enhance stdlib dataclasses by adding validation
-A pydantic dataclass can be generated from scratch or from a stdlib one.
-
-Behind the scene, a pydantic dataclass is just like a regular one on which we attach
-a `BaseModel` and magic methods to trigger the validation of the data.
-`__init__` and `__post_init__` are hence overridden and have extra logic to be
-able to validate input data.
-
-When a pydantic dataclass is generated from scratch, it's just a plain dataclass
-with validation triggered at initialization
-
-The tricky part if for stdlib dataclasses that are converted after into pydantic ones e.g.
-
-```py
-@dataclasses.dataclass
-class M:
-    x: int
-
-ValidatedM = pydantic.dataclasses.dataclass(M)
-```
-
-We indeed still want to support equality, hashing, repr, ... as if it was the stdlib one!
-
-```py
-assert isinstance(ValidatedM(x=1), M)
-assert ValidatedM(x=1) == M(x=1)
-```
-
-This means we **don't want to create a new dataclass that inherits from it**
-The trick is to create a wrapper around `M` that will act as a proxy to trigger
-validation without altering default `M` behaviour.
+Provide an enhanced dataclass that performs validation.
 """
+from __future__ import annotations as _annotations
+
 import dataclasses
 import sys
-from contextlib import contextmanager
-from functools import wraps
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    Callable,
-    ClassVar,
-    Dict,
-    Generator,
-    Optional,
-    Set,
-    Type,
-    TypeVar,
-    Union,
-    overload,
-)
-
-from typing_extensions import dataclass_transform
+from typing import TYPE_CHECKING, Any, Callable, Optional, Type, TypeVar, Union, overload
 
-from pydantic._internal._utils import ClassAttribute
+from typing_extensions import Literal, dataclass_transform
 
-from ._internal import _fields
-from .config import ConfigDict, Extra, get_config
+from ._internal import _dataclasses as _pydantic_dataclasses
+from .config import ConfigDict, get_config
 from .fields import Field, FieldInfo
-from .main import create_model
 
 if TYPE_CHECKING:
-    from .main import BaseModel
+    from ._internal._dataclasses import PydanticDataclass
 
-    NoArgAnyCallable = Callable[[], Any]
-    CallableGenerator = Generator[Callable[..., Any], None, None]
-    DataclassT = TypeVar('DataclassT', bound='Dataclass')
 
-    DataclassClassOrWrapper = Union[Type['Dataclass'], 'DataclassProxy']
-
-    class Dataclass:
-        # stdlib attributes
-        __dataclass_fields__: ClassVar[Dict[str, Any]]
-        __dataclass_params__: ClassVar[Any]  # in reality `dataclasses._DataclassParams`
-        __post_init__: ClassVar[Callable[..., None]]
-
-        # Added by pydantic
-        __pydantic_run_validation__: ClassVar[bool]
-        __post_init_post_parse__: ClassVar[Callable[..., None]]
-        __pydantic_initialised__: ClassVar[bool]
-        __pydantic_model__: ClassVar[Type[BaseModel]]
-        __pydantic_validate_values__: ClassVar[Callable[['Dataclass'], None]]
-        __pydantic_has_field_info_default__: ClassVar[bool]  # whether a `pydantic.Field` is used as default value
-
-        def __init__(self, *args: object, **kwargs: object) -> None:
-            pass
-
-        @classmethod
-        def __get_validators__(cls: Type['Dataclass']) -> 'CallableGenerator':
-            pass
-
-        @classmethod
-        def __validate__(cls: Type['DataclassT'], v: Any) -> 'DataclassT':
-            pass
-
-
-__all__ = [
-    'dataclass',
-    'set_validation',
-    'create_pydantic_model_from_dataclass',
-    'is_builtin_dataclass',
-    'make_dataclass_validator',
-]
+__all__ = ('dataclass',)
 
 _T = TypeVar('_T')
 
@@ -110,7 +27,7 @@ def __validate__(cls: Type['DataclassT'], v: Any) -> 'DataclassT':
     @overload
     def dataclass(
         *,
-        init: bool = True,
+        init: Literal[False] = False,
         repr: bool = True,
         eq: bool = True,
         order: bool = False,
@@ -119,7 +36,7 @@ def dataclass(
         config: Union[ConfigDict, Type[object], None] = None,
         validate_on_init: Optional[bool] = None,
         kw_only: bool = ...,
-    ) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']:
+    ) -> Callable[[Type[_T]], PydanticDataclass]:
         ...
 
     @dataclass_transform(field_specifiers=(dataclasses.field, Field))
@@ -127,7 +44,7 @@ def dataclass(
     def dataclass(
         _cls: Type[_T],
         *,
-        init: bool = True,
+        init: Literal[False] = False,
         repr: bool = True,
         eq: bool = True,
         order: bool = False,
@@ -136,7 +53,7 @@ def dataclass(
         config: Union[ConfigDict, Type[object], None] = None,
         validate_on_init: Optional[bool] = None,
         kw_only: bool = ...,
-    ) -> 'DataclassClassOrWrapper':
+    ) -> PydanticDataclass:
         ...
 
 else:
@@ -145,7 +62,7 @@ def dataclass(
     @overload
     def dataclass(
         *,
-        init: bool = True,
+        init: Literal[False] = False,
         repr: bool = True,
         eq: bool = True,
         order: bool = False,
@@ -153,7 +70,7 @@ def dataclass(
         frozen: bool = False,
         config: Union[ConfigDict, Type[object], None] = None,
         validate_on_init: Optional[bool] = None,
-    ) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']:
+    ) -> Callable[[Type[_T]], PydanticDataclass]:
         ...
 
     @dataclass_transform(field_specifiers=(dataclasses.field, Field))
@@ -161,7 +78,7 @@ def dataclass(
     def dataclass(
         _cls: Type[_T],
         *,
-        init: bool = True,
+        init: Literal[False] = False,
         repr: bool = True,
         eq: bool = True,
         order: bool = False,
@@ -169,7 +86,7 @@ def dataclass(
         frozen: bool = False,
         config: Union[ConfigDict, Type[object], None] = None,
         validate_on_init: Optional[bool] = None,
-    ) -> 'DataclassClassOrWrapper':
+    ) -> PydanticDataclass:
         ...
 
 
@@ -177,7 +94,7 @@ def dataclass(
 def dataclass(
     _cls: Optional[Type[_T]] = None,
     *,
-    init: bool = True,
+    init: Literal[False] = False,
     repr: bool = True,
     eq: bool = True,
     order: bool = False,
@@ -186,294 +103,46 @@ def dataclass(
     config: Union[ConfigDict, Type[object], None] = None,
     validate_on_init: Optional[bool] = None,
     kw_only: bool = False,
-) -> Union[Callable[[Type[_T]], 'DataclassClassOrWrapper'], 'DataclassClassOrWrapper']:
+) -> Union[Callable[[Type[_T]], PydanticDataclass], PydanticDataclass]:
     """
-    Like the python standard lib dataclasses but with type validation.
-    The result is either a pydantic dataclass that will validate input data
-    or a wrapper that will trigger validation around a stdlib dataclass
-    to avoid modifying it directly
+    Like the python standard lib dataclasses but enhanced with validation.
     """
     the_config = get_config(config)
-
-    def wrap(cls: Type[Any]) -> 'DataclassClassOrWrapper':
-        if is_builtin_dataclass(cls) and _extra_dc_args(_cls) == _extra_dc_args(_cls.__bases__[0]):  # type: ignore
-            dc_cls_doc = ''
-            dc_cls = DataclassProxy(cls)
-            default_validate_on_init = False
-        else:
-            dc_cls_doc = cls.__doc__ or ''  # needs to be done before generating dataclass
-            if sys.version_info >= (3, 10):
-                dc_cls = dataclasses.dataclass(  # type: ignore[call-overload]
-                    cls,
-                    init=init,
-                    repr=repr,
-                    eq=eq,
-                    order=order,
-                    unsafe_hash=unsafe_hash,
-                    frozen=frozen,
-                    kw_only=kw_only,
-                )
-            else:
-                dc_cls = dataclasses.dataclass(
-                    cls, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
-                )
-            default_validate_on_init = True
-
-        should_validate_on_init = default_validate_on_init if validate_on_init is None else validate_on_init
-        _add_pydantic_validation_attributes(cls, the_config, should_validate_on_init, dc_cls_doc)
-        return dc_cls
+    assert init is False, 'pydantic.dataclasses.dataclass only supports init=False'
+
+    def create_dataclass(cls: Type[Any]) -> PydanticDataclass:
+        if dataclasses.is_dataclass(cls) and not hasattr(cls, '__pydantic_fields__'):
+            # so we don't add validation to the existing std lib dataclass, so we subclass it, but we need to
+            # set `__pydantic_fields__` while subclassing so the logic below can treat the new class like its
+            # parent is a pydantic dataclass
+            dc_fields = dataclasses.fields(cls)
+            pydantic_fields = {}
+            omitted_fields = set()
+            for f in dc_fields:
+                if f.init:
+                    pydantic_fields[f.name] = FieldInfo.from_dataclass_field(f)
+                else:
+                    omitted_fields.add(f.name)
+            fields = {f.name: FieldInfo.from_dataclass_field(f) for f in dataclasses.fields(cls) if f.init}
+            cls = type(
+                cls.__name__,
+                (cls,),
+                {'__pydantic_fields__': fields, '__pydantic_omitted_fields__': omitted_fields or None},
+            )
+
+        _pydantic_dataclasses.prepare_dataclass(cls, the_config, kw_only)
+        return dataclasses.dataclass(  # type: ignore[call-overload]
+            cls,
+            init=init,
+            repr=repr,
+            eq=eq,
+            order=order,
+            unsafe_hash=unsafe_hash,
+            frozen=frozen,
+            kw_only=kw_only,
+        )
 
     if _cls is None:
-        return wrap
-
-    return wrap(_cls)
-
-
-@contextmanager
-def set_validation(cls: Type['DataclassT'], value: bool) -> Generator[Type['DataclassT'], None, None]:
-    original_run_validation = cls.__pydantic_run_validation__
-    try:
-        cls.__pydantic_run_validation__ = value
-        yield cls
-    finally:
-        cls.__pydantic_run_validation__ = original_run_validation
-
-
-class DataclassProxy:
-    __slots__ = '__dataclass__'
-
-    def __init__(self, dc_cls: Type['Dataclass']) -> None:
-        object.__setattr__(self, '__dataclass__', dc_cls)
-
-    def __call__(self, *args: Any, **kwargs: Any) -> Any:
-        with set_validation(self.__dataclass__, True):
-            return self.__dataclass__(*args, **kwargs)
-
-    def __getattr__(self, name: str) -> Any:
-        return getattr(self.__dataclass__, name)
-
-    def __instancecheck__(self, instance: Any) -> bool:
-        return isinstance(instance, self.__dataclass__)
-
-
-def _add_pydantic_validation_attributes(  # noqa: C901 (ignore complexity)
-    dc_cls: Type['Dataclass'],
-    config: ConfigDict,
-    validate_on_init: bool,
-    dc_cls_doc: str,
-) -> None:
-    """
-    We need to replace the right method. If no `__post_init__` has been set in the stdlib dataclass
-    it won't even exist (code is generated on the fly by `dataclasses`)
-    By default, we run validation after `__init__` or `__post_init__` if defined
-    """
-    init = dc_cls.__init__
-
-    @wraps(init)
-    def handle_extra_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None:
-        if config['extra'] == Extra.ignore:
-            init(self, *args, **{k: v for k, v in kwargs.items() if k in self.__dataclass_fields__})
-
-        elif config['extra'] == Extra.allow:
-            for k, v in kwargs.items():
-                self.__dict__.setdefault(k, v)
-            init(self, *args, **{k: v for k, v in kwargs.items() if k in self.__dataclass_fields__})
-
-        else:
-            init(self, *args, **kwargs)
-
-    if hasattr(dc_cls, '__post_init__'):
-        post_init = dc_cls.__post_init__
-
-        @wraps(post_init)
-        def new_post_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None:
-            if config['post_init_call'] == 'before_validation':
-                post_init(self, *args, **kwargs)
-
-            if self.__class__.__pydantic_run_validation__:
-                self.__pydantic_validate_values__()
-                if hasattr(self, '__post_init_post_parse__'):
-                    self.__post_init_post_parse__(*args, **kwargs)
-
-            if config['post_init_call'] == 'after_validation':
-                post_init(self, *args, **kwargs)
+        return create_dataclass
 
-        setattr(dc_cls, '__init__', handle_extra_init)
-        setattr(dc_cls, '__post_init__', new_post_init)
-
-    else:
-
-        @wraps(init)
-        def new_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None:
-            handle_extra_init(self, *args, **kwargs)
-
-            if self.__class__.__pydantic_run_validation__:
-                self.__pydantic_validate_values__()
-
-            if hasattr(self, '__post_init_post_parse__'):
-                # We need to find again the initvars. To do that we use `__dataclass_fields__` instead of
-                # public method `dataclasses.fields`
-
-                # get all initvars and their default values
-                initvars_and_values: Dict[str, Any] = {}
-                for i, f in enumerate(self.__class__.__dataclass_fields__.values()):
-                    if f._field_type is dataclasses._FIELD_INITVAR:  # type: ignore[attr-defined]
-                        try:
-                            # set arg value by default
-                            initvars_and_values[f.name] = args[i]
-                        except IndexError:
-                            initvars_and_values[f.name] = kwargs.get(f.name, f.default)
-
-                self.__post_init_post_parse__(**initvars_and_values)
-
-        setattr(dc_cls, '__init__', new_init)
-
-    setattr(dc_cls, '__pydantic_run_validation__', ClassAttribute('__pydantic_run_validation__', validate_on_init))
-    setattr(dc_cls, '__pydantic_initialised__', False)
-    setattr(dc_cls, '__pydantic_model__', create_pydantic_model_from_dataclass(dc_cls, config, dc_cls_doc))
-    setattr(dc_cls, '__pydantic_validate_values__', _dataclass_validate_values)
-    setattr(dc_cls, '__validate__', classmethod(_validate_dataclass))
-    setattr(dc_cls, '__get_validators__', classmethod(_get_validators))
-
-    if dc_cls.__pydantic_model__.model_config['validate_assignment'] and not dc_cls.__dataclass_params__.frozen:
-        setattr(dc_cls, '__setattr__', _dataclass_validate_assignment_setattr)
-
-
-def _get_validators(cls: 'DataclassClassOrWrapper') -> 'CallableGenerator':
-    yield cls.__validate__
-
-
-def _validate_dataclass(cls: Type['DataclassT'], v: Any) -> 'DataclassT':
-    with set_validation(cls, True):
-        if isinstance(v, cls):
-            v.__pydantic_validate_values__()
-            return v
-        elif isinstance(v, (list, tuple)):
-            return cls(*v)
-        elif isinstance(v, dict):
-            return cls(**v)
-        else:
-            raise Exception('todo')
-            # raise DataclassTypeError(class_name=cls.__name__)
-
-
-def create_pydantic_model_from_dataclass(
-    dc_cls: Type['Dataclass'],
-    config: Optional[ConfigDict] = None,
-    dc_cls_doc: Optional[str] = None,
-) -> Type['BaseModel']:
-    if config is None:
-        config = ConfigDict()
-
-    field_definitions: Dict[str, Any] = {}
-    for field in dataclasses.fields(dc_cls):
-        default: Any = _fields.Undefined
-        default_factory: Optional['NoArgAnyCallable'] = None
-        field_info: FieldInfo
-
-        if field.default is not dataclasses.MISSING:
-            default = field.default
-        elif field.default_factory is not dataclasses.MISSING:
-            default_factory = field.default_factory
-        else:
-            default = ...
-
-        if isinstance(default, FieldInfo):
-            field_info = default
-            dc_cls.__pydantic_has_field_info_default__ = True
-        else:
-            field_info = Field(default=default, default_factory=default_factory, **field.metadata)
-
-        field_definitions[field.name] = (field.type, field_info)
-
-    # from .validator_functions import gather_all_validators
-
-    # validators = gather_all_validators(dc_cls)
-    model: Type['BaseModel'] = create_model(
-        dc_cls.__name__,
-        __config__=config,
-        __module__=dc_cls.__module__,
-        # __validators__=validators,
-        __cls_kwargs__={'__resolve_forward_refs__': False},
-        **field_definitions,
-    )
-    model.__doc__ = dc_cls_doc if dc_cls_doc is not None else dc_cls.__doc__ or ''
-    return model
-
-
-def _dataclass_validate_values(self: 'Dataclass') -> None:
-    # validation errors can occur if this function is called twice on an already initialised dataclass.
-    # for example if Extra.forbid is enabled, it would consider __pydantic_initialised__ an invalid extra property
-    if getattr(self, '__pydantic_initialised__'):
-        return
-    if getattr(self, '__pydantic_has_field_info_default__', False):
-        # We need to remove `FieldInfo` values since they are not valid as input
-        # It's ok to do that because they are obviously the default values!
-        input_data = {k: v for k, v in self.__dict__.items() if not isinstance(v, FieldInfo)}
-    else:
-        input_data = self.__dict__
-    raise NotImplementedError(f'TODO {input_data}')
-    # d, _, validation_error = validate_model(self.__pydantic_model__, input_data, cls=self.__class__)
-    # if validation_error:
-    #     raise validation_error
-    # self.__dict__.update(d)
-    # object.__setattr__(self, '__pydantic_initialised__', True)
-
-
-def _dataclass_validate_assignment_setattr(self: 'Dataclass', name: str, value: Any) -> None:
-    if self.__pydantic_initialised__:
-        d = dict(self.__dict__)
-        d.pop(name, None)
-        # known_field = self.__pydantic_model__.model_fields.get(name, None)
-        # if known_field:
-        #     value, error_ = known_field.validate(value, d, loc=name, cls=self.__class__)
-        #     if error_:
-        #         raise ValidationError([error_], self.__class__)
-
-    object.__setattr__(self, name, value)
-
-
-def _extra_dc_args(cls: Type[Any]) -> Set[str]:
-    return {
-        x
-        for x in dir(cls)
-        if x not in getattr(cls, '__dataclass_fields__', {}) and not (x.startswith('__') and x.endswith('__'))
-    }
-
-
-def is_builtin_dataclass(_cls: Type[Any]) -> bool:
-    """
-    Whether a class is a stdlib dataclass
-    (useful to discriminated a pydantic dataclass that is actually a wrapper around a stdlib dataclass)
-
-    we check that
-    - `_cls` is a dataclass
-    - `_cls` is not a processed pydantic dataclass (with a basemodel attached)
-    - `_cls` is not a pydantic dataclass inheriting directly from a stdlib dataclass
-    e.g.
-    ```
-    @dataclasses.dataclass
-    class A:
-        x: int
-
-    @pydantic.dataclasses.dataclass
-    class B(A):
-        y: int
-    ```
-    In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
-    which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
-    """
-    return (
-        dataclasses.is_dataclass(_cls)
-        and not hasattr(_cls, '__pydantic_model__')
-        and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
-    )
-
-
-def make_dataclass_validator(dc_cls: Type['Dataclass'], config: Type[ConfigDict]) -> 'CallableGenerator':
-    """
-    Create a pydantic.dataclass from a builtin dataclass to add type validation
-    and yield the validators
-    It retrieves the parameters of the dataclass and forwards them to the newly created dataclass
-    """
-    yield from _get_validators(dataclass(dc_cls, config=config, validate_on_init=False))
+    return create_dataclass(_cls)
diff --git a/pydantic/fields.py b/pydantic/fields.py
index a2d7fbe16da..0df77270bff 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -12,6 +12,8 @@
 from ._internal._fields import Undefined
 
 if typing.TYPE_CHECKING:
+    from dataclasses import Field as DataclassField
+
     from ._internal._repr import ReprArgs
 
 
@@ -38,6 +40,8 @@ class FieldInfo(_repr.Representation):
         'repr',
         'discriminator',
         'json_schema_extra',
+        'init_var',
+        'kw_only',
     )
 
     # used to convert kwargs to metadata/constraints,
@@ -86,11 +90,14 @@ def __init__(self, **kwargs: Any) -> None:
         self.discriminator = kwargs.get('discriminator')
         self.repr = kwargs.get('repr', True)
         self.json_schema_extra = kwargs.get('json_schema_extra')
+        # currently only used on dataclasses
+        self.init_var = kwargs.get('init_var', None)
+        self.kw_only = kwargs.get('kw_only', None)
 
     @classmethod
     def from_field(cls, default: Any = Undefined, **kwargs: Any) -> 'FieldInfo':
         """
-        Create `FieldInfo` with the `Field` function:
+        Create `FieldInfo` with the `Field` function, e.g.:
         >>> import pydantic
         >>> class MyModel(pydantic.BaseModel):
         >>>     foo: int = pydantic.Field(4, ...)  # <-- like this
@@ -132,14 +139,21 @@ def from_annotated_attribute(cls, annotation: type[Any], default: Any) -> 'Field
         Create `FieldInfo` from an annotation with a default value, e.g.:
         >>> import pydantic, annotated_types, typing
         >>> class MyModel(pydantic.BaseModel):
-        >>>     foo: int = 4
-        >>>     bar: typing.Annotated[int, annotated_types.Gt(4)] = 4
-        >>>     spam: typing.Annotated[int, pydantic.Field(gt=4)] = 4
+        >>>     foo: int = 4  # <-- like this
+        >>>     bar: typing.Annotated[int, annotated_types.Gt(4)] = 4  # <-- or this
+        >>>     spam: typing.Annotated[int, pydantic.Field(gt=4)] = 4  # <-- or this
         """
+        import dataclasses
+
         if isinstance(default, cls):
             default.annotation, annotation_metadata = cls._extract_metadata(annotation)
             default.metadata += annotation_metadata
             return default
+        elif isinstance(default, dataclasses.Field):
+            pydantic_field = cls.from_dataclass_field(default)
+            pydantic_field.annotation, annotation_metadata = cls._extract_metadata(annotation)
+            pydantic_field.metadata += annotation_metadata
+            return pydantic_field
         else:
             if _typing_extra.is_annotated(annotation):
                 first_arg, *extra_args = typing_extensions.get_args(annotation)
@@ -155,6 +169,28 @@ def from_annotated_attribute(cls, annotation: type[Any], default: Any) -> 'Field
 
             return cls(annotation=annotation, default=default)
 
+    @classmethod
+    def from_dataclass_field(cls, dc_field: DataclassField) -> 'FieldInfo':
+        """
+        Construct a `FieldInfo` from a `dataclasses.Field` instance.
+        """
+        import dataclasses
+
+        default = dc_field.default
+        if default is dataclasses.MISSING:
+            default = Undefined
+
+        default_factory = dc_field.default_factory
+        if default_factory is dataclasses.MISSING:
+            default_factory = None
+
+        # use the `Field` function so in correct kwargs raise the correct `TypeError`
+        field = Field(default=default, default_factory=default_factory, repr=dc_field.repr, **dc_field.metadata)
+
+        field.annotation, annotation_metadata = cls._extract_metadata(dc_field.type)
+        field.metadata += annotation_metadata
+        return field
+
     @classmethod
     def _extract_metadata(cls, annotation: type[Any] | None) -> tuple[type[Any] | None, list[Any]]:
         """
diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py
index d7856614aeb..5cf02cc7768 100644
--- a/pydantic/json_schema.py
+++ b/pydantic/json_schema.py
@@ -599,6 +599,14 @@ def model_schema(self, schema: core_schema.ModelSchema) -> JsonSchemaValue:
             # TODO: Should we be setting `allowedProperties: false` if the model's ConfigDict has extra='forbid'?
         return json_schema
 
+    def dataclass_args_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue:
+        # TODO copy much of typed_dict_schema, share logic where possible
+        return {}
+
+    def dataclass_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue:
+        # TODO copy much of model_schema, share logic where possible
+        return {}
+
     def arguments_schema(self, schema: core_schema.ArgumentsSchema, prefer_positional: bool = False) -> JsonSchemaValue:
         source_class = _core_metadata.CoreMetadataHandler(schema).get_source_class()
         prefer_positional = _utils.lenient_issubclass(source_class, tuple)  # intended to catch NamedTuple
@@ -1052,7 +1060,7 @@ def schema(
     schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
 ) -> dict[str, Any]:
     instance = schema_generator(by_alias=by_alias, ref_template=ref_template)
-    definitions = instance.generate_definitions([_utils.get_model(x).__pydantic_core_schema__ for x in models])
+    definitions = instance.generate_definitions([x.__pydantic_core_schema__ for x in models])
 
     json_schema: dict[str, Any] = {}
     if definitions:
@@ -1071,7 +1079,6 @@ def model_schema(
     ref_template: str = DEFAULT_REF_TEMPLATE,
     schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
 ) -> dict[str, Any]:
-    model = _utils.get_model(model)
     return model.model_json_schema(by_alias=by_alias, ref_template=ref_template, schema_generator=schema_generator)
 
 
diff --git a/pydantic/main.py b/pydantic/main.py
index 49d2d7ae135..e4d278fa048 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -92,11 +92,11 @@ def __pydantic_post_init__(self_: Any, **kwargs: Any) -> None:
             elif 'model_post_init' in namespace:
                 namespace['__pydantic_post_init__'] = namespace['model_post_init']
 
-            validator_functions = _decorators.ValidationFunctions(bases)
-            namespace[validator_functions.model_attribute] = validator_functions
+            namespace['__pydantic_validator_functions__'] = validator_functions = _decorators.ValidationFunctions(bases)
 
-            serializer_functions = _decorators.SerializationFunctions(bases)
-            namespace[serializer_functions.model_attribute] = serializer_functions
+            namespace['__pydantic_serializer_functions__'] = serializer_functions = _decorators.SerializationFunctions(
+                bases
+            )
 
             for name, value in namespace.items():
                 found_validator = validator_functions.extract_decorator(name, value)
@@ -135,8 +135,6 @@ def hash_func(self_: Any) -> int:
             _model_construction.complete_model_class(
                 cls,
                 cls_name,
-                validator_functions,
-                serializer_functions,
                 bases,
                 types_namespace=_typing_extra.parent_frame_namespace(),
                 raise_errors=False,
@@ -544,8 +542,6 @@ def model_rebuild(
             return _model_construction.complete_model_class(
                 cls,
                 cls.__name__,
-                cls.__pydantic_validator_functions__,
-                cls.__pydantic_serializer_functions__,
                 cls.__bases__,
                 raise_errors=raise_errors,
                 types_namespace=types_namespace,
diff --git a/pydantic/types.py b/pydantic/types.py
index 57f0e131384..df455020d0e 100644
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -78,12 +78,6 @@
 from ._internal._utils import update_not_none
 from .json_schema import JsonSchemaMetadata
 
-if TYPE_CHECKING:
-    from .dataclasses import Dataclass
-    from .main import BaseModel
-
-    ModelOrDc = Type[Union[BaseModel, Dataclass]]
-
 
 @_dataclasses.dataclass
 class Strict(_fields.PydanticMetadata):
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index b2a3c0a43bf..904dcbf6e2b 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -14,7 +14,6 @@
 from pydantic import BaseModel, ConfigDict, Extra, ValidationError, validator
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_simple():
     @pydantic.dataclasses.dataclass
     class MyDataclass:
@@ -29,7 +28,6 @@ class MyDataclass:
     assert d.b == 10
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_model_name():
     @pydantic.dataclasses.dataclass
     class MyDataClass:
@@ -41,7 +39,6 @@ class MyDataClass:
     assert d.model_name == 'foo'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_value_error():
     @pydantic.dataclasses.dataclass
     class MyDataclass:
@@ -51,12 +48,17 @@ class MyDataclass:
     with pytest.raises(ValidationError) as exc_info:
         MyDataclass(1, 'wrong')
 
+    # insert_assert(exc_info.value.errors())
     assert exc_info.value.errors() == [
-        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
+        {
+            'type': 'int_parsing',
+            'loc': (1,),
+            'msg': 'Input should be a valid integer, unable to parse string as an integer',
+            'input': 'wrong',
+        }
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_frozen():
     @pydantic.dataclasses.dataclass(frozen=True)
     class MyDataclass:
@@ -69,12 +71,9 @@ class MyDataclass:
         d.a = 7
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='validate assignment')
 def test_validate_assignment():
-    class Config:
-        validate_assignment = True
-
-    @pydantic.dataclasses.dataclass(config=Config)
+    @pydantic.dataclasses.dataclass(config=dict(validate_assignment=True))
     class MyDataclass:
         a: int
 
@@ -85,7 +84,7 @@ class MyDataclass:
     assert d.a == 7
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='validate assignment')
 def test_validate_assignment_error():
     @pydantic.dataclasses.dataclass(config=dict(validate_assignment=True))
     class MyDataclass:
@@ -100,7 +99,6 @@ class MyDataclass:
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_not_validate_assignment():
     @pydantic.dataclasses.dataclass
     class MyDataclass:
@@ -113,12 +111,9 @@ class MyDataclass:
     assert d.a == '7'
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='validate assignment')
 def test_validate_assignment_value_change():
-    class Config:
-        validate_assignment = True
-
-    @pydantic.dataclasses.dataclass(config=Config, frozen=False)
+    @pydantic.dataclasses.dataclass(config=dict(validate_assignment=True), frozen=False)
     class MyDataclass:
         a: int
 
@@ -133,12 +128,8 @@ def double_a(cls, v):
     assert d.a == 6
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_validate_assignment_extra():
-    class Config:
-        validate_assignment = True
-
-    @pydantic.dataclasses.dataclass(config=Config, frozen=False)
+    @pydantic.dataclasses.dataclass(config=dict(validate_assignment=True), frozen=False)
     class MyDataclass:
         a: int
 
@@ -151,7 +142,6 @@ class MyDataclass:
     assert d.extra_field == 'bye'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init():
     post_init_called = False
 
@@ -168,7 +158,6 @@ def __post_init__(self):
     assert post_init_called
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init_validation():
     @dataclasses.dataclass
     class DC:
@@ -177,15 +166,53 @@ class DC:
         def __post_init__(self):
             self.a *= 2
 
-        def __post_init_post_parse__(self):
-            self.a += 1
-
+    assert DC(a='2').a == '22'
     PydanticDC = pydantic.dataclasses.dataclass(DC)
     assert DC(a='2').a == '22'
-    assert PydanticDC(a='2').a == 23
+    assert PydanticDC(a='2').a == 4
+
+
+def test_convert_vanilla_dc():
+    @dataclasses.dataclass
+    class DC:
+        a: int
+        b: str = dataclasses.field(init=False)
+
+        def __post_init__(self):
+            self.a *= 2
+            self.b = 'hello'
+
+    dc1 = DC(a='2')
+    assert dc1.a == '22'
+    assert dc1.b == 'hello'
+    PydanticDC = pydantic.dataclasses.dataclass(DC)
+    dc2 = DC(a='2')
+    assert dc2.a == '22'
+    assert dc2.b == 'hello'
+
+    py_dc = PydanticDC(a='2')
+    assert py_dc.a == 4
+    assert py_dc.b == 'hello'
+
+
+def test_std_dataclass_with_parent():
+    @dataclasses.dataclass
+    class DCParent:
+        a: int
+
+    @dataclasses.dataclass
+    class DC(DCParent):
+        b: int
+
+        def __post_init__(self):
+            self.a *= 2
+
+    assert dataclasses.asdict(DC(a='2', b='1')) == {'a': '22', 'b': '1'}
+    PydanticDC = pydantic.dataclasses.dataclass(DC)
+    assert dataclasses.asdict(DC(a='2', b='1')) == {'a': '22', 'b': '1'}
+    assert dataclasses.asdict(PydanticDC(a='2', b='1')) == {'a': 4, 'b': 1}
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init_inheritance_chain():
     parent_post_init_called = False
     post_init_called = False
@@ -214,44 +241,17 @@ def __post_init__(self):
     assert post_init_called
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init_post_parse():
-    post_init_post_parse_called = False
+    with pytest.warns(DeprecationWarning, match='Support for `__post_init_post_parse__` has been dropped'):
 
-    @pydantic.dataclasses.dataclass
-    class MyDataclass:
-        a: int
-
-        def __post_init_post_parse__(self):
-            nonlocal post_init_post_parse_called
-            post_init_post_parse_called = True
-
-    d = MyDataclass('1')
-    assert d.a == 1
-    assert post_init_post_parse_called
-
-
-@pytest.mark.xfail(reason='working on V2')
-def test_post_init_post_parse_types():
-    @pydantic.dataclasses.dataclass
-    class CustomType:
-        b: int
-
-    @pydantic.dataclasses.dataclass
-    class MyDataclass:
-        a: CustomType
-
-        def __post_init__(self):
-            assert type(self.a) == dict
-
-        def __post_init_post_parse__(self):
-            assert type(self.a) == CustomType
+        @pydantic.dataclasses.dataclass
+        class MyDataclass:
+            a: int
 
-    d = MyDataclass(**{'a': {'b': 1}})
-    assert d.a.b == 1
+            def __post_init_post_parse__(self):
+                pass
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init_assignment():
     from dataclasses import field
 
@@ -271,12 +271,14 @@ def __post_init__(self):
     assert c.c == 0.30000000000000004
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_inheritance():
     @pydantic.dataclasses.dataclass
     class A:
         a: str = None
 
+    a_ = A(a=b'a')
+    assert a_.a == 'a'
+
     @pydantic.dataclasses.dataclass
     class B(A):
         b: int = None
@@ -288,36 +290,33 @@ class B(A):
     with pytest.raises(ValidationError):
         B(a='a', b='b')
 
+    a_ = A(a=b'a')
+    assert a_.a == 'a'
 
-@pytest.mark.xfail(reason='working on V2')
-def test_validate_long_string_error():
-    class Config:
-        str_max_length = 3
 
-    @pydantic.dataclasses.dataclass(config=Config)
+def test_validate_long_string_error():
+    @pydantic.dataclasses.dataclass(config=dict(str_max_length=3))
     class MyDataclass:
         a: str
 
     with pytest.raises(ValidationError) as exc_info:
         MyDataclass('xxxx')
 
+    # insert_assert(exc_info.value.errors())
     assert exc_info.value.errors() == [
         {
-            'loc': ('a',),
-            'msg': 'ensure this value has at most 3 characters',
-            'type': 'value_error.any_str.max_length',
-            'ctx': {'limit_value': 3},
+            'type': 'string_too_long',
+            'loc': (0,),
+            'msg': 'String should have at most 3 characters',
+            'input': 'xxxx',
+            'ctx': {'max_length': 3},
         }
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='validate_assignment')
 def test_validate_assigment_long_string_error():
-    class Config:
-        str_max_length = 3
-        validate_assignment = True
-
-    @pydantic.dataclasses.dataclass(config=Config)
+    @pydantic.dataclasses.dataclass(config=dict(str_max_length=3, validate_assignment=True))
     class MyDataclass:
         a: str
 
@@ -336,13 +335,8 @@ class MyDataclass:
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_no_validate_assigment_long_string_error():
-    class Config:
-        str_max_length = 3
-        validate_assignment = False
-
-    @pydantic.dataclasses.dataclass(config=Config)
+    @pydantic.dataclasses.dataclass(config=dict(str_max_length=3, validate_assignment=False))
     class MyDataclass:
         a: str
 
@@ -352,7 +346,6 @@ class MyDataclass:
     assert d.a == 'xxxx'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_nested_dataclass():
     @pydantic.dataclasses.dataclass
     class Nested:
@@ -366,42 +359,41 @@ class Outer:
     assert isinstance(navbar.n, Nested)
     assert navbar.n.number == 1
 
-    navbar = Outer(n=('2',))
-    assert isinstance(navbar.n, Nested)
-    assert navbar.n.number == 2
-
     navbar = Outer(n={'number': '3'})
     assert isinstance(navbar.n, Nested)
     assert navbar.n.number == 3
 
     with pytest.raises(ValidationError) as exc_info:
         Outer(n='not nested')
+    # insert_assert(exc_info.value.errors())
     assert exc_info.value.errors() == [
         {
+            'type': 'dataclass_type',
             'loc': ('n',),
-            'msg': 'instance of Nested, tuple or dict expected',
-            'type': 'type_error.dataclass',
-            'ctx': {'class_name': 'Nested'},
+            'msg': 'Input should be a dictionary or an instance of Nested',
+            'input': 'not nested',
+            'ctx': {'dataclass_name': 'Nested'},
         }
     ]
 
     with pytest.raises(ValidationError) as exc_info:
-        Outer(n=('x',))
+        Outer(n={'number': 'x'})
     assert exc_info.value.errors() == [
-        {'loc': ('n', 'number'), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
+        {
+            'type': 'int_parsing',
+            'loc': ('n', 'number'),
+            'msg': 'Input should be a valid integer, unable to parse string as an integer',
+            'input': 'x',
+        }
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_arbitrary_types_allowed():
     class Button:
         def __init__(self, href: str):
             self.href = href
 
-    class Config:
-        arbitrary_types_allowed = True
-
-    @pydantic.dataclasses.dataclass(config=Config)
+    @pydantic.dataclasses.dataclass(config=dict(arbitrary_types_allowed=True))
     class Navbar:
         button: Button
 
@@ -411,17 +403,18 @@ class Navbar:
 
     with pytest.raises(ValidationError) as exc_info:
         Navbar(button=('b',))
+    # insert_assert(exc_info.value.errors())
     assert exc_info.value.errors() == [
         {
+            'type': 'is_instance_of',
             'loc': ('button',),
-            'msg': 'instance of Button expected',
-            'type': 'type_error.arbitrary_type',
-            'ctx': {'expected_arbitrary_type': 'Button'},
+            'msg': 'Input should be an instance of test_arbitrary_types_allowed.<locals>.Button',
+            'input': ('b',),
+            'ctx': {'class': 'test_arbitrary_types_allowed.<locals>.Button'},
         }
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_nested_dataclass_model():
     @pydantic.dataclasses.dataclass
     class Nested:
@@ -434,7 +427,6 @@ class Outer(BaseModel):
     assert navbar.n.number == 1
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_fields():
     @pydantic.dataclasses.dataclass
     class User:
@@ -443,46 +435,42 @@ class User:
         signup_ts: datetime = None
 
     user = User(id=123)
-    fields = user.__pydantic_model__.model_fields
+    fields = user.__pydantic_fields__
 
-    assert fields['id'].required is True
-    assert fields['id'].default is None
+    assert fields['id'].is_required() is True
 
-    assert fields['name'].required is False
+    assert fields['name'].is_required() is False
     assert fields['name'].default == 'John Doe'
 
-    assert fields['signup_ts'].required is False
+    assert fields['signup_ts'].is_required() is False
     assert fields['signup_ts'].default is None
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_default_factory_field():
     @pydantic.dataclasses.dataclass
     class User:
         id: int
-        aliases: Dict[str, str] = dataclasses.field(default_factory=lambda: {'John': 'Joey'})
+        other: Dict[str, str] = dataclasses.field(default_factory=lambda: {'John': 'Joey'})
 
     user = User(id=123)
-    fields = user.__pydantic_model__.model_fields
+    assert user.id == 123
+    # assert user.other == {'John': 'Joey'}
+    fields = user.__pydantic_fields__
 
-    assert fields['id'].required is True
-    assert fields['id'].default is None
+    assert fields['id'].is_required() is True
+    assert repr(fields['id'].default) == 'PydanticUndefined'
 
-    assert fields['aliases'].required is False
-    assert fields['aliases'].default_factory() == {'John': 'Joey'}
+    assert fields['other'].is_required() is False
+    assert fields['other'].default_factory() == {'John': 'Joey'}
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_default_factory_singleton_field():
     class MySingleton:
         pass
 
-    class MyConfig:
-        arbitrary_types_allowed = True
-
     MY_SINGLETON = MySingleton()
 
-    @pydantic.dataclasses.dataclass(config=MyConfig)
+    @pydantic.dataclasses.dataclass(config=dict(arbitrary_types_allowed=True))
     class Foo:
         singleton: MySingleton = dataclasses.field(default_factory=lambda: MY_SINGLETON)
 
@@ -490,7 +478,7 @@ class Foo:
     assert Foo().singleton is Foo().singleton
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - json schema')
 def test_schema():
     @pydantic.dataclasses.dataclass
     class User:
@@ -532,7 +520,7 @@ class User:
     }
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - json schema')
 def test_nested_schema():
     @pydantic.dataclasses.dataclass
     class Nested:
@@ -558,7 +546,6 @@ class Outer:
     }
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_initvar():
     InitVar = dataclasses.InitVar
 
@@ -573,7 +560,6 @@ class TestInitVar:
         tiv.y
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_derived_field_from_initvar():
     InitVar = dataclasses.InitVar
 
@@ -585,13 +571,12 @@ class DerivedWithInitVar:
         def __post_init__(self, number):
             self.plusone = number + 1
 
-    derived = DerivedWithInitVar(1)
+    derived = DerivedWithInitVar('1')
     assert derived.plusone == 2
-    with pytest.raises(TypeError):
+    with pytest.raises(ValidationError, match='Input should be a valid integer, unable to parse string as an integer'):
         DerivedWithInitVar('Not A Number')
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_initvars_post_init():
     @pydantic.dataclasses.dataclass
     class PathDataPostInit:
@@ -607,43 +592,10 @@ def __post_init__(self, base_path):
     assert 'base_path' not in path_data.__dict__
     assert path_data.path == Path('world')
 
-    with pytest.raises(TypeError) as exc_info:
-        PathDataPostInit('world', base_path='/hello')
-    assert str(exc_info.value) == "unsupported operand type(s) for /: 'str' and 'str'"
-
-
-@pytest.mark.xfail(reason='working on V2')
-def test_initvars_post_init_post_parse():
-    @pydantic.dataclasses.dataclass
-    class PathDataPostInitPostParse:
-        path: Path
-        base_path: dataclasses.InitVar[Optional[Path]] = None
-
-        def __post_init_post_parse__(self, base_path):
-            if base_path is not None:
-                self.path = base_path / self.path
-
-    path_data = PathDataPostInitPostParse('world')
-    assert 'path' in path_data.__dict__
-    assert 'base_path' not in path_data.__dict__
-    assert path_data.path == Path('world')
-
-    assert PathDataPostInitPostParse('world', base_path='/hello').path == Path('/hello/world')
+    p = PathDataPostInit('world', base_path='/hello')
+    assert p.path == Path('/hello/world')
 
 
-@pytest.mark.xfail(reason='working on V2')
-def test_post_init_post_parse_without_initvars():
-    @pydantic.dataclasses.dataclass
-    class Foo:
-        a: int
-
-        def __post_init_post_parse__(self):
-            ...
-
-    Foo(a=1)
-
-
-@pytest.mark.xfail(reason='working on V2')
 def test_classvar():
     @pydantic.dataclasses.dataclass
     class TestClassVar:
@@ -654,7 +606,6 @@ class TestClassVar:
     assert tcv.klassvar == "I'm a Class variable"
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_frozenset_field():
     @pydantic.dataclasses.dataclass
     class TestFrozenSet:
@@ -666,7 +617,6 @@ class TestFrozenSet:
     assert object_under_test.set == test_set
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_inheritance_post_init():
     post_init_called = False
 
@@ -686,7 +636,7 @@ class Child(Base):
     assert post_init_called
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - support hashable')
 def test_hashable_required():
     @pydantic.dataclasses.dataclass
     class MyDataclass:
@@ -703,7 +653,7 @@ class MyDataclass:
     assert "__init__() missing 1 required positional argument: 'v'" in str(exc_info.value)
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - support hashable')
 @pytest.mark.parametrize('default', [1, None, ...])
 def test_hashable_optional(default):
     @pydantic.dataclasses.dataclass
@@ -714,7 +664,6 @@ class MyDataclass:
     MyDataclass(v=None)
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_override_builtin_dataclass():
     @dataclasses.dataclass
     class File:
@@ -739,10 +688,17 @@ class File:
 
     with pytest.raises(ValidationError) as e:
         ValidFile(hash=[1], name='name', size=3)
-    assert e.value.errors() == [{'loc': ('hash',), 'msg': 'str type expected', 'type': 'type_error.str'}]
+
+    assert e.value.errors() == [
+        {
+            'type': 'string_type',
+            'loc': ('hash',),
+            'msg': 'Input should be a valid string',
+            'input': [1],
+        },
+    ]
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_override_builtin_dataclass_2():
     @dataclasses.dataclass
     class Meta:
@@ -764,7 +720,7 @@ class File(Meta):
     assert f.seen_count == 7
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='TODO we need to optionally run validation even on exact types')
 def test_override_builtin_dataclass_nested():
     @dataclasses.dataclass
     class Meta:
@@ -839,7 +795,6 @@ class File:
     }
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_inherit_builtin_dataclass():
     @dataclasses.dataclass
     class Z:
@@ -879,7 +834,6 @@ class TestModel(BaseModel):
     TestModel(a=ArbitraryType(), b=(ArbitraryType(), [ArbitraryType()]))
 
 
-@pytest.mark.xfail(reason='working on V2', strict=False)
 def test_forward_stdlib_dataclass_params():
     @dataclasses.dataclass(frozen=True)
     class Item:
@@ -897,7 +851,6 @@ class Example(BaseModel):
         e.item.name = 'pika2'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_pydantic_callable_field():
     """pydantic callable fields behaviour should be the same as stdlib dataclass"""
 
@@ -1056,7 +1009,6 @@ def test_issue_2162(foo, bar):
     assert foo == bar.c
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_issue_2383():
     @dataclasses.dataclass
     class A:
@@ -1075,7 +1027,6 @@ class B(pydantic.BaseModel):
     assert hash(b.a) == 123
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_issue_2398():
     @dataclasses.dataclass(order=True)
     class DC:
@@ -1093,7 +1044,6 @@ class Model(pydantic.BaseModel):
     assert real_dc <= model.dc
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_issue_2424():
     @dataclasses.dataclass
     class Base:
@@ -1159,9 +1109,9 @@ class M(pydantic.BaseModel):
         s: Sentence
 
     assert M.model_json_schema()
+    pytest.fail('TODO check the schema is actually right')
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_issue_2594():
     @dataclasses.dataclass
     class Empty:
@@ -1210,8 +1160,9 @@ class B:
     assert A.__pydantic_model__.model_json_schema()['description'] == 'my description'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_issue_3011():
+    """Validation of a subclass of a dataclass"""
+
     @dataclasses.dataclass
     class A:
         thing_a: str
@@ -1219,10 +1170,7 @@ class A:
     class B(A):
         thing_b: str
 
-    class Config:
-        arbitrary_types_allowed = True
-
-    @pydantic.dataclasses.dataclass(config=Config)
+    @pydantic.dataclasses.dataclass
     class C:
         thing: A
 
@@ -1302,7 +1250,6 @@ class Top:
     }
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_post_init_after_validation():
     @dataclasses.dataclass
     class SetWrapper:
@@ -1313,12 +1260,12 @@ def __post_init__(self):
                 self.set, set
             ), f"self.set should be a set but it's {self.set!r} of type {type(self.set).__name__}"
 
-    class Model(pydantic.BaseModel, post_init_call='after_validation'):
+    class Model(pydantic.BaseModel):
         set_wrapper: SetWrapper
 
     model = Model(set_wrapper=SetWrapper({1, 2, 3}))
     json_text = model.model_dump_json()
-    assert Model.parse_raw(json_text) == model
+    assert Model.model_validate_json(json_text).model_dump() == model.model_dump()
 
 
 @pytest.mark.xfail(reason='working on V2')
@@ -1427,7 +1374,6 @@ class MyDataclass:
     assert MyDataclass.__pydantic_model__.model_fields['self_reference'].type_ is MyDataclass
 
 
-@pytest.mark.xfail(reason='working on V2')
 @pytest.mark.skipif(sys.version_info < (3, 10), reason='kw_only is not available in python < 3.10')
 def test_kw_only():
     @pydantic.dataclasses.dataclass(kw_only=True)
@@ -1435,13 +1381,12 @@ class A:
         a: int | None = None
         b: str
 
-    with pytest.raises(TypeError, match='takes 1 positional argument but 3 were given'):
+    with pytest.raises(ValidationError):
         A(1, '')
 
     assert A(b='hi').b == 'hi'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_extra_forbid_list_no_error():
     @pydantic.dataclasses.dataclass(config=dict(extra=Extra.forbid))
     class Bar:
@@ -1454,58 +1399,52 @@ class Foo:
     assert isinstance(Foo(a=[Bar()]).a[0], Bar)
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_extra_forbid_list_error():
-    @pydantic.dataclasses.dataclass(config=dict(extra=Extra.forbid))
+    @pydantic.dataclasses.dataclass
     class Bar:
         ...
 
-    with pytest.raises(TypeError, match=re.escape("__init__() got an unexpected keyword argument 'a'")):
+    with pytest.raises(ValidationError, match=r'a\s+Unexpected keyword argument'):
+        Bar(a=1)
 
-        @pydantic.dataclasses.dataclass
-        class Foo:
-            a: List[Bar(a=1)]
 
+def test_validator():
+    @pydantic.dataclasses.dataclass
+    class MyDataclass:
+        a: int
+        b: float
 
-@pytest.mark.xfail(reason='working on V2')
+        @validator('b')
+        def double_b(cls, v, _):
+            return v * 2
+
+    d = MyDataclass('1', '2.5')
+    assert d.a == 1
+    assert d.b == 5.0
+
+
+@pytest.mark.xfail(reason='working on V2 - validator in child not applied')
 def test_parent_post_init():
     @dataclasses.dataclass
     class A:
-        a: float = 1
+        a: float
 
         def __post_init__(self):
             self.a *= 2
 
-    @pydantic.dataclasses.dataclass
-    class B(A):
-        @validator('a')
-        def validate_a(cls, value):
-            value += 3
-            return value
-
-    assert B().a == 5  # 1 * 2 + 3
-
-
-@pytest.mark.xfail(reason='working on V2')
-def test_subclass_post_init_post_parse():
-    @dataclasses.dataclass
-    class A:
-        a: float = 1
+    assert A(a=1.2).a == 2.4
 
     @pydantic.dataclasses.dataclass
     class B(A):
-        def __post_init_post_parse__(self):
-            self.a *= 2
-
         @validator('a')
-        def validate_a(cls, value):
+        def validate_a(cls, value, _):
             value += 3
             return value
 
-    assert B().a == 8  # (1 + 3) * 2
+    assert B(a=1).a == 5  # 1 * 2 + 3
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - validator in child not applied')
 def test_subclass_post_init():
     @dataclasses.dataclass
     class A:
@@ -1524,7 +1463,7 @@ def validate_a(cls, value):
     assert B().a == 5  # 1 * 2 + 3
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='working on V2 - validator in child not applied')
 def test_subclass_post_init_inheritance():
     @dataclasses.dataclass
     class A:
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index e0dfeb2a2e2..7d9336b513e 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -79,7 +79,12 @@ class Model(BaseModel):
     # insert_assert(exc_info.value.errors())
     assert exc_info.value.errors() == [
         {'type': 'int_type', 'loc': ('v', 'int'), 'msg': 'Input should be a valid integer', 'input': None},
-        {'type': 'string_type', 'loc': ('v', 'str'), 'msg': 'Input should be a valid string', 'input': None},
+        {
+            'type': 'string_type',
+            'loc': ('v', 'str'),
+            'msg': 'Input should be a valid string',
+            'input': None,
+        },
     ]
 
 
diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py
index 9c54fe38730..8aad8dac804 100644
--- a/tests/test_forward_ref.py
+++ b/tests/test_forward_ref.py
@@ -3,7 +3,6 @@
 from typing import Optional, Tuple
 
 import pytest
-from dirty_equals import HasRepr
 
 from pydantic import BaseModel, PydanticUserError, ValidationError
 
@@ -58,8 +57,8 @@ class Bar(BaseModel):
     b = module.Bar(b={'a': {'b': {}}})
     assert b.model_dump() == {'b': {'a': {'b': {'a': None}}}}
 
-    # __field__ is complete on Foo
-    assert module.Foo.model_fields['a'] == HasRepr(
+    # model_fields is complete on Foo
+    assert repr(module.Foo.model_fields['a']) == (
         'FieldInfo(annotation=Union[PydanticForwardRef, NoneType], required=False)'
     )
 
@@ -507,7 +506,6 @@ def module():
         assert instance.sub.model_dump() == {'foo': 'bar'}
 
 
-@pytest.mark.xfail(reason='TODO dataclasses')
 def test_resolve_forward_ref_dataclass(create_module):
     module = create_module(
         # language=Python
diff --git a/tests/test_json.py b/tests/test_json.py
index f83c523be72..420d7e19dba 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -259,7 +259,6 @@ class Foo:
     assert '{"bar": 123, "spam": "apple pie"}' == json.dumps(f, default=pydantic_encoder)
 
 
-@pytest.mark.xfail(reason='dataclasses not implemented')
 def test_encode_pydantic_dataclass():
     @pydantic_dataclass
     class Foo:
@@ -267,7 +266,7 @@ class Foo:
         spam: str
 
     f = Foo(bar=123, spam='apple pie')
-    assert '{"bar": 123, "spam": "apple pie"}' == json.dumps(f, default=pydantic_encoder)
+    assert json.dumps(f, default=pydantic_encoder) == '{"bar": 123, "spam": "apple pie"}'
 
 
 def test_json_nested_encode_models():
diff --git a/tests/test_tools.py b/tests/test_tools.py
index 60febf3ffea..345e61f315d 100644
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -64,7 +64,6 @@ def test_parsing_model_naming():
     assert str(exc_info.value).split('\n')[0] == '1 validation error for int'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_parse_as_dataclass():
     @dataclass
     class PydanticDataclass:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index eb2284395b2..16c7e4f86a3 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -18,7 +18,6 @@
     ValueItems,
     all_identical,
     deep_update,
-    get_model,
     lenient_issubclass,
     smart_deepcopy,
     to_lower_camel,
@@ -26,7 +25,6 @@
 )
 from pydantic._internal._validators import import_string
 from pydantic.color import Color
-from pydantic.dataclasses import dataclass
 from pydantic.fields import Undefined
 
 try:
@@ -345,26 +343,6 @@ def test_undefined_copy():
     assert deepcopy(Undefined) is Undefined
 
 
-@pytest.mark.xfail(reason='not implemented')
-def test_get_model():
-    class A(BaseModel):
-        a: str
-
-    assert get_model(A) == A
-
-    @dataclass
-    class B:
-        a: str
-
-    assert get_model(B) == B.__pydantic_model__
-
-    class C:
-        pass
-
-    with pytest.raises(TypeError):
-        get_model(C)
-
-
 def test_class_attribute():
     class Foo:
         attr = ClassAttribute('attr', 'foo')
diff --git a/tests/test_validators_dataclass.py b/tests/test_validators_dataclass.py
index a948759a77f..1c3259b31d5 100755
--- a/tests/test_validators_dataclass.py
+++ b/tests/test_validators_dataclass.py
@@ -7,7 +7,6 @@
 from pydantic.dataclasses import dataclass
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_simple():
     @dataclass
     class MyDataclass:
@@ -20,13 +19,12 @@ def change_a(cls, v):
     assert MyDataclass(a='this is foobar good').a == 'this is foobar good changed'
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_validate_pre():
     @dataclass
     class MyDataclass:
         a: List[int]
 
-        @validator('a', pre=True)
+        @validator('a', mode='before')
         def check_a1(cls, v):
             v.append('123')
             return v
@@ -48,9 +46,9 @@ class MyDataclass:
         b: str
 
         @validator('a', 'b')
-        def check_a_and_b(cls, v, field, **kwargs):
+        def check_a_and_b(cls, v, info):
             if len(v) < 4:
-                raise TypeError(f'{field.alias} is too short')
+                raise TypeError(f'{info.field_name} is too short')
             return v + 'x'
 
     assert asdict(MyDataclass(a='1234', b='5678')) == {'a': '1234x', 'b': '5678x'}
@@ -63,7 +61,7 @@ def check_a_and_b(cls, v, field, **kwargs):
     ]
 
 
-@pytest.mark.xfail(reason='working on V2')
+# @pytest.mark.xfail(reason='working on V2')
 def test_classmethod():
     @dataclass
     class MyDataclass:
@@ -79,7 +77,6 @@ def check_a(cls, v):
     m.check_a('x')
 
 
-@pytest.mark.xfail(reason='working on V2')
 def test_validate_parent():
     @dataclass
     class Parent:
@@ -97,20 +94,20 @@ class Child(Parent):
     assert Child(a='this is foobar good').a == 'this is foobar good changed'
 
 
-@pytest.mark.xfail(reason='working on V2')
+@pytest.mark.xfail(reason='duplicate validators should override')
 def test_inheritance_replace():
     @dataclass
     class Parent:
         a: int
 
         @validator('a')
-        def add_to_a(cls, v):
+        def add_to_a(cls, v, **kwargs):
             return v + 1
 
     @dataclass
     class Child(Parent):
         @validator('a')
-        def add_to_a(cls, v):
+        def add_to_a(cls, v, **kwargs):
             return v + 5
 
     assert Child(a=0).a == 5
@@ -126,11 +123,11 @@ class MyDataclass:
         b: str
 
         @validator('b')
-        def repeat_b(cls, v):
+        def repeat_b(cls, v, **kwargs):
             return v * 2
 
         @root_validator
-        def root_validator(cls, values):
+        def root_validator(cls, values, **kwargs):
             root_val_values.append(values)
             if 'snap' in values.get('b', ''):
                 raise ValueError('foobar')


Code review comments:
diff_hunk: @@ -79,3 +90,162 @@ def _update_attrs(self, constraints: dict[str, Any], attrs: set[str] | None = No
             if k not in attrs:
                 raise TypeError(f'{self.__class__.__name__} has no attribute {k!r}')
             setattr(self, k, v)
+
+
+def collect_fields(  # noqa: C901
+    cls: type[Any],
+    bases: tuple[type[Any], ...],
+    types_namespace: dict[str, Any] | None,
+    *,
+    typevars_map: dict[str, Any] | None = None,
+    is_dataclass: bool = False,
+    dc_kw_only: bool | None = None,
+) -> dict[str, FieldInfo]:
+    """
+    Collect the fields of a pydantic model or dataclass, also returns the model/class ref to use in the
+    core-schema.
+
+    :param cls: BaseModel or dataclass
+    :param bases: parents of the class, generally `cls.__bases__`
+    :param types_namespace: optional extra namespace to look for types in
+    :param typevars_map: TODO ???
+    :param is_dataclass: whether the class is a dataclass, used to decide about kw_only setting
+    :param dc_kw_only: whether the whole dataclass is kw_only
+    """
+    from ..fields import FieldInfo
+
+    module_name = getattr(cls, '__module__', None)
+    global_ns: dict[str, Any] | None = None
+    if module_name:
+        try:
+            global_ns = sys.modules[module_name].__dict__
+        except KeyError:
+            # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
+            pass
+
+    # get type hints and raise a PydanticUndefinedAnnotation if any types are undefined
+    try:
+        type_hints = get_type_hints(cls, global_ns, types_namespace, include_extras=True)
+    except NameError as e:
+        try:
+            name = e.name
+        except AttributeError:
+            m = re.search(r".*'(.+?)'", str(e))
+            if m:
+                name = m.group(1)
+            else:
+                # should never happen
+                raise
+        raise PydanticUndefinedAnnotation(name) from e
+
+    # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
+    # annotations is only used for finding fields in parent classes
+    annotations = cls.__dict__.get('__annotations__', {})
+    fields: dict[str, FieldInfo] = {}
+
+    # FIXME (dataclasses) what are we doing here?
+    # cls_fields: dict[str, FieldInfo] = getattr(cls, 'model_fields', None) or getattr(cls, '__pydantic_fields__', {})
+
+    # currently just used for `init=False` dataclass fields, but could be used more
+    omitted_fields: set[str] | None = getattr(cls, '__pydantic_omitted_fields__', None)
+
+    for ann_name, ann_type in type_hints.items():
+        if ann_name.startswith('_') or is_classvar(ann_type) or (omitted_fields and ann_name in omitted_fields):
+            continue
+
+        # raise a PydanticUndefinedAnnotation if type is undefined
+        if isinstance(ann_type, ForwardRef):
+            raise PydanticUndefinedAnnotation(ann_type.__forward_arg__)
+
+        # FIXME (dataclasses) what are we doing here?
+        pydantic_generic_typevars_map = getattr(cls, '__pydantic_generic_typevars_map__', None)
path: pydantic/_internal/_fields.py
text: @user1:
It looks to me like this may be a bug (if it's still present on main), since the typevars map is applied to all fields below, meaning this is a double-apply. Generally that won't cause issues, but it could if you try to sub `T` with `List[T]`, for example.

@author:
can I let you check that once this is merged?

@user1:
This was indeed a bug, fixed in #5209, with a previously-failing test added. (This was a harder case to hit than you might expect because it behaved correctly if you fully parametrized the typevar, or if you didn't have a default value for the field.)