Repository: pydantic/pydantic

Issue:
Add support to chainable validations
<!-- Questions, Feature Requests, and Bug Reports are all welcome -->
<!-- delete as applicable: -->
# Question | Feature Request | Bug

For bugs/questions:
* OS: Ubuntu 18.04
* Python version: 3.7.3 (default, Mar 26 2019, 00:55:50)  [GCC 7.3.0]
* Pydantic version: 0.23

**Note**: I [asked this question](https://stackoverflow.com/questions/55666792/how-to-chain-validations-with-pydantic) on StackOverflow.

Now, BaseModel objects can only be constructed from dict. But it's very desirable to construct BaseModel object from another BaseModel objects.

This is my case:
I have a webhook which is used for whole protocol. All different data types are passed as json body.
```py
@app.route("/", methods=['POST'])
async def telegram_webhook(request):
    update = Update.parse_obj(request.json)
    /* do something with update */
```
I use Update model for validation (which is used are base validation):
```py
class Update(BaseModel):
    update_id: int
    message: Optional[Message]
    ...

class Message(BaseModel):
    message_id: int
    text: Optional[str]
```
But later, I want to apply additional validation to Message (so to check that it is TextMessage and to convert to it):
```py
class TextMessage(Message):
    text: str # Now is required

    @validator('text')
    def check_text_length(cls, value):
        length = len(value)
        if length > 4096:
            raise ValueError(f'text length {length} is too large')
        return value
```

So I get Message object in function and get expection:
```
def process_text_message(message):
    text_message = TextMessage.parse_obj(message)
```

How to solve this?
It would be great, if I could get entry dict, convert it to BaseModel object and process all validation / conversions / etc through BaseModel, without conversions to dict. And at the end, as last step, to convert it back to dict (for example to insert into MongoDB)



Discussion:
I've given a quick and dirty answer on SO: use `parse_obj(model.dict())`.

But the real solution is to improve `parse_obj` to cope with dict-like objects.

Something like 

```py
        if not isinstance(obj, dict):
            exc = TypeError(f'{cls.__name__} expected dict not {type(obj).__name__}')
            raise ValidationError([ErrorWrapper(exc, loc='__obj__')])
        return cls(**obj)
```

Should be replaced with

```py
try:
    cls(**dict(obj))
except (KeyError, ValueError) as e:
    raise ValidationError([ErrorWrapper(exc, loc='__obj__')]) from e
```



Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -14,6 +14,7 @@
     Dict,
     Generator,
     List,
+    Mapping,
     Optional,
     Set,
     Tuple,
@@ -315,10 +316,13 @@ def json(
         )
 
     @classmethod
-    def parse_obj(cls: Type['Model'], obj: 'DictAny') -> 'Model':
+    def parse_obj(cls: Type['Model'], obj: Mapping[Any, Any]) -> 'Model':
         if not isinstance(obj, dict):
-            exc = TypeError(f'{cls.__name__} expected dict not {type(obj).__name__}')
-            raise ValidationError([ErrorWrapper(exc, loc='__obj__')])
+            try:
+                obj = dict(obj)
+            except (TypeError, ValueError) as e:
+                exc = TypeError(f'{cls.__name__} expected dict not {type(obj).__name__}')
+                raise ValidationError([ErrorWrapper(exc, loc='__obj__')]) from e
         return cls(**obj)
 
     @classmethod


Test patch:
diff --git a/tests/test_parse.py b/tests/test_parse.py
--- a/tests/test_parse.py
+++ b/tests/test_parse.py
@@ -6,7 +6,7 @@
 
 
 class Model(BaseModel):
-    a: float = ...
+    a: float
     b: int = 10
 
 
@@ -15,7 +15,7 @@ def test_obj():
     assert str(m) == 'Model a=10.2 b=10'
 
 
-def test_fails():
+def test_parse_obj_fails():
     with pytest.raises(ValidationError) as exc_info:
         Model.parse_obj([1, 2, 3])
     assert exc_info.value.errors() == [
@@ -23,6 +23,20 @@ def test_fails():
     ]
 
 
+def test_parse_obj_submodel():
+    m = Model.parse_obj(Model(a=10.2))
+    assert m.dict() == {'a': 10.2, 'b': 10}
+
+
+def test_parse_obj_wrong_model():
+    class Foo(BaseModel):
+        c = 123
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model.parse_obj(Foo())
+    assert exc_info.value.errors() == [{'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'}]
+
+
 def test_json():
     assert Model.parse_raw('{"a": 12, "b": 8}') == Model(a=12, b=8)