Top

nflfan module

Module nflfan provides a way to connect your fantasy rosters with statistical data in nfldb in order to perform live scoring. nflfan also provides a simple web interface that can be used to watch all of your fantasy games with real time updates. The web server can be started by running:

python -m nflfan.web

Currently, only Yahoo and ESPN fantasy leagues are supported. But nflfan is extensible, and it will be easy to add more leagues in the future.

"""
Module nflfan provides a way to connect your fantasy rosters with
statistical data in nfldb in order to perform live scoring. nflfan also
provides a simple web interface that can be used to watch all of your
fantasy games with real time updates. The web server can be started by
running:

    #!bash
    python -m nflfan.web

Currently, only Yahoo and ESPN fantasy leagues are supported. But
nflfan is extensible, and it will be easy to add more leagues in the
future.
"""
from nflfan.config import builtin_providers, load_config, cache_path
from nflfan.provider import __pdoc__ as __provider_pdoc__
from nflfan.provider import player_search
from nflfan.provider import League
from nflfan.provider import Matchup, Owner, Roster, RosterPlayer
from nflfan.provider import Provider, Yahoo, ESPN
from nflfan.score import __pdoc__ as __score_pdoc__
from nflfan.score import score_details, score_roster, score_players
from nflfan.score import tag_players
from nflfan.score import ScoreSchema

__pdoc__ = {}
__pdoc__ = dict(__pdoc__, **__provider_pdoc__)
__pdoc__ = dict(__pdoc__, **__score_pdoc__)

__all__ = [
    # nflfan.config
    'builtin_providers', 'load_config', 'cache_path',

    # nflfan.provider
    'player_search',
    'League',
    'Matchup', 'Owner', 'Roster', 'RosterPlayer',
    'Provider', 'Yahoo', 'ESPN',

    # nflfan.score
    'score_details', 'score_roster', 'score_players', 'tag_players',
    'ScoreSchema',
]

Module variables

var builtin_providers

Functions

def cache_path(

)

Returns a file path to the cache directory. If a cache directory does not exist, one is created.

If there is a problem creating a cache directory, an IOError exception is raised.

def cache_path():
    """
    Returns a file path to the cache directory. If a cache directory
    does not exist, one is created.

    If there is a problem creating a cache directory, an `IOError`
    exception is raised.
    """
    for fp in _data_paths:
        if os.access(fp, os.R_OK):
            cdir = path.join(fp, 'data')
            if not os.access(cdir, os.R_OK):
                try:
                    os.mkdir(cdir)
                except IOError as e:
                    raise IOError(e + ' (please create a cache directory)')
            return cdir
    raise IOError('could not find or create a cache directory')

def load_config(

providers={'yahoo': <class 'nflfan.provider.Yahoo'>, 'espn': <class 'nflfan.provider.ESPN'>}, file_path='')

Reads and loads the configuration file containing fantasy football league information.

The return value is a dictionary mapping provider name (e.g., yahoo) to a list of leagues for that provider. Each league is guaranteed to have at least a name, season, phase and scoring attributes filled in as values that are not None. Providers also have their own specific mandatory fields:

If no configuration file can be found, then an IOError is raised.

def load_config(providers=builtin_providers, file_path=''):
    """
    Reads and loads the configuration file containing fantasy football
    league information.

    The return value is a dictionary mapping provider name (e.g.,
    `yahoo`) to a list of leagues for that provider. Each league
    is guaranteed to have at least a `name`, `season`, `phase`
    and `scoring` attributes filled in as values that are not
    `None`. Providers also have their own specific mandatory fields:

    If no configuration file can be found, then an `IOError` is raised.
    """
    def prov_leagues(d):
        return ((k, d[k]) for k in sorted(d.keys()) if isinstance(d[k], dict))

    schema = {
        'all': {
            'req': provider.Provider.conf_required,
            'opt': provider.Provider.conf_optional,
        },
    }
    for prov in providers.values():
        schema[prov.provider_name] = {
            'req': prov.conf_required, 'opt': prov.conf_optional,
        }

    raw = toml.loads(get_data('config.toml', file_path=file_path))
    scoring = merge(raw['scoring'])

    conf = {'leagues': OrderedDict()}
    for pname in sorted(raw.keys()):
        prov = raw[pname]
        if pname == 'scoring':
            continue
        if not isinstance(prov, dict):
            conf[pname] = prov
            continue

        conf['leagues'][pname] = OrderedDict()
        for lg_name, lg in prov_leagues(prov):
            lg['league_name'] = lg_name
            lg['provider_class'] = providers[pname]
            apply_schema(schema, scoring, pname, prov, lg)

            lg = provider.League(lg['season'], lg['phase'], lg['league_id'],
                                 pname, lg_name, lg['scoring'], lg)
            conf['leagues'][pname][lg_name] = lg
    return conf

A thin wrapper around nfldb.player_search that tries searching with team or position when given, but if no results are found, then this returns the results of a search with just the full name.

This allows for a slightly out-of-date database to still provide a match while also disambiguating players with the same name.

def score_details(

schema, pp, fgs=None)

Given an nfldb database connection, a ScoreSchema and a nfldb.PlayPlayer object, return a dictionary mapping the name of a score statistic to a tuple of statistic and total point value corresponding to the statistics in pp.

fgs should be a a list of nfldb.PlayPlayer, where each describes a single field goal `attempt.

def score_details(schema, pp, fgs=None):
    """
    Given an nfldb database connection, a `nflfan.ScoreSchema` and
    a `nfldb.PlayPlayer` object, return a dictionary mapping the name of
    a score statistic to a tuple of statistic and total point value
    corresponding to the statistics in `pp`.

    `fgs` should be a a list of `nfldb.PlayPlayer`, where each
    describes a *single* field goal `attempt.
    """
    fgs = fgs or []
    def add(d, cat, stat, pts):
        if pts == 0:
            return
        if cat in d:
            d[cat] = (stat + d[cat][0], pts + d[cat][1])
        else:
            d[cat] = (stat, pts)

    d = {}
    for cat, v in _pp_stats(pp, lambda cat: not _is_defense_stat(cat)):
        add(d, cat, v, v * schema.settings.get(cat, 0.0))
    for field, pts, start, end in schema._bonuses():
        v = getattr(pp, field, 0.0)
        if start <= v <= end:
            add(d, field, v, pts)
    for pp in fgs:
        for cat, v in _pp_stats(pp, lambda cat: cat.startswith('kicking_fg')):
            if cat in ('kicking_fgm_yds', 'kicking_fgmissed_yds'):
                prefix = re.sub('_yds$', '', cat)
                scat = schema._pick_range_setting(prefix, v)
                if scat is not None:
                    add(d, scat, 1, schema.settings.get(scat, 0.0))
    return d

def score_players(

db, schema, players, phase=<season_phase.Regular: 2>)

Given a database connection, a ScoreSchema, and a list of RosterPlayer, return a list of new RosterPlayer objects with the playing and points attributes set.

phase may be set to a different phase of the season, but traditional fantasy sports are only played during the regular season, which is the default.

N.B. players is a list because of performance reasons. Namely, scoring can use fewer SQL queries when given a collection of players to score as opposed to a single player.

def score_players(db, schema, players, phase=nfldb.Enums.season_phase.Regular):
    """
    Given a database connection, a `nflfan.ScoreSchema`, and a list of
    `nflfan.RosterPlayer`, return a list of new `nflfan.RosterPlayer`
    objects with the `playing` and `points` attributes set.

    `phase` may be set to a different phase of the season, but
    traditional fantasy sports are only played during the regular
    season, which is the default.

    N.B. `players` is a list because of performance reasons. Namely,
    scoring can use fewer SQL queries when given a collection of players
    to score as opposed to a single player.
    """
    if len(players) == 0:
        return []
    season, week = players[0].season, players[0].week

    def player_query(ids):
        return _game_query(db, players[0], phase=phase).player(player_id=ids)

    def week_games():
        q = _game_query(db, players[0], phase=phase)

        games = {}
        for game in q.as_games():
            games[game.home_team] = game
            games[game.away_team] = game
        return games

    def tag(games, pps, fgs, rp):
        if rp.is_empty:
            return rp

        game = games.get(rp.team, None)
        if rp.is_defense:
            pts = _score_defense_team(schema, db, game, rp, phase)
        else:
            pp = pps.get(rp.player_id, None)
            pp_fgs = fgs.get(rp.player_id, [])
            pts = _score_player(schema, pp, pp_fgs)
        return rp._replace(game=game, points=pts)

    games = week_games()
    pids = [p.player_id for p in players if p.is_player]
    fgs = _pp_field_goals(db, players, phase=phase)
    pps = dict([(p.player_id, p) for p in player_query(pids).as_aggregate()])
    return map(functools.partial(tag, games, pps, fgs), players)

def score_roster(

db, schema, roster, phase=<season_phase.Regular: 2>)

Given a database connection, a ScoreSchema, and a Roster, return a new Roster with RosterPlayer objects with the playing and points attributes set.

phase may be set to a different phase of the season, but traditional fantasy sports are only played during the regular season, which is the default.

Each RosterPlayer in the roster given will also have the player attribute set to its corresponding nfldb.Player object. (Except for roster players corresponding to an entire team, e.g., a defense.)

def score_roster(db, schema, roster, phase=nfldb.Enums.season_phase.Regular):
    """
    Given a database connection, a `nflfan.ScoreSchema`, and
    a `nflfan.Roster`, return a new `nflfan.Roster` with
    `nflfan.RosterPlayer` objects with the `playing` and `points`
    attributes set.

    `phase` may be set to a different phase of the season, but
    traditional fantasy sports are only played during the regular
    season, which is the default.

    Each `nflfan.RosterPlayer` in the roster given will also have the
    `nflfan.RosterPlayer.player` attribute set to its corresponding
    `nfldb.Player` object. (Except for roster players corresponding to
    an entire team, e.g., a defense.)
    """
    tagged = tag_players(db, roster.players)
    scored = score_players(db, schema, tagged, phase=phase)
    return roster._replace(players=scored)

def tag_players(

db, players)

Given a list of RosterPlayer objects, set the player attribute to its corresponding nfldb.Player object. (Except for roster players corresponding to an entire team, e.g., a defense.)

def tag_players(db, players):
    """
    Given a list of `nflfan.RosterPlayer` objects, set the
    `nflfan.RosterPlayer.player` attribute to its corresponding
    `nfldb.Player` object. (Except for roster players corresponding to
    an entire team, e.g., a defense.)
    """
    ids = [p.player_id for p in players if p.is_player]
    q = nfldb.Query(db).player(player_id=ids)
    dbps = dict([(p.player_id, p) for p in q.as_players()])
    return [p._replace(player=dbps.get(p.player_id, None)) for p in players]

Classes

class ESPN

class ESPN (Provider):
    provider_name = 'espn'
    conf_required = []
    conf_optional = ['username', 'password']
    _headers = {'User-Agent': _user_agent}
    _login_url = 'http://games.espn.go.com/ffl/signin?_=_'

    def owners(self):
        url = _urls['espn']['owner'].format(
            league_id=self._lg.ident, season_id=self._lg.season)
        soup = BeautifulSoup(self._request(url).text)
        owners = []
        for td in soup.select('tr.ownerRow td.teamName'):
            ident = self._owner_id_from_url(td.a['href'])
            owners.append(Owner(ident, td.text.strip()))
        return owners

    def matchups(self, week):
        owner_id = self._owner_id_from_url

        url = _urls['espn']['matchup'].format(
            league_id=self._lg.ident, season_id=self._lg.season, week=week)
        soup = BeautifulSoup(self._request(url).text)
        matchupDiv = soup.find(id='scoreboardMatchups')
        matchups = []
        for table in matchupDiv.select('table.matchup'):
            t1, t2 = list(table.find_all(class_='name'))
            id1, id2 = owner_id(t1.a['href']), owner_id(t2.a['href'])
            name1, name2 = t1.a.text.strip(), t2.a.text.strip()
            o1, o2 = Owner(id1, name1), Owner(id2, name2)

            matchups.append(Matchup(o1, o2))
        return matchups

    def roster(self, player_search, owner, week):
        def to_pos(row):
            pos = row.find(class_='playerSlot').text.strip().upper()
            if pos == 'BENCH':
                return 'BN'
            return pos

        def to_name(row):
            name = row.find(class_='playertablePlayerName').a.text.strip()

            # If this is the defense, apparently 'D/ST' is included in
            # the name. Wtf?
            return re.sub('\s+D/ST$', '', name)

        def to_team(row):
            tpos = row.find(class_='playertablePlayerName').a.next_sibling
            tpos = tpos.strip(' \r\n\t*,|').upper()

            # This is a little weird because the team name seems to run
            # in with the position. Perhaps a weird encoding quirk?
            if len(tpos) < 2:
                return 'UNK'
            elif len(tpos) == 2:
                return nfldb.standard_team(tpos)
            else:
                team = nfldb.standard_team(tpos[0:3])
                if team == 'UNK':
                    team = nfldb.standard_team(tpos[0:2])
                return team

        def rplayer(r, name, team, pos):
            bench = pos == 'BN'
            name_team = nfldb.standard_team(name)
            if name is None and team is None:
                return r.new_player(pos, None, bench, None)
            elif name_team != 'UNK':
                return r.new_player(pos, name_team, bench, None)
            else:
                player = player_search(name, team=team, position=pos)
                return r.new_player(pos, team, bench, player.player_id)

        url = _urls['espn']['roster'].format(
            league_id=self._lg.ident, season_id=self._lg.season, week=week,
            team_id=owner.ident)
        soup = BeautifulSoup(self._request(url).text)

        roster = Roster(owner, self._lg.season, week, [])
        for tr in soup.select('tr.pncPlayerRow'):
            if tr.get('id', '') == 'pncEmptyRow':
                continue
            pos = to_pos(tr)
            try:
                team, name = to_team(tr), to_name(tr)
                roster.players.append(rplayer(roster, name, team, pos))
            except AttributeError:
                roster.players.append(rplayer(roster, None, None, pos))
        return roster

    def _owner_id_from_url(self, url):
        return re.search('teamId=([0-9]+)', url).group(1)

    def _login(self):
        soup = super(ESPN, self)._login()
        if self._login_form(soup):
            err_msg = []
            for msg in soup.find_all('font', color='#ff0000'):
                err_msg.append(msg.text.strip())
            err_msg = '\n'.join(err_msg) if err_msg else 'Unknown error.'
            raise IOError('Login failed: %s' % err_msg)

    def _login_params(self):
        return {
            'username': self._lg.conf.get('username', ''),
            'password': self._lg.conf.get('password', ''),
            'submit': 'Sign In',
        }

    def _login_form(self, soup):
        return soup.find('form', attrs={'name': 'loginForm'})

Ancestors (in MRO)

Class variables

var conf_optional

Inheritance: Provider.conf_optional

A list of fields that are optional for every provider.

var conf_required

Inheritance: Provider.conf_required

A list of fields required for every provider.

var provider_name

Inheritance: Provider.provider_name

The name of the provider used in the configuration file.

Methods

def __init__(

self, lg)

Inheritance: Provider.__init__

def __init__(self, lg):
    self._lg = lg
    self._session = requests.Session()
    self._session.headers.update(getattr(self, '_headers', {}))

def matchups(

self, week)

Inheritance: Provider.matchups

Given a week number, this returns a list of Matchup objects describing the head-to-head matchups for week.

def matchups(self, week):
    owner_id = self._owner_id_from_url
    url = _urls['espn']['matchup'].format(
        league_id=self._lg.ident, season_id=self._lg.season, week=week)
    soup = BeautifulSoup(self._request(url).text)
    matchupDiv = soup.find(id='scoreboardMatchups')
    matchups = []
    for table in matchupDiv.select('table.matchup'):
        t1, t2 = list(table.find_all(class_='name'))
        id1, id2 = owner_id(t1.a['href']), owner_id(t2.a['href'])
        name1, name2 = t1.a.text.strip(), t2.a.text.strip()
        o1, o2 = Owner(id1, name1), Owner(id2, name2)
        matchups.append(Matchup(o1, o2))
    return matchups

def owners(

self)

Inheritance: Provider.owners

Returns a list of Owner objects.

def owners(self):
    url = _urls['espn']['owner'].format(
        league_id=self._lg.ident, season_id=self._lg.season)
    soup = BeautifulSoup(self._request(url).text)
    owners = []
    for td in soup.select('tr.ownerRow td.teamName'):
        ident = self._owner_id_from_url(td.a['href'])
        owners.append(Owner(ident, td.text.strip()))
    return owners

def roster(

self, player_search, owner, week)

Inheritance: Provider.roster

Given a Owner and a week number, this returns a Roster object. The Roster contains a list of nfldb.Player objects and their corresponding position on the roster.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

Note that the roster position is a string but the set of possible values is provider dependent. It is used for display purposes only.

def roster(self, player_search, owner, week):
    def to_pos(row):
        pos = row.find(class_='playerSlot').text.strip().upper()
        if pos == 'BENCH':
            return 'BN'
        return pos
    def to_name(row):
        name = row.find(class_='playertablePlayerName').a.text.strip()
        # If this is the defense, apparently 'D/ST' is included in
        # the name. Wtf?
        return re.sub('\s+D/ST$', '', name)
    def to_team(row):
        tpos = row.find(class_='playertablePlayerName').a.next_sibling
        tpos = tpos.strip(' \r\n\t*,|').upper()
        # This is a little weird because the team name seems to run
        # in with the position. Perhaps a weird encoding quirk?
        if len(tpos) < 2:
            return 'UNK'
        elif len(tpos) == 2:
            return nfldb.standard_team(tpos)
        else:
            team = nfldb.standard_team(tpos[0:3])
            if team == 'UNK':
                team = nfldb.standard_team(tpos[0:2])
            return team
    def rplayer(r, name, team, pos):
        bench = pos == 'BN'
        name_team = nfldb.standard_team(name)
        if name is None and team is None:
            return r.new_player(pos, None, bench, None)
        elif name_team != 'UNK':
            return r.new_player(pos, name_team, bench, None)
        else:
            player = player_search(name, team=team, position=pos)
            return r.new_player(pos, team, bench, player.player_id)
    url = _urls['espn']['roster'].format(
        league_id=self._lg.ident, season_id=self._lg.season, week=week,
        team_id=owner.ident)
    soup = BeautifulSoup(self._request(url).text)
    roster = Roster(owner, self._lg.season, week, [])
    for tr in soup.select('tr.pncPlayerRow'):
        if tr.get('id', '') == 'pncEmptyRow':
            continue
        pos = to_pos(tr)
        try:
            team, name = to_team(tr), to_name(tr)
            roster.players.append(rplayer(roster, name, team, pos))
        except AttributeError:
            roster.players.append(rplayer(roster, None, None, pos))
    return roster

def save(

self, fp, player_search, week)

Inheritance: Provider.save

Writes a JSON encoding of all the owners, matchups and rosters for the given week to a file at fp.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

def save(self, fp, player_search, week):
    """
    Writes a JSON encoding of all the owners, matchups and rosters
    for the given week to a file at `fp`.
    `player_search` should be a function that takes a full
    player name and returns the closest matching player as a
    `nfldb.Player` object. It should also optionally take keyword
    arguments `team` and `position` that allow for extra filtering.
    """
    d = {
        'owners': self.owners(),
        'matchups': self.matchups(week),
    }
    # I'm hoping this doesn't hurt custom providers that don't need
    # to do IO to fetch a roster.
    def roster(owner):
        return self.roster(player_search, owner, week)
    # pool = multiprocessing.pool.ThreadPool(3) 
    # d['rosters'] = pool.map(roster, d['owners']) 
    d['rosters'] = map(roster, d['owners'])
    json.dump(d, open(fp, 'w+'))

class League

class League (namedtuple('League',
                         'season phase ident prov_name name scoring conf')):
    __pdoc__['League.season'] = \
        """The year of the NFL season for this league."""

    __pdoc__['League.phase'] = \
        """The phase of the season: preseason, regular or post."""

    __pdoc__['League.ident'] = \
        """
        A unique identifier for this league. The type and format of
        this value is provider dependent.
        """

    __pdoc__['League.prov_name'] = \
        """The name of the provider for this league."""

    __pdoc__['League.name'] = \
        """The name of this league from the configuration."""

    __pdoc__['League.scoring'] = \
        """The `nflfan.ScoreSchema` for this league."""

    __pdoc__['League.conf'] = \
        """
        A dictionary of configuration settings. The keys and values in
        this dictionary are provider dependent.
        """

    def __init__(self, *args):
        super(League, self).__init__(*args)
        self._cache = {}

    @property
    def full_name(self):
        return '%s.%s' % (self.prov_name, self.name)

    def is_me(self, obj):
        if not self.conf.get('me', None):
            return False

        if isinstance(obj, Roster):
            return self.is_me(obj.owner)
        elif isinstance(obj, Matchup):
            return self.is_me(obj.owner1) or self.is_me(obj.owner2)
        else:
            return self.conf['me'].lower() in obj.name.lower()

    def me(self, objs):
        for obj in objs:
            if self.is_me(obj):
                return obj
        return None

    def owners(self, week):
        return self._cached(week, 'owners')

    def owner(self, week, ident):
        for o in self.owners(week):
            if o.ident == ident:
                return o
        return None

    def matchups(self, week):
        return self._cached(week, 'matchups')

    def matchup(self, week, ident):
        for m in self.matchups(week):
            if m.owner1.ident == ident or m.owner2.ident == ident:
                return m
        return None

    def rosters(self, week):
        return self._cached(week, 'rosters')

    def roster(self, week, ident):
        for r in self.rosters(week):
            if r.owner.ident == ident:
                return r
        return None

    def cache_path(self, week):
        return os.path.join(nflfan.config.cache_path(),
                            str(self.season), str(self.phase), str(week),
                            self.full_name + '.json')

    def _cached(self, week, key):
        if week not in self._cache:
            self._load(week)
        return self._cache[week][key]

    def _load(self, week):
        raw = None
        fp = self.cache_path(week)
        try:
            with open(fp) as f:
                raw = json.load(f)
        except IOError:
            raise IOError(
                "No cached data for week %d in %s could be found at %s\n"
                "Have you run `nflfan-update --week %d` yet?"
                % (week, self.full_name, fp, week))

        d = {'owners': [], 'matchups': [], 'rosters': []}
        for owner in raw['owners']:
            d['owners'].append(Owner._make(owner))
        for matchup in raw['matchups']:
            o1 = None if matchup[0] is None else Owner._make(matchup[0])
            o2 = None if matchup[1] is None else Owner._make(matchup[1])
            d['matchups'].append(Matchup(o1, o2))
        for roster in raw['rosters']:
            o = Owner._make(roster[0])
            r = Roster(o, roster[1], roster[2], [])
            for rp in roster[3]:
                r.players.append(RosterPlayer._make(rp))
            d['rosters'].append(r)
        self._cache[week] = d

    def __str__(self):
        return self.full_name

Ancestors (in MRO)

Instance variables

var conf

A dictionary of configuration settings. The keys and values in this dictionary are provider dependent.

var full_name

var ident

A unique identifier for this league. The type and format of this value is provider dependent.

var name

The name of this league from the configuration.

var phase

The phase of the season: preseason, regular or post.

var prov_name

The name of the provider for this league.

var scoring

The ScoreSchema for this league.

var season

The year of the NFL season for this league.

Methods

def __init__(

self, *args)

def __init__(self, *args):
    super(League, self).__init__(*args)
    self._cache = {}

def cache_path(

self, week)

def cache_path(self, week):
    return os.path.join(nflfan.config.cache_path(),
                        str(self.season), str(self.phase), str(week),
                        self.full_name + '.json')

def is_me(

self, obj)

def is_me(self, obj):
    if not self.conf.get('me', None):
        return False
    if isinstance(obj, Roster):
        return self.is_me(obj.owner)
    elif isinstance(obj, Matchup):
        return self.is_me(obj.owner1) or self.is_me(obj.owner2)
    else:
        return self.conf['me'].lower() in obj.name.lower()

def matchup(

self, week, ident)

def matchup(self, week, ident):
    for m in self.matchups(week):
        if m.owner1.ident == ident or m.owner2.ident == ident:
            return m
    return None

def matchups(

self, week)

def matchups(self, week):
    return self._cached(week, 'matchups')

def me(

self, objs)

def me(self, objs):
    for obj in objs:
        if self.is_me(obj):
            return obj
    return None

def owner(

self, week, ident)

def owner(self, week, ident):
    for o in self.owners(week):
        if o.ident == ident:
            return o
    return None

def owners(

self, week)

def owners(self, week):
    return self._cached(week, 'owners')

def roster(

self, week, ident)

def roster(self, week, ident):
    for r in self.rosters(week):
        if r.owner.ident == ident:
            return r
    return None

def rosters(

self, week)

def rosters(self, week):
    return self._cached(week, 'rosters')

class Matchup

class Matchup (namedtuple('Matchup', 'owner1 owner2')):
    __pdoc__['Matchup.owner1'] = \
        """
        One of the two teams in this matchup represented as an
        `nflfan.Owner` object.
        """

    __pdoc__['Matchup.owner2'] = \
        """
        One of the two teams in this matchup represented as an
        `nflfan.Owner` object.
        """

    def other(self, ident):
        """
        Given an identifier for one of the owner's in this matchup,
        return the `nflfan.Owner` of the other owner.
        """
        assert ident in (self.owner1.ident, self.owner2.ident)
        if ident == self.owner1.ident:
            return self.owner2
        else:
            return self.owner1

    def __str__(self):
        return '%s vs. %s' % (self.owner1, self.owner2)

Ancestors (in MRO)

Instance variables

var owner1

One of the two teams in this matchup represented as an Owner object.

var owner2

One of the two teams in this matchup represented as an Owner object.

Methods

def other(

self, ident)

Given an identifier for one of the owner's in this matchup, return the Owner of the other owner.

def other(self, ident):
    """
    Given an identifier for one of the owner's in this matchup,
    return the `nflfan.Owner` of the other owner.
    """
    assert ident in (self.owner1.ident, self.owner2.ident)
    if ident == self.owner1.ident:
        return self.owner2
    else:
        return self.owner1

class Owner

class Owner (namedtuple('Owner', 'ident name')):
    __pdoc__['Owner.ident'] = \
        """
        A unique identifier corresponding to this owner. The type
        of this value is provider-dependent.
        """

    __pdoc__['Owner.name'] = \
        """A string representing the name of this owner."""

    def __str__(self):
        return self.name

Ancestors (in MRO)

Instance variables

var ident

A unique identifier corresponding to this owner. The type of this value is provider-dependent.

var name

A string representing the name of this owner.

class Provider

This class describes the interface that each fantasy football provider must implement so that it can work with nflfan. In other words, this is an abstract base class that should not be instantiated directly.

All public members of this class must also be defined in each provider implementation, including the class variables.

class Provider (object):
    """
    This class describes the interface that each fantasy football
    provider must implement so that it can work with nflfan. In other
    words, this is an abstract base class that should **not** be
    instantiated directly.

    All public members of this class must also be defined in each
    provider implementation, including the class variables.
    """

    provider_name = None
    """The name of the provider used in the configuration file."""

    conf_required = ['scoring', 'league_name', 'season', 'phase', 'league_id']
    """A list of fields required for every provider."""

    conf_optional = ['me']
    """A list of fields that are optional for every provider."""

    def __init__(self, lg):
        self._lg = lg
        self._session = requests.Session()
        self._session.headers.update(getattr(self, '_headers', {}))

    def owners(self):
        """Returns a list of `nflfan.Owner` objects."""
        assert False, 'subclass responsibility'

    def matchups(self, week):
        """
        Given a week number, this returns a list of `nflfan.Matchup`
        objects describing the head-to-head matchups for `week`.
        """
        assert False, 'subclass responsibility'

    def roster(self, player_search, owner, week):
        """
        Given a `nflfan.Owner` and a week number, this returns a
        `nflfan.Roster` object. The `nflfan.Roster` contains a list of
        `nfldb.Player` objects and their corresponding position on the
        roster.

        `player_search` should be a function that takes a full
        player name and returns the closest matching player as a
        `nfldb.Player` object. It should also optionally take keyword
        arguments `team` and `position` that allow for extra filtering.

        Note that the roster position is a string but the set of
        possible values is provider dependent. It is used for display
        purposes only.
        """
        assert False, 'subclass responsibility'

    def save(self, fp, player_search, week):
        """
        Writes a JSON encoding of all the owners, matchups and rosters
        for the given week to a file at `fp`.

        `player_search` should be a function that takes a full
        player name and returns the closest matching player as a
        `nfldb.Player` object. It should also optionally take keyword
        arguments `team` and `position` that allow for extra filtering.
        """
        d = {
            'owners': self.owners(),
            'matchups': self.matchups(week),
        }

        # I'm hoping this doesn't hurt custom providers that don't need
        # to do IO to fetch a roster.
        def roster(owner):
            return self.roster(player_search, owner, week)

        # pool = multiprocessing.pool.ThreadPool(3) 
        # d['rosters'] = pool.map(roster, d['owners']) 
        d['rosters'] = map(roster, d['owners'])
        json.dump(d, open(fp, 'w+'))

    def _request(self, url):
        eprint('download %s' % url)
        r = self._session.get(url)
        soup = BeautifulSoup(r.text)
        if self._login_form(soup):
            self._login()

            r = self._session.get(url)
            soup = BeautifulSoup(r.text)
            if self._login_form(soup):
                raise IOError("Authentication failure.")
        return r

    def _login(self):
        assert self._login_url is not None
        soup = BeautifulSoup(self._session.get(self._login_url).text)

        if not self._login_form(soup):
            # Already logged in!
            return

        form = self._login_form(soup)
        params = self._login_params()
        for inp in form.find_all('input', type='hidden'):
            params[inp['name']] = inp['value']
        r = self._session.post(form['action'], params=params)
        return BeautifulSoup(r.text)

    def _login_params(self):
        assert False, 'subclass responsibility'

    def _login_form(self, soup):
        assert False, 'subclass responsibility'

    def __str__(self):
        return self.__class__.provider_name

Ancestors (in MRO)

Class variables

var conf_optional

A list of fields that are optional for every provider.

var conf_required

A list of fields required for every provider.

var provider_name

The name of the provider used in the configuration file.

Methods

def __init__(

self, lg)

def __init__(self, lg):
    self._lg = lg
    self._session = requests.Session()
    self._session.headers.update(getattr(self, '_headers', {}))

def matchups(

self, week)

Given a week number, this returns a list of Matchup objects describing the head-to-head matchups for week.

def matchups(self, week):
    """
    Given a week number, this returns a list of `nflfan.Matchup`
    objects describing the head-to-head matchups for `week`.
    """
    assert False, 'subclass responsibility'

def owners(

self)

Returns a list of Owner objects.

def owners(self):
    """Returns a list of `nflfan.Owner` objects."""
    assert False, 'subclass responsibility'

def roster(

self, player_search, owner, week)

Given a Owner and a week number, this returns a Roster object. The Roster contains a list of nfldb.Player objects and their corresponding position on the roster.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

Note that the roster position is a string but the set of possible values is provider dependent. It is used for display purposes only.

def roster(self, player_search, owner, week):
    """
    Given a `nflfan.Owner` and a week number, this returns a
    `nflfan.Roster` object. The `nflfan.Roster` contains a list of
    `nfldb.Player` objects and their corresponding position on the
    roster.
    `player_search` should be a function that takes a full
    player name and returns the closest matching player as a
    `nfldb.Player` object. It should also optionally take keyword
    arguments `team` and `position` that allow for extra filtering.
    Note that the roster position is a string but the set of
    possible values is provider dependent. It is used for display
    purposes only.
    """
    assert False, 'subclass responsibility'

def save(

self, fp, player_search, week)

Writes a JSON encoding of all the owners, matchups and rosters for the given week to a file at fp.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

def save(self, fp, player_search, week):
    """
    Writes a JSON encoding of all the owners, matchups and rosters
    for the given week to a file at `fp`.
    `player_search` should be a function that takes a full
    player name and returns the closest matching player as a
    `nfldb.Player` object. It should also optionally take keyword
    arguments `team` and `position` that allow for extra filtering.
    """
    d = {
        'owners': self.owners(),
        'matchups': self.matchups(week),
    }
    # I'm hoping this doesn't hurt custom providers that don't need
    # to do IO to fetch a roster.
    def roster(owner):
        return self.roster(player_search, owner, week)
    # pool = multiprocessing.pool.ThreadPool(3) 
    # d['rosters'] = pool.map(roster, d['owners']) 
    d['rosters'] = map(roster, d['owners'])
    json.dump(d, open(fp, 'w+'))

class Roster

class Roster (namedtuple('Roster', 'owner season week players')):
    __pdoc__['Roster.owner'] = \
        """
        A `nflfan.Owner` object corresponding to the owner of this
        roster.
        """

    __pdoc__['Roster.players'] = \
        """
        A list of `nflfan.RosterPlayer` objects corresponding to the
        set of players on this roster.
        """

    def new_player(self, pos, team, bench, player_id):
        """
        A convenience method for creating a new `nflfan.RosterPlayer`
        given the current roster.
        """
        return RosterPlayer(pos, team, bench, self.season, self.week,
                            None, 0.0, None, player_id)

    @property
    def active(self):
        return filter(lambda rp: not rp.bench, self.players)

    @property
    def benched(self):
        return filter(lambda rp: rp.bench, self.players)

    @property
    def points(self):
        """Returns the total number of points for non-benched players."""
        return sum(p.points for p in self.players if not p.bench)

    def __str__(self):
        s = []
        for rp in self.players:
            s.append(str(rp))
        return '\n'.join(s)

Ancestors (in MRO)

Instance variables

var active

var benched

var owner

A Owner object corresponding to the owner of this roster.

var players

A list of RosterPlayer objects corresponding to the set of players on this roster.

var points

Returns the total number of points for non-benched players.

var season

Alias for field number 1

var week

Alias for field number 2

Methods

def new_player(

self, pos, team, bench, player_id)

A convenience method for creating a new RosterPlayer given the current roster.

def new_player(self, pos, team, bench, player_id):
    """
    A convenience method for creating a new `nflfan.RosterPlayer`
    given the current roster.
    """
    return RosterPlayer(pos, team, bench, self.season, self.week,
                        None, 0.0, None, player_id)

class RosterPlayer

class RosterPlayer (
    namedtuple('RosterPlayer',
               'position team bench season week '
               'game points player player_id')):
    __pdoc__['RosterPlayer.position'] = \
        """
        A string corresponding to the position of the roster spot
        occupied by this player. The possible values of this string are
        provider dependent.
        """

    __pdoc__['RosterPlayer.team'] = \
        """
        A team abbreviation that this player belongs to. It must be a
        valid nfldb team abbreviation and *cannot* be `UNK`.
        """

    __pdoc__['RosterPlayer.bench'] = \
        """A boolean indicating whether this is a bench position or not."""

    __pdoc__['RosterPlayer.season'] = \
        """The year of the corresponding NFL season."""

    __pdoc__['RosterPlayer.week'] = \
        """The week number in which this roster was set."""

    __pdoc__['RosterPlayer.game'] = \
        """
        The `nfldb.Game` object for the game that this player played
        in. If this roster position corresponds to a bye week, then
        this attribute is set to `None`.
        """

    __pdoc__['RosterPlayer.points'] = \
        """The total fantasy points for this roster player."""

    __pdoc__['RosterPlayer.player'] = \
        """
        A `nfldb.Player` object corresponding to this roster player.

        This attribute is `None` by default, and is always `None` for
        roster players corresponding to entire teams (e.g., defense).
        """

    __pdoc__['RosterPlayer.player_id'] = \
        """
        A player id string corresponding to the player in this roster
        position and a player in nfldb. This may be `None` when the
        roster player corresponds to an entire team. (e.g., A defense.)
        """

    @property
    def is_empty(self):
        return self.team is None and self.player_id is None

    @property
    def is_defense(self):
        return self.team is not None and self.player_id is None

    @property
    def is_player(self):
        return self.player_id is not None

    @property
    def id(self):
        if self.is_empty:
            return 'Empty'
        elif self.is_defense:
            return self.team
        else:
            return self.player_id

    @property
    def name(self):
        return self.id if not self.player else self.player.full_name

    def __str__(self):
        if self.game is not None and self.game.is_playing:
            playing = '*'
        else:
            playing = ' '
        return '%-6s %-4s %-20s %s%0.2f' \
               % (self.position, self.team, self.name, playing, self.points)

Ancestors (in MRO)

Instance variables

var bench

A boolean indicating whether this is a bench position or not.

var game

The nfldb.Game object for the game that this player played in. If this roster position corresponds to a bye week, then this attribute is set to None.

var id

var is_defense

var is_empty

var is_player

var name

var player

A nfldb.Player object corresponding to this roster player.

This attribute is None by default, and is always None for roster players corresponding to entire teams (e.g., defense).

var player_id

A player id string corresponding to the player in this roster position and a player in nfldb. This may be None when the roster player corresponds to an entire team. (e.g., A defense.)

var points

The total fantasy points for this roster player.

var position

A string corresponding to the position of the roster spot occupied by this player. The possible values of this string are provider dependent.

var season

The year of the corresponding NFL season.

var team

A team abbreviation that this player belongs to. It must be a valid nfldb team abbreviation and cannot be UNK.

var week

The week number in which this roster was set.

class ScoreSchema

class ScoreSchema (namedtuple('ScoreSchema', 'name settings')):
    __pdoc__['ScoreSchema.name'] = \
        """The name given to this schema in the configuration."""

    __pdoc__['ScoreSchema.settings'] = \
        """
        A dictionary mapping a scoring category to its point value. The
        interpretation of the point value depends on the scoring
        category.
        """

    def _pick_range_setting(self, prefix, v):
        match = re.compile('%s_([0-9]+)_([0-9]+)' % prefix)
        for cat in self.settings.keys():
            m = match.match(cat)
            if not m:
                continue
            start, end = int(m.group(1)), int(m.group(2))
            if start <= v <= end:
                return cat
        return None

    def _bonuses(self):
        match = re.compile('^bonus_(.+)_([0-9]+)_([0-9]+)$')
        for cat, pts in self.settings.items():
            m = match.match(cat)
            if not m:
                continue
            field, start, end = m.group(1), int(m.group(2)), int(m.group(3))
            yield field, pts, start, end

Ancestors (in MRO)

Instance variables

var name

The name given to this schema in the configuration.

var settings

A dictionary mapping a scoring category to its point value. The interpretation of the point value depends on the scoring category.

class Yahoo

class Yahoo (Provider):
    provider_name = 'yahoo'
    conf_required = []
    conf_optional = ['username', 'password']
    _headers = {'User-Agent': _user_agent}
    _login_url = 'https://login.yahoo.com/config/login'

    def __init__(self, lg):
        super(Yahoo, self).__init__(lg)
        _, _, self._league_num = self._lg.ident.split('.')

    def owners(self):
        match_owner_link = re.compile('team-[0-9]+-name')

        url = _urls['yahoo']['owner'] % self._league_num
        soup = BeautifulSoup(self._request(url).text)
        owners = []
        for link in soup.find_all(id=match_owner_link):
            ident = self._owner_id_from_url(link['href'])
            owners.append(Owner(ident, link.text.strip()))
        return owners

    def matchups(self, week):
        mk_owner = lambda div: Owner(owner_id(div.a['href']), div.text.strip())
        owner_id = self._owner_id_from_url

        url = _urls['yahoo']['matchup'] % (self._league_num, week)
        rjson = self._request(url).json()
        soup = BeautifulSoup(rjson['content'])
        matchups = []
        for matchup in soup.find('ul').children:
            pair = list(matchup.find_all('div', class_='Fz-sm'))
            if len(pair) == 1:
                matchups.append(Matchup(mk_owner(pair[0]), None))
            else:
                matchups.append(Matchup(mk_owner(pair[0]), mk_owner(pair[1])))
        return matchups

    def roster(self, player_search, owner, week):
        def to_pos(row):
            return row.td.find(class_='pos-label')['data-pos'].strip().upper()

        def to_name(row):
            return row.find(class_='ysf-player-name').a.text.strip()

        def to_team(row):
            team_pos = row.find(class_='ysf-player-name').span.text.strip()
            return nfldb.standard_team(re.search('^\S+', team_pos).group(0))

        def rplayer(r, name, team, pos):
            bench = pos == 'BN'
            if name is None and team is None:
                return r.new_player(pos, None, bench, None)
            elif nfldb.standard_team(name) != 'UNK':
                return r.new_player(pos, team, bench, None)
            else:
                player = player_search(name, team=team, position=pos)
                return r.new_player(pos, team, bench, player.player_id)

        match_table_id = re.compile('^statTable[0-9]+$')

        url = _urls['yahoo']['roster'] % (self._league_num, owner.ident, week)
        soup = BeautifulSoup(self._request(url).text)

        roster = Roster(owner, self._lg.season, week, [])
        for table in soup.find_all(id=match_table_id):
            for row in table.tbody.find_all('tr', recursive=False):
                pos = to_pos(row)
                try:
                    team, name = to_team(row), to_name(row)
                    roster.players.append(rplayer(roster, name, team, pos))
                except AttributeError:
                    roster.players.append(rplayer(roster, None, None, pos))
        return roster

    def _owner_id_from_url(self, url):
        return re.search('%s/([0-9]+)' % self._league_num, url).group(1)

    def _login(self):
        soup = super(Yahoo, self)._login()
        if self._login_form(soup):
            err_div = soup.find('div', class_='yregertxt')
            err_msg = 'Unknown error.'
            if err_div:
                err_msg = err_div.text.strip()
            raise IOError('Login failed: %s' % err_msg)

    def _login_params(self):
        return {
            'login': self._lg.conf.get('username', ''),
            'passwd': self._lg.conf.get('password', ''),
            '.save': 'Sign In',
        }

    def _login_form(self, soup):
        return soup.find(id='login_form')

Ancestors (in MRO)

Class variables

var conf_optional

Inheritance: Provider.conf_optional

A list of fields that are optional for every provider.

var conf_required

Inheritance: Provider.conf_required

A list of fields required for every provider.

var provider_name

Inheritance: Provider.provider_name

The name of the provider used in the configuration file.

Methods

def __init__(

self, lg)

Inheritance: Provider.__init__

def __init__(self, lg):
    super(Yahoo, self).__init__(lg)
    _, _, self._league_num = self._lg.ident.split('.')

def matchups(

self, week)

Inheritance: Provider.matchups

Given a week number, this returns a list of Matchup objects describing the head-to-head matchups for week.

def matchups(self, week):
    mk_owner = lambda div: Owner(owner_id(div.a['href']), div.text.strip())
    owner_id = self._owner_id_from_url
    url = _urls['yahoo']['matchup'] % (self._league_num, week)
    rjson = self._request(url).json()
    soup = BeautifulSoup(rjson['content'])
    matchups = []
    for matchup in soup.find('ul').children:
        pair = list(matchup.find_all('div', class_='Fz-sm'))
        if len(pair) == 1:
            matchups.append(Matchup(mk_owner(pair[0]), None))
        else:
            matchups.append(Matchup(mk_owner(pair[0]), mk_owner(pair[1])))
    return matchups

def owners(

self)

Inheritance: Provider.owners

Returns a list of Owner objects.

def owners(self):
    match_owner_link = re.compile('team-[0-9]+-name')
    url = _urls['yahoo']['owner'] % self._league_num
    soup = BeautifulSoup(self._request(url).text)
    owners = []
    for link in soup.find_all(id=match_owner_link):
        ident = self._owner_id_from_url(link['href'])
        owners.append(Owner(ident, link.text.strip()))
    return owners

def roster(

self, player_search, owner, week)

Inheritance: Provider.roster

Given a Owner and a week number, this returns a Roster object. The Roster contains a list of nfldb.Player objects and their corresponding position on the roster.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

Note that the roster position is a string but the set of possible values is provider dependent. It is used for display purposes only.

def roster(self, player_search, owner, week):
    def to_pos(row):
        return row.td.find(class_='pos-label')['data-pos'].strip().upper()
    def to_name(row):
        return row.find(class_='ysf-player-name').a.text.strip()
    def to_team(row):
        team_pos = row.find(class_='ysf-player-name').span.text.strip()
        return nfldb.standard_team(re.search('^\S+', team_pos).group(0))
    def rplayer(r, name, team, pos):
        bench = pos == 'BN'
        if name is None and team is None:
            return r.new_player(pos, None, bench, None)
        elif nfldb.standard_team(name) != 'UNK':
            return r.new_player(pos, team, bench, None)
        else:
            player = player_search(name, team=team, position=pos)
            return r.new_player(pos, team, bench, player.player_id)
    match_table_id = re.compile('^statTable[0-9]+$')
    url = _urls['yahoo']['roster'] % (self._league_num, owner.ident, week)
    soup = BeautifulSoup(self._request(url).text)
    roster = Roster(owner, self._lg.season, week, [])
    for table in soup.find_all(id=match_table_id):
        for row in table.tbody.find_all('tr', recursive=False):
            pos = to_pos(row)
            try:
                team, name = to_team(row), to_name(row)
                roster.players.append(rplayer(roster, name, team, pos))
            except AttributeError:
                roster.players.append(rplayer(roster, None, None, pos))
    return roster

def save(

self, fp, player_search, week)

Inheritance: Provider.save

Writes a JSON encoding of all the owners, matchups and rosters for the given week to a file at fp.

player_search should be a function that takes a full player name and returns the closest matching player as a nfldb.Player object. It should also optionally take keyword arguments team and position that allow for extra filtering.

def save(self, fp, player_search, week):
    """
    Writes a JSON encoding of all the owners, matchups and rosters
    for the given week to a file at `fp`.
    `player_search` should be a function that takes a full
    player name and returns the closest matching player as a
    `nfldb.Player` object. It should also optionally take keyword
    arguments `team` and `position` that allow for extra filtering.
    """
    d = {
        'owners': self.owners(),
        'matchups': self.matchups(week),
    }
    # I'm hoping this doesn't hurt custom providers that don't need
    # to do IO to fetch a roster.
    def roster(owner):
        return self.roster(player_search, owner, week)
    # pool = multiprocessing.pool.ThreadPool(3) 
    # d['rosters'] = pool.map(roster, d['owners']) 
    d['rosters'] = map(roster, d['owners'])
    json.dump(d, open(fp, 'w+'))