Skip to content

Commit

Permalink
Support ORM objects to 'parse_obj', replace #520
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed May 29, 2019
1 parent 4263258 commit 0bd417c
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 10 deletions.
29 changes: 19 additions & 10 deletions pydantic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
Dict,
Generator,
List,
Mapping,
Optional,
Set,
Tuple,
Expand Down Expand Up @@ -313,13 +312,16 @@ def json(
)

@classmethod
def parse_obj(cls: Type['Model'], obj: Mapping[Any, Any]) -> 'Model':
def parse_obj(cls: Type['Model'], obj: Any) -> 'Model':
if not isinstance(obj, dict):
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
if hasattr(obj, '__iter__'):
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
else:
obj = cls._decompose_class(obj)
return cls(**obj)

@classmethod
Expand Down Expand Up @@ -421,14 +423,21 @@ def __get_validators__(cls) -> 'CallableGenerator':
yield cls.validate

@classmethod
def validate(cls: Type['Model'], value: Union['DictStrAny', 'Model']) -> 'Model':
def validate(cls: Type['Model'], value: Any) -> 'Model':
if isinstance(value, dict):
return cls(**value)
elif isinstance(value, cls):
return value.copy()
else:
elif hasattr(value, '__iter__'):
with change_exception(DictError, TypeError, ValueError):
return cls(**dict(value)) # type: ignore
return cls(**dict(value))
else:
with change_exception(DictError, TypeError, ValueError, AttributeError):
return cls(**cls._decompose_class(value))

@classmethod
def _decompose_class(cls, obj: Any) -> 'DictStrAny':
return obj.__dict__

@classmethod
def _get_value(cls, v: Any, by_alias: bool, skip_defaults: bool) -> Any:
Expand Down
34 changes: 34 additions & 0 deletions tests/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,3 +874,37 @@ class Spam(BaseModel):
assert Spam(c=Foo(a='123')).dict() == {'c': {'a': 123}}
with pytest.raises(ValidationError):
Spam(c=Bar(b='123'))


def test_orm_mode():
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species

class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
self.name = name
self.age = age
self.pets = pets

class Pet(BaseModel):
name: str
species: str

class Person(BaseModel):
name: str
age: float = None
pets: List[Pet]

bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
alice = PersonCls(name='Alice', age=20, pets=[bones, orion])

alice_model = Person.parse_obj(alice)

assert alice_model.dict() == {
'name': 'Alice',
'pets': [{'name': 'Bones', 'species': 'dog'}, {'name': 'Orion', 'species': 'cat'}],
'age': 20.0,
}

0 comments on commit 0bd417c

Please sign in to comment.