Skip to content

Commit 90950a9

Browse files
committed
Change behaviour of create_resource_from_dict to not raise Validation errors from field.to_python when full_clean is False.
This allows for invalid fields to still be loaded and allows for the full_clean validation process to generate errors but still allow for the data to be reviewed afterward.
1 parent 0b2ac17 commit 90950a9

4 files changed

Lines changed: 157 additions & 17 deletions

File tree

src/odin/resources.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ def create_resource_from_dict(
701701
be a parent(s) of any resource defined by the dict.
702702
:param full_clean: Perform a full clean as part of the creation.
703703
:param copy_dict: Use a copy of the input dictionary rather than destructively processing the input dict.
704-
:param default_to_not_provided: If an value is not supplied keep the value as NOT_PROVIDED. This is used
704+
:param default_to_not_provided: If a value is not supplied keep the value as NOT_PROVIDED. This is used
705705
to support merging an updated value.
706706
"""
707707
if not isinstance(d, dict):
@@ -732,7 +732,7 @@ def create_resource_from_dict(
732732
attrs.append(value)
733733

734734
# Stop if there are any errors
735-
if errors:
735+
if errors and full_clean:
736736
raise ValidationError(errors)
737737

738738
# Create the new instance

tests/conftest.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
import os
2-
import sys
31
import datetime
2+
from pathlib import Path
3+
import sys
4+
5+
import pytest
46

5-
HERE = os.path.abspath(os.path.dirname(__file__))
6-
SRC = os.path.normpath(os.path.join(HERE, "..", "src"))
7-
sys.path.insert(0, SRC)
7+
HERE = Path(__file__).parent
8+
SRC = HERE.parent / "src"
9+
sys.path.insert(0, SRC.as_posix())
810

911
import odin.datetimeutil
1012

1113
ARE_YOU_EXPERIENCED = datetime.date(1967, 5, 12)
1214
MWT = odin.datetimeutil.FixedTimezone(-6, "Mountain War Time")
1315
BOOM = datetime.datetime(1945, 7, 16, 5, 29, 45, 0, MWT)
16+
17+
18+
@pytest.fixture
19+
def fixture_path():
20+
return HERE / "fixtures"
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
"$": "Library",
3+
"name": "Tim's Library",
4+
"books": [
5+
{
6+
"$": "library.Book",
7+
"authors": [
8+
{
9+
"$": "Author",
10+
"name": "Iain M. Banks"
11+
}
12+
],
13+
"fiction": true,
14+
"published": ["1987-01-01T00:00:00.000Z"],
15+
"genre": "sci-fi",
16+
"num_pages": 471,
17+
"publisher": {
18+
"$": "Publisher",
19+
"name": "Macmillan"
20+
},
21+
"rrp": 19.5,
22+
"isbn": "0-333-45430-8",
23+
"title": "Consider Phlebas"
24+
},
25+
{
26+
"$": "library.Book",
27+
"authors": [
28+
{
29+
"$": "Author",
30+
"name": "Iain M. Banks"
31+
}
32+
],
33+
"fiction": true,
34+
"published": ["1988-01-01T00:00:00.000Z"],
35+
"genre": "sci-fi",
36+
"num_pages": 288,
37+
"publisher": {
38+
"$": "Publisher",
39+
"name": "Macmillan"
40+
},
41+
"rrp": 19.5,
42+
"title": "The Player of Games"
43+
},
44+
{
45+
"$": "library.Book",
46+
"authors": [
47+
{
48+
"$": "Author",
49+
"name": "Iain M. Banks"
50+
}
51+
],
52+
"fiction": true,
53+
"published": ["1990-01-01T00:00:00.000Z"],
54+
"genre": "sci-fi",
55+
"num_pages": 352,
56+
"publisher": {
57+
"$": "Publisher",
58+
"name": "Orbit"
59+
},
60+
"rrp": 19.5,
61+
"isbn": "1-85723-135-X",
62+
"title": "Use of Weapons"
63+
},
64+
{
65+
"$": "library.Book",
66+
"authors": [
67+
{
68+
"$": "Author"
69+
}
70+
],
71+
"fiction": true,
72+
"published": ["1996-01-01T00:00:00.000Z"],
73+
"genre": "sci-fi",
74+
"num_pages": 451,
75+
"publisher": {
76+
"$": "Publisher",
77+
"name": "Orbit Books"
78+
},
79+
"rrp": "nineteen dollars fifty cents",
80+
"isbn": "1-85723-394-8",
81+
"title": "Excession"
82+
},
83+
{
84+
"$": "library.Book",
85+
"authors": [
86+
{
87+
"$": "Author",
88+
"name": "Erich Gamma"
89+
},
90+
{
91+
"$": "Author",
92+
"name": "Richard Helm"
93+
},
94+
{
95+
"$": "Author",
96+
"name": "Ralph Johnson"
97+
},
98+
{
99+
"$": "Author",
100+
"name": "John Vlissides"
101+
}
102+
],
103+
"fiction": false,
104+
"published": ["1995-01-01T00:00:00.000Z"],
105+
"genre": "computers-and-tech",
106+
"num_pages": 395,
107+
"publisher": {
108+
"$": "Publisher",
109+
"name": "Addison Wesley"
110+
},
111+
"rrp": 59.99,
112+
"isbn": "0-201-63361-2",
113+
"title": "Design Patterns - Elements of Reusable Object-Orientated Software"
114+
}
115+
]
116+
}

tests/test_kitchensink.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
from .resources import *
1212

1313

14-
FIXTURE_PATH_ROOT = os.path.join(os.path.dirname(__file__), "fixtures")
15-
16-
1714
class TestKitchenSink:
1815
def test_dumps_with_valid_data(self):
1916
book = Book(
@@ -65,7 +62,9 @@ def test_dumps_with_valid_data(self):
6562
actual,
6663
)
6764

68-
def test_full_clean_invalid_data(self):
65+
def test_full_clean_invalid_data(
66+
self,
67+
):
6968
book = Book(
7069
title="Consider Phlebas",
7170
num_pages=471,
@@ -81,13 +80,31 @@ def test_full_clean_invalid_data(self):
8180
with pytest.raises(exceptions.ValidationError):
8281
library.full_clean()
8382

84-
def test_load_valid_data(self):
85-
library = json_codec.load(
86-
open(os.path.join(FIXTURE_PATH_ROOT, "book-valid.json"))
87-
)
83+
def test_load_valid_data(self, fixture_path):
84+
with (fixture_path / "book-valid.json").open() as fp:
85+
library = json_codec.load(fp)
8886

8987
assert "Consider Phlebas" == library.books[0].title
9088

91-
def test_load_invalid_data(self):
89+
def test_load_invalid_data(self, fixture_path):
90+
with pytest.raises(exceptions.ValidationError), (
91+
fixture_path / "book-invalid.json"
92+
).open() as fp:
93+
json_codec.load(fp)
94+
95+
def test_load_with_invalid_nested_data__where_full_clean_is_false(
96+
self, fixture_path
97+
):
98+
"""Load a nested data file where data in nested resources is in-valid.
99+
100+
This test is to ensure that the nested resources are still loaded, and
101+
invalid data is not removed and can be reported.
102+
"""
103+
with (fixture_path / "library-invalid-nested.json").open() as fp:
104+
library = json_codec.load(fp, full_clean=False)
105+
106+
assert library.books[1].isbn is None
107+
assert library.books[3].rrp == "nineteen dollars fifty cents"
108+
92109
with pytest.raises(exceptions.ValidationError):
93-
json_codec.load(open(os.path.join(FIXTURE_PATH_ROOT, "book-invalid.json")))
110+
library.full_clean()

0 commit comments

Comments
 (0)