Skip to content

Commit 3f901dd

Browse files
rgreinhomergify[bot]
authored andcommitted
Update logic to update a Report object (scrapd#216)
Defines the logic to update a `Report` with the attributes from another one.
1 parent 157beca commit 3f901dd

File tree

4 files changed

+73
-33
lines changed

4 files changed

+73
-33
lines changed

scrapd/core/apd.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Define the module containing the function used to scrap data from the APD website."""
22
import asyncio
3-
import datetime
43
from pathlib import Path
54
import re
65
from urllib.parse import urljoin
@@ -136,7 +135,7 @@ def parse_page(page, url, dump=False):
136135
:return: a dictionary representing a fatality.
137136
:rtype: dict
138137
"""
139-
report = model.Report(case='19-123456', date=datetime.datetime.now().date())
138+
report = model.Report(case='19-123456')
140139

141140
# Parse the twitter fields.
142141
twitter_report, twitter_err = twitter.parse(page)

scrapd/core/model.py

+32-9
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class Report(BaseModel):
6868

6969
case: str
7070
crash: int = 0
71-
date: datetime.date
71+
date: datetime.date = None
7272
fatalities: List[Fatality] = []
7373
link: str = ''
7474
latitude: float = 0.0
@@ -90,18 +90,41 @@ def compute_fatalities_age(self):
9090
# Compute the age.
9191
f.age = date_utils.compute_age(self.date, f.dob)
9292

93-
def update(self, other):
94-
"""Update a model with values from another one."""
93+
def update(self, other, strict=False):
94+
"""
95+
Update a model in place with values from another one.
96+
97+
Updates only the empty values of the `self` instance with the non-empty values of the `other` instance.
98+
99+
:param Report other: report to update with
100+
:param bool strict: strict mode
101+
"""
102+
# Do nothing if there is no other instance to update from.
95103
if not other:
96104
return
97105

98-
attrs = ['case', 'crash', 'date', 'fatalities', 'link', 'latitude', 'location', 'longitude', 'notes', 'time']
99-
for attr in attrs:
100-
# Only the required values can be overridden.
101-
if attr in ('case', 'date'):
106+
# Ensure other instance has the right type.
107+
if not isinstance(other, Report):
108+
raise TypeError(f'other instance is not of type "Report": {type(other)}')
109+
110+
# Define the list of attrs.
111+
required_attrs = ['case']
112+
attrs = ['crash', 'date', 'fatalities', 'link', 'latitude', 'location', 'longitude', 'notes', 'time']
113+
114+
# When strict...
115+
if strict:
116+
# The case number and date must be identical.
117+
if not all([getattr(self, attr) == getattr(other, attr) for attr in required_attrs]):
118+
raise ValueError(
119+
f'in strict mode the required attributes "({", ".join(required_attrs)})" must be identical')
120+
else:
121+
# Otherwise they are overridden.
122+
for attr in required_attrs:
102123
setattr(self, attr, getattr(other, attr))
103-
continue
104-
if not getattr(self, attr):
124+
125+
# Set the non-empty attributes of `other` into the empty attributes of the current instance.
126+
for attr in attrs:
127+
if not getattr(self, attr) and getattr(other, attr):
105128
setattr(self, attr, getattr(other, attr))
106129

107130
@validator('case')

tests/core/test_model.py

+36-18
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,34 @@
4747
},
4848
]
4949

50+
now = datetime.datetime.now()
5051
update_model_scenarios = [
5152
{
52-
'input_': model.Report(case='19-123456', date=datetime.datetime.now().date()),
53-
'other': model.Report(case='19-123456', date=datetime.datetime.now().date()),
54-
'expected': model.Report(case='19-123456', date=datetime.datetime.now().date()),
55-
'id': 'simple',
53+
'input_': model.Report(case='19-123456', date=now.date()),
54+
'other': model.Report(case='19-123456', date=now.date()),
55+
'expected': model.Report(case='19-123456', date=now.date()),
56+
'strict': False,
57+
'id': 'identical-nonstrict',
5658
},
5759
{
58-
'input_': model.Report(case='19-123456', date=datetime.datetime.now().date(), link='link'),
60+
'input_': model.Report(case='19-123456', date=now.date()),
61+
'other': model.Report(case='19-123456', date=now.date()),
62+
'expected': model.Report(case='19-123456', date=now.date()),
63+
'strict': True,
64+
'id': 'identical-strict',
65+
},
66+
{
67+
'input_': model.Report(case='19-123456', date=now.date(), link='link'),
5968
'other': None,
60-
'expected': model.Report(case='19-123456', date=datetime.datetime.now().date(), link='link'),
69+
'expected': model.Report(case='19-123456', date=now.date(), link='link'),
70+
'strict': False,
6171
'id': 'None',
6272
},
6373
{
64-
'input_': model.Report(case='19-123456', date=datetime.datetime.now().date(), link='link'),
65-
'other': model.Report(case='19-123456', date=datetime.datetime.now().date(), link='other link', crash=1),
66-
'expected': model.Report(case='19-123456', date=datetime.datetime.now().date(), link='link', crash=1),
74+
'input_': model.Report(case='19-123456', date=now.date(), link='link'),
75+
'other': model.Report(case='19-123456', date=now.date(), link='other link', crash=1),
76+
'expected': model.Report(case='19-123456', date=now.date(), link='link', crash=1),
77+
'strict': False,
6778
'id': 'complex',
6879
},
6980
]
@@ -145,21 +156,28 @@ def test_compute_fatalities_age(self, input_, expected):
145156
assert [f.age for f in input_.fatalities] == expected
146157

147158
@pytest.mark.parametrize(
148-
'input_,other,expected',
149-
[pytest.param(s['input_'], s['other'], s['expected'], id=s['id']) for s in update_model_scenarios],
159+
'input_,other,strict,expected',
160+
[pytest.param(s['input_'], s['other'], s['strict'], s['expected'], id=s['id']) for s in update_model_scenarios],
150161
)
151-
def test_update_00(self, input_, other, expected):
162+
def test_update_00(self, input_, other, strict, expected):
152163
"""Ensure models can be updated."""
153164
actual = input_.copy(deep=True)
154-
actual.update(other)
165+
actual.update(other, strict)
155166
assert actual == expected
156167

157168
def test_update_01(self):
158-
"""Ensure the case field gets updated."""
159-
actual = model.Report(case='19-123456', date=datetime.datetime.now().date())
160-
other = model.Report(case='19-123456', date=datetime.datetime.now().date())
161-
actual.update(other)
162-
assert actual == other
169+
"""Ensure required fields are identical in strict mode."""
170+
actual = model.Report(case='19-123456', date=now.date())
171+
other = model.Report(case='19-654321', date=now.date())
172+
with pytest.raises(ValueError):
173+
actual.update(other, True)
174+
175+
def test_update_02(self):
176+
"""Ensure both instances are of type Report."""
177+
actual = model.Report(case='19-123456', date=now.date())
178+
other = dict(case='19-654321', date=now.date())
179+
with pytest.raises(TypeError):
180+
actual.update(other)
163181

164182
def test_invalid_case_number(self):
165183
"""Ensure the case number has a valid format."""

tests/core/test_twitter.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
'id': 'case-number-only',
3333
'page': 'traffic-fatality-2-3',
3434
'description': "Case: 19-0161105",
35-
'expected': None,
36-
'errors': 1,
35+
'expected': model.Report(case="19-0161105", crash=2),
36+
'errors': 0,
3737
},
3838
{
3939
'id': 'regular',
@@ -100,7 +100,7 @@
100100
See video of suspect vehicle here --> https://youtu.be/ezxaRW79PnI
101101
""",
102102
'expected': None,
103-
'errors': 2,
103+
'errors': 1,
104104
},
105105
{
106106
'id': 'multiple-fatalities',
@@ -142,7 +142,7 @@
142142
'page': None,
143143
'description': '',
144144
'expected': model.Report(case='19-123456', date=datetime.datetime.now().date()),
145-
'errors': 2,
145+
'errors': 1,
146146
},
147147
{
148148
'id': 'with-arrested',

0 commit comments

Comments
 (0)