Package nflgame :: Module live
[frames] | no frames]

Source Code for Module nflgame.live

  1  """ 
  2  The live module provides a mechanism of periodically checking which games are 
  3  being actively played. 
  4   
  5  It requires the third party library pytz to be 
  6  installed, which makes sure game times are compared properly with respect 
  7  to time zones. pytz can be downloaded from PyPI: 
  8  http://pypi.python.org/pypi/pytz/ 
  9   
 10  It works by periodically downloading data from NFL.com for games that started 
 11  before the current time. Once a game completes, the live module stops asking 
 12  NFL.com for data for that game. 
 13   
 14  If there are no games being actively played (i.e., it's been more than N hours 
 15  since the last game started), then the live module sleeps for longer periods 
 16  of time. 
 17   
 18  Thus, the live module can switch between two different modes: active and 
 19  inactive. 
 20   
 21  In the active mode, the live module downloads data from NFL.com in 
 22  short intervals. A transition to an inactive mode occurs when no more games 
 23  are being played. 
 24   
 25  In the inactive mode, the live module only checks if a game is playing (or 
 26  about to play) every 15 minutes. If a game is playing or about to play, the 
 27  live module switches to the active mode. Otherwise, it stays in the inactive 
 28  mode. 
 29   
 30  With this strategy, if the live module is working properly, you could 
 31  theoretically keep it running for the entire season. 
 32   
 33  (N.B. Half-time is ignored. Games are either being actively played or not.) 
 34  """ 
 35  import datetime 
 36  import time 
 37  import urllib2 
 38  import xml.dom.minidom as xml 
 39   
 40  import pytz 
 41   
 42  import nflgame.game 
 43  import nflgame.schedule 
 44   
 45  _MAX_GAME_TIME = 60 * 60 * 6 
 46  """ 
 47  The assumed maximum time allowed for a game to complete. This is used to 
 48  determine whether a particular game that isn't over is currently active. 
 49  """ 
 50   
 51  _WEEK_INTERVAL = 60 * 60 * 12 
 52  """ 
 53  How often to check what the current week is. By default, it is twice a day. 
 54  """ 
 55   
 56  _CUR_SCHEDULE_URL = "http://www.nfl.com/liveupdate/scorestrip/ss.xml" 
 57  """ 
 58  Pinged infrequently to discover the current week number, year and week type. 
 59  The actual schedule of games is taken from the schedule module. 
 60  """ 
 61   
 62  _EASTERN_TZ = pytz.timezone('US/Eastern') 
 63  """Used to convert game times in EST to UTC.""" 
 64   
 65  _cur_week = None 
 66  """The current week. It is updated infrequently automatically.""" 
 67   
 68  _cur_year = None 
 69  """The current year. It is updated infrequently automatically.""" 
 70   
 71  _preseason = False 
 72  """True when it's the preseason.""" 
 73   
 74  _regular = False 
 75  """True when it's the regular season.""" 
 76   
 77  _completed = [] 
 78  """ 
 79  A list of game eids that have been completed since the live module started 
 80  checking for updated game stats. 
 81  """ 
 82   
 83   
84 -def run(callback, active_interval=15, inactive_interval=900, stop=None):
85 """ 86 Starts checking for games that are currently playing. 87 88 Every time there is an update, callback will be called with two lists: 89 active and completed. The active list is a list of game.Game that are 90 currently being played. The completed list is a list of game.Game that 91 have just finished. A game will appear in the completed list only once, 92 after which that game will not be in either the active or completed lists. 93 No game can ever be in both lists at the same time. 94 95 It is possible that a game in the active list is not yet playing because 96 it hasn't started yet. It ends up in the active list because the "pregame" 97 has started on NFL.com's GameCenter web site, and sometimes game data is 98 partially filled. When this is the case, the 'playing' method on 99 a nflgame.game.Game will return False. 100 101 When in the active mode (see live module description), active_interval 102 specifies the number of seconds to wait between checking for updated game 103 data. Please do not make this number too low to avoid angering NFL.com. 104 If you anger them too much, it is possible that they could ban your IP 105 address. 106 107 Note that NFL.com's GameCenter page is updated every 15 seconds, so 108 setting the active_interval much smaller than that is wasteful. 109 110 When in the inactive mode (see live module description), inactive_interval 111 specifies the number of seconds to wait between checking whether any games 112 have started or are about to start. 113 114 With the default parameters, run will never stop. However, you may set 115 stop to a Python datetime.datetime value. After time passes the stopping 116 point, run will quit. (Technically, it's possible that it won't quit until 117 at most inactive_interval seconds after the stopping point is reached.) 118 The stop value is compared against datetime.datetime.now(). 119 """ 120 active = False 121 last_week_check = _update_week_number() 122 123 # Before we start with the main loop, we make a first pass at what we 124 # believe to be the active games. Of those, we check to see if any of 125 # them are actually already over, and add them to _completed. 126 for info in _active_games(inactive_interval): 127 game = nflgame.game.Game(info['eid']) 128 129 # If we couldn't get a game, that probably means the JSON feed 130 # isn't available yet. (i.e., we're early.) 131 if game is None: 132 continue 133 134 # Otherwise, if the game is over, add it to our list of completed 135 # games and move on. 136 if game.game_over(): 137 _completed.append(info['eid']) 138 139 while True: 140 if stop is not None and datetime.datetime.now() > stop: 141 return 142 143 if time.time() - last_week_check > _WEEK_INTERVAL: 144 last_week_check = _update_week_number() 145 146 games = _active_games(inactive_interval) 147 if active: 148 active = _run_active(callback, games) 149 if not active: 150 continue 151 time.sleep(active_interval) 152 else: 153 active = not _run_inactive(games) 154 if active: 155 continue 156 time.sleep(inactive_interval)
157 158
159 -def _run_active(callback, games):
160 """ 161 The active mode traverses each of the active games and fetches info for 162 each from NFL.com. 163 164 Then each game (that has info available on NFL.com---that is, the game 165 has started) is added to one of two lists: active and completed, which 166 are passed as the first and second parameters to callback. A game is 167 put in the active list if it's still being played, and into the completed 168 list if it has finished. In the latter case, it is added to a global store 169 of completed games and will never be passed to callback again. 170 """ 171 # There are no active games, so just quit and return False. Which means 172 # we'll transition to inactive mode. 173 if len(games) == 0: 174 return False 175 176 active, completed = [], [] 177 for info in games: 178 game = nflgame.game.Game(info['eid']) 179 180 # If no JSON was retrieved, then we're probably just a little early. 181 # So just ignore it for now---but we'll keep trying! 182 if game is None: 183 continue 184 185 # If the game is over, added it to completed and _completed. 186 if game.game_over(): 187 completed.append(game) 188 _completed.append(info['eid']) 189 else: 190 active.append(game) 191 192 callback(active, completed) 193 return True
194 195
196 -def _run_inactive(games):
197 """ 198 The inactive mode simply checks if there are any active games. If there 199 are, inactive mode needs to stop and transition to active mode---thus 200 we return False. If there aren't any active games, then the inactive 201 mode should continue, where we return True. 202 203 That is, so long as there are no active games, we go back to sleep. 204 """ 205 return len(games) == 0
206 207
208 -def _active_games(inactive_interval):
209 """ 210 Returns a list of all active games. In this case, an active game is a game 211 that will start within inactive_interval seconds, or has started within 212 _MAX_GAME_TIME seconds in the past. 213 """ 214 games = [] 215 for (year, t, week, _, _), info in nflgame.schedule.games: 216 if year != _cur_year: 217 continue 218 if week != _cur_week: 219 continue 220 if t == 'PRE' and not _preseason: 221 continue 222 if t == 'REG' and not _regular: 223 continue 224 if not _game_is_active(info, inactive_interval): 225 continue 226 games.append(info) 227 return games
228 229
230 -def _game_is_active(gameinfo, inactive_interval):
231 """ 232 Returns true if the game is active. A game is considered active if the 233 game start time is in the past and not in the completed list (which is 234 a private module level variable that is populated automatically) or if the 235 game start time is within inactive_interval seconds from starting. 236 """ 237 gametime = _game_datetime(gameinfo) 238 now = _now() 239 if gametime >= now: 240 return (gametime - now).seconds <= inactive_interval 241 return gameinfo['eid'] not in _completed
242 243
244 -def _seconds_before_game(gametime):
245 now = _now() 246 assert now <= gametime 247 return (gametime - now).seconds
248 249
250 -def _seconds_after_game(gametime):
251 now = _now() 252 assert now >= gametime 253 return (now - gametime).seconds
254 255
256 -def _game_datetime(gameinfo):
257 hour, minute = gameinfo['time'].strip().split(':') 258 d = datetime.datetime(gameinfo['year'], gameinfo['month'], gameinfo['day'], 259 (int(hour) + 12) % 24, int(minute)) 260 return _EASTERN_TZ.localize(d).astimezone(pytz.utc)
261 262
263 -def _now():
264 return datetime.datetime.now(pytz.utc)
265 266
267 -def _update_week_number():
268 global _cur_week, _cur_year, _preseason, _regular 269 270 dom = xml.parse(urllib2.urlopen(_CUR_SCHEDULE_URL)) 271 gms = dom.getElementsByTagName('gms')[0] 272 _cur_week = int(gms.getAttribute('w')) 273 _cur_year = int(gms.getAttribute('y')) 274 _preseason = gms.getAttribute('t').strip() == 'P' 275 _regular = gms.getAttribute('t').strip() == 'R' 276 return time.time()
277