From 242995beb6e856d7d2eb3892d0751ac960267adb Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Mon, 8 May 2017 19:08:16 +0100 Subject: [PATCH] simplify Union field choice --- pydantic/exceptions.py | 4 ++-- pydantic/fields.py | 14 ++++---------- pydantic/types.py | 6 +++++- tests/test_complex.py | 19 +++++++++++++++---- tests/test_types.py | 3 +++ 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pydantic/exceptions.py b/pydantic/exceptions.py index 1891ef7..4bf0a20 100644 --- a/pydantic/exceptions.py +++ b/pydantic/exceptions.py @@ -11,7 +11,7 @@ def type_json(type_: type): return str(type_) -Error = namedtuple('Error', ['exc', 'track_type', 'index']) +Error = namedtuple('Error', ['exc', 'track', 'index']) def jsonify_errors(e): @@ -20,7 +20,7 @@ def jsonify_errors(e): elif isinstance(e, Error): d = { 'error_type': e.exc.__class__.__name__, - 'track': type_json(e.track_type), + 'track': type_json(e.track), 'index': e.index, } if isinstance(e.exc, ValidationError): diff --git a/pydantic/fields.py b/pydantic/fields.py index 1497f06..75a21dd 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -175,20 +175,14 @@ class Field: return result, None def _validate_singleton(self, tracks, v, model, index=None): - result, errors = ..., [] + errors = [] for track in tracks: value, exc = track.validate(v, model, self) if exc: errors.append(Error(exc, track.type_, index)) - elif isinstance(v, track.type_): - # exact match: return immediately - return value, None else: - result = value - if result is not ...: - return result, None - else: - return v, errors[0] if len(tracks) == 1 else errors + return value, None + return v, errors[0] if len(tracks) == 1 else errors def __repr__(self): return f'' @@ -233,7 +227,7 @@ class ValidatorRoute: v = validator(v, model=model, field=field) else: v = validator(model, v) - except (ValueError, TypeError, ImportError) as e: + except (ValueError, TypeError) as e: return v, e return v, None diff --git a/pydantic/types.py b/pydantic/types.py index 74cbd35..10a1830 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -105,7 +105,11 @@ class Module: @classmethod def validate(cls, value): - return import_string(value) + try: + return import_string(value) + except ImportError as e: + # errors must be TypeError or ValueError + raise ValueError(str(e)) from e class DSN(str): diff --git a/tests/test_complex.py b/tests/test_complex.py index 70d53b0..40352b0 100644 --- a/tests/test_complex.py +++ b/tests/test_complex.py @@ -18,7 +18,7 @@ def test_str_bytes(): ) m = StrBytesModel(v=b'b') - assert m.v == b'b' + assert m.v == 'b' with pytest.raises(ValidationError) as exc_info: StrBytesModel(v=None) @@ -50,7 +50,7 @@ def test_str_bytes_none(): assert m.v == 's' m = StrBytesModel(v=b'b') - assert m.v == b'b' + assert m.v == 'b' m = StrBytesModel(v=None) assert m.v is None @@ -73,14 +73,14 @@ def test_union_int_str(): assert m.v == 123 m = Model(v='123') - assert m.v == '123' + assert m.v == 123 m = Model(v=b'foobar') assert m.v == 'foobar' # here both validators work and it's impossible to work out which value "closer" m = Model(v=12.2) - assert m.v == '12.2' + assert m.v == 12 with pytest.raises(ValidationError) as exc_info: Model(v=None) @@ -104,6 +104,17 @@ def test_union_int_str(): }""" == exc_info.value.json(2) +def test_union_priority(): + class ModelOne(BaseModel): + v: Union[int, str] = ... + + class ModelTwo(BaseModel): + v: Union[str, int] = ... + + assert ModelOne(v='123').v == 123 + assert ModelTwo(v='123').v == '123' + + def test_typed_list(): class Model(BaseModel): v: List[int] = ... diff --git a/tests/test_types.py b/tests/test_types.py index b60e4e1..0e8aa53 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -76,6 +76,9 @@ class ModuleModel(BaseModel): def test_module_import(): m = ModuleModel() assert m.module == os.path + with pytest.raises(ValidationError) as exc_info: + ModuleModel(module='foobar') + assert '"\\"foobar\\" doesn\'t look like a module path"' in exc_info.value.args[0] class CheckModel(BaseModel):