Repository: pydantic/pydantic

Issue:
AttributeError when using dataclass with schema function
# Bug

Please complete:
* OS: macOS
* Python version `import sys; print(sys.version)`: 3.7.4
* Pydantic version `import pydantic; print(pydantic.VERSION)`: 0.32.2, as well as master as of 7901711

Where possible please include a self contained code snippet describing your bug:

```py
from pydantic.dataclasses import dataclass
from pydantic.schema import schema

@dataclass
class Model:
    name: str

schema([Model])
```

This produces an error message like:
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jamie/.virtualenvs/tempenv-74cd74727d61/lib/python3.7/site-packages/pydantic/schema.py", line 185, in schema
    flat_models = get_flat_models_from_models(models)
  File "/Users/jamie/.virtualenvs/tempenv-74cd74727d61/lib/python3.7/site-packages/pydantic/schema.py", line 437, in get_flat_models_from_models
    flat_models |= get_flat_models_from_model(model)
  File "/Users/jamie/.virtualenvs/tempenv-74cd74727d61/lib/python3.7/site-packages/pydantic/schema.py", line 378, in get_flat_models_from_model
    fields = cast(Sequence[Field], model.__fields__.values())
AttributeError: type object 'Model' has no attribute '__fields__'
```

The same code using `BaseModel` instead of the `@dataclass` works fine.

With the `dataclass`, `Model.__pydantic_model__.schema()` also works.


Discussion:
That's not how the `schema` function is designed to work. Did you find somewhere in the docs that suggested that?

For now the solution is to use `Model.__pydantic_model__.schema()`, if you'd like to modify `schema()` to accept a dataclass, please submit a PR.
> That's not how the schema function is designed to work. Did you find somewhere in the docs that suggested that?

Ah, apologies, I must've misunderstood. I mostly just expected dataclasses to work similarly to things that inherit from `BaseModel` and don't think I read that `schema()` supports them.

I'd like to make a PR but honestly I'm not sure when I will have the time, so feel free to close if you want.
no problem. Would definitely be better if `schema()` worked with dataclasses as it does with models, so I'll leave this open and fix it when I get a chance if no one else does before.

Resolving patch:
diff --git a/pydantic/schema.py b/pydantic/schema.py
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -72,16 +72,17 @@
     literal_values,
     new_type_supertype,
 )
-from .utils import lenient_issubclass
+from .utils import get_model, lenient_issubclass
 
 if TYPE_CHECKING:
     from .main import BaseModel  # noqa: F401
+    from .dataclasses import DataclassType  # noqa: F401
 
 default_prefix = '#/definitions/'
 
 
 def schema(
-    models: Sequence[Type['BaseModel']],
+    models: Sequence[Union[Type['BaseModel'], Type['DataclassType']]],
     *,
     by_alias: bool = True,
     title: Optional[str] = None,
@@ -104,8 +105,9 @@ def schema(
     :return: dict with the JSON Schema with a ``definitions`` top-level key including the schema definitions for
       the models and sub-models passed in ``models``.
     """
+    clean_models = [get_model(model) for model in models]
     ref_prefix = ref_prefix or default_prefix
-    flat_models = get_flat_models_from_models(models)
+    flat_models = get_flat_models_from_models(clean_models)
     model_name_map = get_model_name_map(flat_models)
     definitions = {}
     output_schema: Dict[str, Any] = {}
@@ -113,7 +115,7 @@ def schema(
         output_schema['title'] = title
     if description:
         output_schema['description'] = description
-    for model in models:
+    for model in clean_models:
         m_schema, m_definitions, m_nested_models = model_process_schema(
             model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix
         )
@@ -125,7 +127,9 @@ def schema(
     return output_schema
 
 
-def model_schema(model: Type['BaseModel'], by_alias: bool = True, ref_prefix: Optional[str] = None) -> Dict[str, Any]:
+def model_schema(
+    model: Union[Type['BaseModel'], Type['DataclassType']], by_alias: bool = True, ref_prefix: Optional[str] = None
+) -> Dict[str, Any]:
     """
     Generate a JSON Schema for one model. With all the sub-models defined in the ``definitions`` top-level
     JSON key.
@@ -139,6 +143,7 @@ def model_schema(model: Type['BaseModel'], by_alias: bool = True, ref_prefix: Op
       prefix.
     :return: dict with the JSON Schema for the passed ``model``
     """
+    model = get_model(model)
     ref_prefix = ref_prefix or default_prefix
     flat_models = get_flat_models_from_model(model)
     model_name_map = get_model_name_map(flat_models)
diff --git a/pydantic/utils.py b/pydantic/utils.py
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -24,6 +24,7 @@
 if TYPE_CHECKING:
     from .main import BaseModel  # noqa: F401
     from .typing import AbstractSetIntStr, DictIntStrAny, IntStr, ReprArgs  # noqa: F401
+    from .dataclasses import DataclassType  # noqa: F401
 
 KeyType = TypeVar('KeyType')
 
@@ -114,6 +115,19 @@ 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: Union[Type['BaseModel'], Type['DataclassType']]) -> Type['BaseModel']:
+    from .main import BaseModel  # noqa: F811
+
+    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
+
+
 class PyObjectStr(str):
     """
     String class where repr doesn't include quotes. Useful with Representation when you want to return a string


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -13,6 +13,7 @@
 
 from pydantic import BaseModel, Extra, Field, ValidationError, conlist, validator
 from pydantic.color import Color
+from pydantic.dataclasses import dataclass
 from pydantic.networks import AnyUrl, EmailStr, IPvAnyAddress, IPvAnyInterface, IPvAnyNetwork, NameEmail, stricturl
 from pydantic.schema import (
     get_flat_models_from_model,
@@ -1639,3 +1640,27 @@ class MyModel(BaseModel):
         },
         'required': ['entries'],
     }
+
+
+def test_dataclass():
+    @dataclass
+    class Model:
+        a: bool
+
+    assert schema([Model]) == {
+        'definitions': {
+            'Model': {
+                'title': 'Model',
+                'type': 'object',
+                'properties': {'a': {'title': 'A', 'type': 'boolean'}},
+                'required': ['a'],
+            }
+        }
+    }
+
+    assert model_schema(Model) == {
+        'title': 'Model',
+        'type': 'object',
+        'properties': {'a': {'title': 'A', 'type': 'boolean'}},
+        'required': ['a'],
+    }
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -7,9 +7,10 @@
 
 from pydantic import BaseModel
 from pydantic.color import Color
+from pydantic.dataclasses import dataclass
 from pydantic.fields import Undefined
 from pydantic.typing import display_as_type, is_new_type, new_type_supertype
-from pydantic.utils import ValueItems, deep_update, import_string, lenient_issubclass, truncate
+from pydantic.utils import ValueItems, deep_update, get_model, import_string, lenient_issubclass, truncate
 
 try:
     import devtools
@@ -265,3 +266,22 @@ def test_deep_update_is_not_mutating():
 
 def test_undefined_repr():
     assert repr(Undefined) == 'PydanticUndefined'
+
+
+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)