Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add game summary for NFL boxscores #287

Merged
merged 1 commit into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions sportsreference/nfl/boxscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def __init__(self, uri):
self._winning_abbr = None
self._losing_name = None
self._losing_abbr = None
self._summary = None
self._away_points = None
self._away_first_downs = None
self._away_rush_attempts = None
Expand Down Expand Up @@ -382,6 +383,51 @@ def _parse_name(self, field, boxscore):
scheme = BOXSCORE_SCHEME[field]
return pq(str(boxscore(scheme)).strip())

def _parse_summary(self, boxscore):
"""
Find the game summary including score in each quarter.

The game summary provides further information on the points scored
during each quarter, including the final score and any overtimes if
applicable. The final output will be in a dictionary with two keys,
'away' and 'home'. The value of each key will be a list for each
respective team's score by order of the quarter, with the first element
belonging to the first quarter, similar to the following:

{
'away': [0, 7, 3, 14],
'home': [7, 7, 3, 0]
}

Parameters
----------
boxscore : PyQuery object
A PyQuery object containing all of the HTML from the boxscore.

Returns
-------
dict
Returns a ``dictionary`` representing the score for each team in
each quarter of the game.
"""
team = ['away', 'home']
summary = {'away': [], 'home': []}
game_summary = boxscore(BOXSCORE_SCHEME['summary'])
for ind, team_info in enumerate(game_summary('tbody tr').items()):
# Only pull the first N-1 items as the last element is the final
# score for each team which is already stored in an attribute, and
# shouldn't be duplicated.
for quarter in list(team_info('td[class="center"]').items())[:-1]:
# The first element contains the logo and name of the teams,
# but not any score information, and should be skipped.
if quarter('div'):
continue
try:
summary[team[ind]].append(int(quarter.text()))
except ValueError:
summary[team[ind]].append(None)
return summary

def _find_boxscore_tables(self, boxscore):
"""
Find all tables with boxscore information on the page.
Expand Down Expand Up @@ -634,6 +680,10 @@ def _parse_game_data(self, uri):
value = self._parse_name(short_field, boxscore)
setattr(self, field, value)
continue
if short_field == 'summary':
value = self._parse_summary(boxscore)
setattr(self, field, value)
continue
index = 0
if short_field in BOXSCORE_ELEMENT_INDEX.keys():
index = BOXSCORE_ELEMENT_INDEX[short_field]
Expand Down Expand Up @@ -787,6 +837,21 @@ def duration(self):
"""
return self._duration

@property
def summary(self):
"""
Returns a ``dictionary`` with two keys, 'away' and 'home'. The value of
each key will be a list for each respective team's score by order of
the quarter, with the first element belonging to the first quarter,
similar to the following:

{
'away': [0, 7, 3, 14],
'home': [7, 7, 3, 0]
}
"""
return self._summary

@property
def winner(self):
"""
Expand Down
1 change: 1 addition & 0 deletions sportsreference/nfl/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
BOXSCORE_SCHEME = {
'game_info': 'div[class="scorebox_meta"]:first',
'home_name': 'a[itemprop="name"]:first',
'summary': 'table[class="linescore nohover stats_table no_freeze"]:first',
'away_name': 'a[itemprop="name"]:last',
'away_points': 'div[class="scorebox"] div[class="score"]',
'away_first_downs': 'td[data-stat="vis_stat"]',
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/boxscore/test_nfl_boxscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ def setup_method(self, *args, **kwargs):
def test_nfl_boxscore_returns_requested_boxscore(self):
for attribute, value in self.results.items():
assert getattr(self.boxscore, attribute) == value
assert getattr(self.boxscore, 'summary') == {
'away': [9, 13, 7, 12],
'home': [3, 9, 14, 7]
}

def test_invalid_url_yields_empty_class(self):
flexmock(Boxscore) \
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/test_nfl_boxscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@ def test_losing_abbr_is_away(self):

assert self.boxscore.losing_abbr == expected_name

def test_game_summary_with_no_scores_returns_none(self):
result = Boxscore(None)._parse_summary(pq(
"""<table class="linescore nohover stats_table no_freeze">
<tbody>
<tr>
<td class="center"></td>
<td class="center"></td>
</tr>
<tr>
<td class="center"></td>
<td class="center"></td>
</tr>
</tbody>
</table>"""))

assert result == {
'away': [None],
'home': [None]
}

@patch('requests.get', side_effect=mock_pyquery)
def test_invalid_url_returns_none(self, *args, **kwargs):
result = Boxscore(None)._retrieve_html_page('bad')
Expand Down