panzarino/mlbgame

View on GitHub
mlbgame/game.py

Summary

Maintainability
F
4 days
Test Coverage
#!/usr/bin/env python

"""Module that is used for getting basic information about a game
such as the scoreboard and the box score.
"""

import datetime
import lxml.etree as etree

import mlbgame.data
import mlbgame.object


def scoreboard(year, month, day, home=None, away=None):
    """Return the scoreboard information for games matching the parameters
    as a dictionary.
    """
    # get data
    data = mlbgame.data.get_scoreboard(year, month, day)
    # parse data
    parsed = etree.parse(data)
    root = parsed.getroot()
    games = {}
    output = {}
    # loop through games
    for game in root:
        if game.tag == 'data':
            return []
        # get team names
        teams = game.findall('team')
        home_name = teams[0].attrib['name']
        away_name = teams[1].attrib['name']
        # check if teams match parameters
        if (home_name == home and home is not None) \
                or (away_name == away and away is not None) \
                or (away is None and home is None):
            # throw all the data into a complicated dictionary
            game_tag = game.tag
            game_data = game.find('game')
            game_id = game_data.attrib['id']
            game_league = game_data.attrib['league']
            game_status = game_data.attrib['status']
            game_start_time = game_data.attrib['start_time']
            home_team_data = teams[0].find('gameteam')
            home_team = home_name
            home_team_runs = int(home_team_data.attrib['R'])
            home_team_hits = int(home_team_data.attrib['H'])
            home_team_errors = int(home_team_data.attrib['E'])
            away_team_data = teams[1].find('gameteam')
            away_team = away_name
            away_team_runs = int(away_team_data.attrib['R'])
            away_team_hits = int(away_team_data.attrib['H'])
            away_team_errors = int(away_team_data.attrib['E'])
            # check type of game
            if game_tag == 'go_game' or game_tag == 'ig_game':
                try:
                    w_pitcher_data = game.find('w_pitcher')
                    w_pitcher = w_pitcher_data.find('pitcher').attrib['name']
                    w_pitcher_wins = int(w_pitcher_data.attrib['wins'])
                    w_pitcher_losses = int(w_pitcher_data.attrib['losses'])
                except Exception:
                    w_pitcher = ""
                    w_pitcher_wins = 0
                    w_pitcher_losses = 0
                try:
                    l_pitcher_data = game.find('l_pitcher')
                    l_pitcher = l_pitcher_data.find('pitcher').attrib['name']
                    l_pitcher_wins = int(l_pitcher_data.attrib['wins'])
                    l_pitcher_losses = int(l_pitcher_data.attrib['losses'])
                except Exception:
                    l_pitcher = ""
                    l_pitcher_wins = 0
                    l_pitcher_losses = 0
                try:
                    sv_pitcher_data = game.find('sv_pitcher')
                    sv_pitcher = sv_pitcher_data.find('pitcher').attrib['name']
                    sv_pitcher_saves = int(sv_pitcher_data.attrib['saves'])
                except Exception:
                    sv_pitcher = ""
                    sv_pitcher_saves = 0
                output = {
                    'game_id': game_id,
                    'game_tag': game_tag,
                    'game_league': game_league,
                    'game_status': game_status,
                    'game_start_time': game_start_time,
                    'home_team': home_team,
                    'home_team_runs': home_team_runs,
                    'home_team_hits': home_team_hits,
                    'home_team_errors': home_team_errors,
                    'away_team': away_team,
                    'away_team_runs': away_team_runs,
                    'away_team_hits': away_team_hits,
                    'away_team_errors': away_team_errors,
                    'w_pitcher': w_pitcher,
                    'w_pitcher_wins': w_pitcher_wins,
                    'w_pitcher_losses': w_pitcher_losses,
                    'l_pitcher': l_pitcher,
                    'l_pitcher_wins': l_pitcher_wins,
                    'l_pitcher_losses': l_pitcher_losses,
                    'sv_pitcher': sv_pitcher,
                    'sv_pitcher_saves': sv_pitcher_saves
                }
            # games that were not played
            elif game_tag == 'sg_game':
                try:
                    p_pitcher_data = game.findall('p_pitcher')
                    p_pitcher_home_data = p_pitcher_data[1] # swapped positions
                    p_pitcher_home = p_pitcher_home_data.find(
                        'pitcher').attrib['name']
                    p_pitcher_home_wins = int(p_pitcher_home_data.
                                              attrib['wins'])
                    p_pitcher_home_losses = int(p_pitcher_home_data.
                                                attrib['losses'])
                    p_pitcher_away_data = p_pitcher_data[0] # swapped positions
                    p_pitcher_away = p_pitcher_away_data.find(
                        'pitcher').attrib['name']
                    p_pitcher_away_wins = int(p_pitcher_away_data.
                                              attrib['wins'])
                    p_pitcher_away_losses = int(p_pitcher_away_data.
                                                attrib['losses'])
                except Exception:
                    p_pitcher_home = ''
                    p_pitcher_home_wins = 0
                    p_pitcher_home_losses = 0
                    p_pitcher_away = ''
                    p_pitcher_away_wins = 0
                    p_pitcher_away_losses = 0
                output = {
                    'game_id': game_id,
                    'game_tag': game_tag,
                    'game_league': game_league,
                    'game_status': game_status,
                    'game_start_time': game_start_time,
                    'home_team': home_team,
                    'home_team_runs': home_team_runs,
                    'home_team_hits': home_team_hits,
                    'home_team_errors': home_team_errors,
                    'away_team': away_team,
                    'away_team_runs': away_team_runs,
                    'away_team_hits': away_team_hits,
                    'away_team_errors': away_team_errors,
                    'p_pitcher_home': p_pitcher_home,
                    'p_pitcher_home_wins': p_pitcher_home_wins,
                    'p_pitcher_home_losses': p_pitcher_home_losses,
                    'p_pitcher_away': p_pitcher_away,
                    'p_pitcher_away_wins': p_pitcher_away_wins,
                    'p_pitcher_away_losses': p_pitcher_away_losses
                }
            # put this dictionary into the larger dictionary
            games[game_id] = output
    return games


class GameScoreboard(object):
    """Object to hold scoreboard information about a certain game.

    Properties:
        away_team
        away_team_errors
        away_team_hits
        away_team_runs
        date
        game_id
        game_league
        game_start_time
        game_status
        game_tag
        home_team
        home_team_errors
        home_team_hits
        home_team_runs
        l_pitcher
        l_pitcher_losses
        l_pitcher_wins
        l_team
        sv_pitcher
        sv_pitcher_saves
        w_pitcher
        w_pitcher_losses
        w_pitcher_wins
        w_team
    """

    def __init__(self, data):
        """Creates a `GameScoreboard` object.

        data is expected to come from the `scoreboard()` function.
        """
        # loop through data
        for x in data:
            # set information as correct data type
            try:
                setattr(self, x, int(data[x]))
            except ValueError:
                try:
                    setattr(self, x, float(data[x]))
                except ValueError:
                    # string if not number
                    setattr(self, x, str(data[x]))
        # calculate the winning team
        if self.home_team_runs > self.away_team_runs:
            self.w_team = self.home_team
            self.l_team = self.away_team
        elif self.away_team_runs > self.home_team_runs:
            self.w_team = self.away_team
            self.l_team = self.home_team
        # create a datetime object that represents the game start time
        # the object has no timezone info but should be interpreted as
        # being in the US/Eastern timezone
        year, month, day = self.game_id.split('_')[:3]
        game_start_date = "/".join([year, month, day])
        game_start_time = self.game_start_time.replace(' ', '')
        self.date = datetime.datetime.strptime(
                " ".join([game_start_date, game_start_time]),
                "%Y/%m/%d %I:%M%p")

    def nice_score(self):
        """Return a nicely formatted score of the game."""
        return ('{0.away_team} ({0.away_team_runs}) at '
                '{0.home_team} ({0.home_team_runs})').format(self)

    def __str__(self):
        return self.nice_score()


def box_score(game_id):
    """Gets the box score information for the game with matching id."""
    # get data
    data = mlbgame.data.get_box_score(game_id)
    # parse data
    parsed = etree.parse(data)
    root = parsed.getroot()
    linescore = root.find('linescore')
    result = dict()
    result['game_id'] = game_id
    # loop through innings and add them to output
    for x in linescore:
        inning = x.attrib['inning']
        home = value_to_int(x.attrib, 'home')
        away = value_to_int(x.attrib, 'away')
        result[int(inning)] = {'home': home, 'away': away}
    return result


def value_to_int(attrib, key):
    """ Massage runs in an inning to 0 if an empty string,
    or key not found. Otherwise return the value """
    val = attrib.get(key, 0)
    if isinstance(val, str):
        if val.isspace() or val == '':
            return 0
    return val


class GameBoxScore(object):
    """Object to hold the box score of a certain game.

    Properties:
        game_id
        innings:
            inning
            home
            away
    """

    def __init__(self, data):
        """Creates a `GameBoxScore` object.

        data is expected to come from the `box_score()` function.
        """
        self.game_id = data['game_id']
        data.pop('game_id', None)
        # dictionary of innings
        self.innings = []
        # loops through the innings
        for x in sorted(data):
            # Cast score for each half-inning to `int` if score is a digit
            # For reference---examples of a blank score ('') appearing:
            # 1. Team hasn't scored during the half-inning for an ongoing game
            # 2. Home team does not bat during the bottom of the 9th
            home_score = data[x]['home']
            if type(home_score) == str and home_score.isdigit():
                home_score = int(home_score)
            away_score = data[x]['away']
            if type(away_score) == str and away_score.isdigit():
                away_score = int(away_score)
            result = {
                'inning': int(x),
                'home': home_score,
                'away': away_score
            }
            self.innings.append(result)

    def __iter__(self):
        """Allows object to be iterated over."""
        for x in self.innings:
            yield x

    def __enumerate_scoreboard(self, data):
        output = ''
        for y, x in enumerate(data, start=1):
            if y >= 10:
                output += str(x) + '  '
            else:
                output += str(x) + ' '
        return output

    def print_scoreboard(self):
        """Print object as a scoreboard."""
        output = ''
        # parallel dictionaries with innings and scores
        innings = []
        away = []
        home = []
        for x in self:
            innings.append(x['inning'])
            away.append(x['away'])
            home.append(x['home'])
        # go through all the information and make a nice output
        # that looks like a scoreboard
        output += 'Inning\t'
        for x in innings:
            output += str(x) + ' '
        output += '\n'
        for x in innings:
            output += '---'
        output += '\nAway\t' + self.__enumerate_scoreboard(away)
        output += '\nHome\t' + self.__enumerate_scoreboard(home)
        return output


def overview(game_id):
    """Gets the overview information for the game with matching id."""
    output = {}
    # get data
    overview = mlbgame.data.get_overview(game_id)
    # parse data
    overview_root = etree.parse(overview).getroot()

    try:
        output = add_raw_box_score_attributes(output, game_id)
    except ValueError:
        pass

    # get overview attributes
    for x in overview_root.attrib:
        output[x] = overview_root.attrib[x]

    # Get probable starter attributes if they exist
    home_pitcher_tree = overview_root.find('home_probable_pitcher')
    if home_pitcher_tree is not None:
        output.update(build_namespaced_attributes(
            'home_probable_pitcher', home_pitcher_tree))
    else:
        output.update(build_probable_starter_defaults('home'))

    away_pitcher_tree = overview_root.find('away_probable_pitcher')
    if away_pitcher_tree is not None:
        output.update(build_namespaced_attributes(
            'away_probable_pitcher', away_pitcher_tree))
    else:
        output.update(build_probable_starter_defaults('away'))

    return output


def add_raw_box_score_attributes(output, game_id):
    # rawboxscore may not be available prior to a game
    raw_box_score = mlbgame.data.get_raw_box_score(game_id)
    try:
        raw_box_score_root = etree.parse(raw_box_score).getroot()
        # get raw box score attributes
        for attr in raw_box_score_root.attrib:
            output[attr] = raw_box_score_root.attrib[attr]
    except etree.XMLSyntaxError:
        pass
    return output


def build_namespaced_attributes(name, tree):
    output = {}
    for attr in tree.attrib:
        output[name + '_' + attr] = tree.attrib[attr]
    return output


def build_probable_starter_defaults(name):
    output = {}
    output[name + '_probable_pitcher_era'] = ''
    output[name + '_probable_pitcher_first'] = ''
    output[name + '_probable_pitcher_first_name'] = ''
    output[name + '_probable_pitcher_id'] = ''
    output[name + '_probable_pitcher_last'] = ''
    output[name + '_probable_pitcher_last_name'] = ''
    output[name + '_probable_pitcher_losses'] = ''
    output[name + '_probable_pitcher_name_display_roster'] = ''
    output[name + '_probable_pitcher_number'] = ''
    output[name + '_probable_pitcher_s_era'] = ''
    output[name + '_probable_pitcher_s_losses'] = ''
    output[name + '_probable_pitcher_s_wins'] = ''
    output[name + '_probable_pitcher_stats_season'] = ''
    output[name + '_probable_pitcher_stats_type'] = ''
    output[name + '_probable_pitcher_throwinghand'] = ''
    output[name + '_probable_pitcher_wins'] = ''
    return output


class Overview(mlbgame.object.Object):
    """Object to hold an overview of game information

    Properties:
        ampm
        attendance
        aw_lg_ampm
        away_ampm
        away_code
        away_division
        away_file_code
        away_games_back
        away_games_back_wildcard
        away_league_id
        away_loss
        away_name_abbrev
        away_preview_link
        away_probable_pitcher_era
        away_probable_pitcher_first
        away_probable_pitcher_first_name
        away_probable_pitcher_id
        away_probable_pitcher_last
        away_probable_pitcher_last_name
        away_probable_pitcher_losses
        away_probable_pitcher_name_display_roster
        away_probable_pitcher_number
        away_probable_pitcher_s_era
        away_probable_pitcher_s_losses
        away_probable_pitcher_s_wins
        away_probable_pitcher_stats_season
        away_probable_pitcher_stats_type
        away_probable_pitcher_throwinghand
        away_probable_pitcher_wins
        away_recap_link
        away_sport_code
        away_team_city
        away_team_errors
        away_team_hits
        away_team_id
        away_team_name
        away_team_runs
        away_time
        away_time_zone
        away_win
        balls
        date
        day
        double_header_sw
        elapsed_time
        first_pitch_et
        game_data_directory
        game_id
        game_nbr
        game_pk
        game_type
        gameday_link
        gameday_sw
        hm_lg_ampm
        home_ampm
        home_code
        home_division
        home_file_code
        home_games_back
        home_games_back_wildcard
        home_league_id
        home_loss
        home_name_abbrev
        home_preview_link
        home_probable_pitcher_era
        home_probable_pitcher_first
        home_probable_pitcher_first_name
        home_probable_pitcher_id
        home_probable_pitcher_last
        home_probable_pitcher_last_name
        home_probable_pitcher_losses
        home_probable_pitcher_name_display_roster
        home_probable_pitcher_number
        home_probable_pitcher_s_era
        home_probable_pitcher_s_losses
        home_probable_pitcher_s_wins
        home_probable_pitcher_stats_season
        home_probable_pitcher_stats_type
        home_probable_pitcher_throwinghand
        home_probable_pitcher_wins
        home_recap_link
        home_sport_code
        home_team_city
        home_team_errors
        home_team_hits
        home_team_id
        home_team_name
        home_team_runs
        home_time
        home_time_zone
        home_win
        id
        ind
        inning
        inning_state
        is_no_hitter
        is_perfect_game
        league
        location
        note
        official_scorer
        original_date
        outs
        photos_link
        preview
        scheduled_innings
        start_time
        status
        status_ind
        strikes
        tbd_flag
        tiebreaker_sw
        time
        time_aw_lg
        time_date
        time_date_aw_lg
        time_date_hm_lg
        time_hm_lg
        time_zone
        time_zone_aw_lg
        time_zone_hm_lg
        top_inning
        tv_station
        tz_aw_lg_gen
        tz_hm_lg_gen
        venue
        venue_id
        venue_name
        venue_w_chan_loc
        weather
        wind
        wrapup_link
    """
    pass


def players(game_id):
    """Gets player/coach/umpire information for the game with matching id."""
    # get data
    data = mlbgame.data.get_players(game_id)
    # parse data
    parsed = etree.parse(data)
    root = parsed.getroot()

    output = {}
    output['game_id'] = game_id

    # get player/coach data
    for team in root.findall('team'):
        type = team.attrib['type'] + "_team"
        # the type is either home_team or away_team
        output[type] = {}
        output[type]['players'] = []
        output[type]['coaches'] = []

        for p in team.findall('player'):
            player = {}
            for key in p.keys():
                player[key] = p.get(key)
            output[type]['players'].append(player)

        for c in team.findall('coach'):
            coach = {}
            for key in c.keys():
                coach[key] = c.get(key)
            output[type]['coaches'].append(coach)

    # get umpire data
    output['umpires'] = []
    for u in root.find('umpires').findall('umpire'):
        umpire = {}
        for key in u.keys():
            umpire[key] = u.get(key)
        output['umpires'].append(umpire)

    return output


class Players(object):
    """Object to hold player/coach/umpire information for a game.

    Properties:
        away_coaches
        away_players
        game_id
        home_coaches
        home_players
        umpires
    """

    def __init__(self, data):
        """Creates a players object that matches the corresponding info in `data`.
        `data` should be an dictionary of values.
        """
        self.game_id = data['game_id']
        self.home_players = [Player(x) for x in data['home_team']['players']]
        self.home_coaches = [Coach(x) for x in data['home_team']['coaches']]
        self.away_players = [Player(x) for x in data['away_team']['players']]
        self.away_coaches = [Coach(x) for x in data['away_team']['coaches']]
        self.umpires = [Umpire(x) for x in data['umpires']]


class Player(mlbgame.object.Object):
    """Object to hold player information

    Properties:
        avg
        bats
        boxname
        current_position
        era
        first
        hr
        id
        last
        losses
        num
        parent_team_abbrev
        parent_team_id
        position
        rbi
        rl
        status
        team_abbrev
        team_id
        wins
    """
    pass


class Coach(mlbgame.object.Object):
    """Object to hold coach information

    Properties:
        first
        id
        last
        num
        position
    """
    pass


class Umpire(mlbgame.object.Object):
    """Object to hold umpire information

    Properties:
        first
        id
        last
        name
        position
    """
    pass