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:
#!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.
"""
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
def player_search(
db, full_name, team=None, position=None)
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 player_search(db, full_name, team=None, position=None):
"""
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.
"""
if position not in nfldb.Enums.player_pos:
position = None
p, _ = nfldb.player_search(db, full_name, team=team, position=position)
if p is None and position is not None:
p, _ = nfldb.player_search(db, full_name, team=team, position=None)
if p is None and team is not None:
p, _ = nfldb.player_search(db, full_name, team=None, position=position)
if p is None and team is not None and position is not None:
p, _ = nfldb.player_search(db, full_name, team=None, position=None)
return p
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 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)
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)
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)
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 is None or m.owner2 is None:
continue
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)
- League
- nflfan.provider.League
- __builtin__.tuple
- __builtin__.object
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 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 is None or m.owner2 is None:
continue
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)
- Matchup
- nflfan.provider.Matchup
- __builtin__.tuple
- __builtin__.object
Instance variables
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)
- Owner
- nflfan.provider.Owner
- __builtin__.tuple
- __builtin__.object
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)
- Provider
- __builtin__.object
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)
- Roster
- nflfan.provider.Roster
- __builtin__.tuple
- __builtin__.object
Instance variables
var active
var benched
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)
- RosterPlayer
- nflfan.provider.RosterPlayer
- __builtin__.tuple
- __builtin__.object
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)
- ScoreSchema
- nflfan.score.ScoreSchema
- __builtin__.tuple
- __builtin__.object
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 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)
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)
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)
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+'))