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  Alpha status 
 36  ============ 
 37  This module is emphatically in alpha status. I believe things will work OK for 
 38  the regular season, but the postseason brings new challenges. Moreover, it 
 39  will probably affect the API at least a little bit. 
 40  """ 
 41  import datetime 
 42  import time 
 43  import urllib2 
 44  import xml.dom.minidom as xml 
 45   
 46  try: 
 47      import pytz 
 48  except ImportError: 
 49      pass 
 50   
 51  import nflgame 
 52  import nflgame.game 
 53  import nflgame.schedule 
 54   
 55  # [00:21] <rasher> burntsushi: Alright, the schedule changes on Wednesday 7:00 
 56  # UTC during the regular season 
 57   
 58  _MAX_GAME_TIME = 60 * 60 * 6 
 59  """ 
 60  The assumed maximum time allowed for a game to complete. This is used to 
 61  determine whether a particular game that isn't over is currently active. 
 62  """ 
 63   
 64  _WEEK_INTERVAL = 60 * 60 * 12 
 65  """ 
 66  How often to check what the current week is. By default, it is twice a day. 
 67  """ 
 68   
 69  _CUR_SCHEDULE_URL = "http://www.nfl.com/liveupdate/scorestrip/ss.xml" 
 70  """ 
 71  Pinged infrequently to discover the current week number, year and week type. 
 72  The actual schedule of games is taken from the schedule module. 
 73  """ 
 74   
 75  # http://www.nfl.com/ajax/scorestrip?season=2009&seasonType=POST&week=22 
 76  _POST_URL = "http://static.nfl.com/liveupdate/scorestrip/postseason/ss.xml" 
 77  """ 
 78  The URL for the XML schedule of the post season. This is only used 
 79  during the post season. 
 80   
 81  TODO: How do we know if it's the post season? 
 82  """ 
 83   
 84  _cur_week = None 
 85  """The current week. It is updated infrequently automatically.""" 
 86   
 87  _cur_year = None 
 88  """The current year. It is updated infrequently automatically.""" 
 89   
 90  _preseason = False 
 91  """True when it's the preseason.""" 
 92   
 93  _regular = False 
 94  """True when it's the regular season.""" 
 95   
 96  _completed = [] 
 97  """ 
 98  A list of game eids that have been completed since the live module started 
 99  checking for updated game stats. 
100  """ 
101   
102   
103 -def current_year_and_week():
104 """ 105 Returns a tuple (year, week) where year is the current year of the season 106 and week is the current week number of games being played. 107 i.e., (2012, 3). 108 109 N.B. This always downloads the schedule XML data. 110 """ 111 dom = xml.parse(urllib2.urlopen(_CUR_SCHEDULE_URL)) 112 gms = dom.getElementsByTagName('gms')[0] 113 year = int(gms.getAttribute('y')) 114 week = int(gms.getAttribute('w')) 115 return (year, week)
116 117
118 -def current_games(year=None, week=None, kind='REG'):
119 """ 120 Returns a list of game.Games of games that are currently playing. 121 This fetches all current information from NFL.com. 122 123 If either year or week is none, then the current year and week are 124 fetched from the schedule on NFL.com. If they are *both* provided, then 125 the schedule from NFL.com won't need to be downloaded, and thus saving 126 time. 127 128 So for example:: 129 130 year, week = nflgame.live.current_year_and_week() 131 while True: 132 games = nflgame.live.current_games(year, week) 133 # Do something with games 134 time.sleep(60) 135 136 The kind parameter specifies whether to fetch preseason, regular season 137 or postseason games. Valid values are PRE, REG and POST. 138 """ 139 if year is None or week is None: 140 year, week = current_year_and_week() 141 142 guesses = [] 143 now = _now() 144 games = _games_in_week(year, week, kind='REG') 145 for info in games: 146 gametime = _game_datetime(info) 147 if gametime >= now: 148 if (gametime - now).total_seconds() <= 60 * 15: 149 guesses.append(info['eid']) 150 elif (now - gametime).total_seconds() <= _MAX_GAME_TIME: 151 guesses.append(info['eid']) 152 153 # Now we have a list of all games that are currently playing, are 154 # about to start in less than 15 minutes or have already been playing 155 # for _MAX_GAME_TIME (6 hours?). Now fetch data for each of them and 156 # rule out games in the last two categories. 157 current = [] 158 for guess in guesses: 159 game = nflgame.game.Game(guess['eid']) 160 if game.playing(): 161 current.append(game) 162 return current
163 164
165 -def run(callback, active_interval=15, inactive_interval=900, stop=None):
166 """ 167 Starts checking for games that are currently playing. 168 169 Every time there is an update, callback will be called with two lists: 170 active and completed. The active list is a list of game.Game that are 171 currently being played. The completed list is a list of game.Game that 172 have just finished. A game will appear in the completed list only once, 173 after which that game will not be in either the active or completed lists. 174 No game can ever be in both lists at the same time. 175 176 It is possible that a game in the active list is not yet playing because 177 it hasn't started yet. It ends up in the active list because the "pregame" 178 has started on NFL.com's GameCenter web site, and sometimes game data is 179 partially filled. When this is the case, the 'playing' method on 180 a nflgame.game.Game will return False. 181 182 When in the active mode (see live module description), active_interval 183 specifies the number of seconds to wait between checking for updated game 184 data. Please do not make this number too low to avoid angering NFL.com. 185 If you anger them too much, it is possible that they could ban your IP 186 address. 187 188 Note that NFL.com's GameCenter page is updated every 15 seconds, so 189 setting the active_interval much smaller than that is wasteful. 190 191 When in the inactive mode (see live module description), inactive_interval 192 specifies the number of seconds to wait between checking whether any games 193 have started or are about to start. 194 195 With the default parameters, run will never stop. However, you may set 196 stop to a Python datetime.datetime value. After time passes the stopping 197 point, run will quit. (Technically, it's possible that it won't quit until 198 at most inactive_interval seconds after the stopping point is reached.) 199 The stop value is compared against datetime.datetime.now(). 200 """ 201 active = False 202 last_week_check = _update_week_number() 203 204 # Before we start with the main loop, we make a first pass at what we 205 # believe to be the active games. Of those, we check to see if any of 206 # them are actually already over, and add them to _completed. 207 for info in _active_games(inactive_interval): 208 game = nflgame.game.Game(info['eid']) 209 210 # If we couldn't get a game, that probably means the JSON feed 211 # isn't available yet. (i.e., we're early.) 212 if game is None: 213 continue 214 215 # Otherwise, if the game is over, add it to our list of completed 216 # games and move on. 217 if game.game_over(): 218 _completed.append(info['eid']) 219 220 while True: 221 if stop is not None and datetime.datetime.now() > stop: 222 return 223 224 if time.time() - last_week_check > _WEEK_INTERVAL: 225 last_week_check = _update_week_number() 226 227 games = _active_games(inactive_interval) 228 if active: 229 active = _run_active(callback, games) 230 if not active: 231 continue 232 time.sleep(active_interval) 233 else: 234 active = not _run_inactive(games) 235 if active: 236 continue 237 time.sleep(inactive_interval)
238 239
240 -def _run_active(callback, games):
241 """ 242 The active mode traverses each of the active games and fetches info for 243 each from NFL.com. 244 245 Then each game (that has info available on NFL.com---that is, the game 246 has started) is added to one of two lists: active and completed, which 247 are passed as the first and second parameters to callback. A game is 248 put in the active list if it's still being played, and into the completed 249 list if it has finished. In the latter case, it is added to a global store 250 of completed games and will never be passed to callback again. 251 """ 252 # There are no active games, so just quit and return False. Which means 253 # we'll transition to inactive mode. 254 if len(games) == 0: 255 return False 256 257 active, completed = [], [] 258 for info in games: 259 game = nflgame.game.Game(info['eid']) 260 261 # If no JSON was retrieved, then we're probably just a little early. 262 # So just ignore it for now---but we'll keep trying! 263 if game is None: 264 continue 265 266 # If the game is over, added it to completed and _completed. 267 if game.game_over(): 268 completed.append(game) 269 _completed.append(info['eid']) 270 else: 271 active.append(game) 272 273 callback(active, completed) 274 return True
275 276
277 -def _run_inactive(games):
278 """ 279 The inactive mode simply checks if there are any active games. If there 280 are, inactive mode needs to stop and transition to active mode---thus 281 we return False. If there aren't any active games, then the inactive 282 mode should continue, where we return True. 283 284 That is, so long as there are no active games, we go back to sleep. 285 """ 286 return len(games) == 0
287 288
289 -def _active_games(inactive_interval):
290 """ 291 Returns a list of all active games. In this case, an active game is a game 292 that will start within inactive_interval seconds, or has started within 293 _MAX_GAME_TIME seconds in the past. 294 """ 295 games = _games_in_week(_cur_year, _cur_week, kind='REG') 296 active = [] 297 for info in games: 298 if not _game_is_active(info, inactive_interval): 299 continue 300 active.append(info) 301 return active
302 303
304 -def _games_in_week(year, week, kind='REG'):
305 """ 306 A list for the games matching the year/week/kind parameters. 307 308 The kind parameter specifies whether to fetch preseason, regular season 309 or postseason games. Valid values are PRE, REG and POST. 310 """ 311 return nflgame._search_schedule(year, week, kind=kind)
312 313
314 -def _game_is_active(gameinfo, inactive_interval):
315 """ 316 Returns true if the game is active. A game is considered active if the 317 game start time is in the past and not in the completed list (which is 318 a private module level variable that is populated automatically) or if the 319 game start time is within inactive_interval seconds from starting. 320 """ 321 gametime = _game_datetime(gameinfo) 322 now = _now() 323 if gametime >= now: 324 return (gametime - now).total_seconds() <= inactive_interval 325 return gameinfo['eid'] not in _completed
326 327
328 -def _game_datetime(gameinfo):
329 hour, minute = gameinfo['time'].strip().split(':') 330 d = datetime.datetime(gameinfo['year'], gameinfo['month'], gameinfo['day'], 331 (int(hour) + 12) % 24, int(minute)) 332 return pytz.timezone('US/Eastern').localize(d).astimezone(pytz.utc)
333 334
335 -def _now():
336 return datetime.datetime.now(pytz.utc)
337 338
339 -def _update_week_number():
340 global _cur_week, _cur_year, _preseason, _regular 341 342 dom = xml.parse(urllib2.urlopen(_CUR_SCHEDULE_URL)) 343 gms = dom.getElementsByTagName('gms')[0] 344 _cur_week = int(gms.getAttribute('w')) 345 _cur_year = int(gms.getAttribute('y')) 346 _preseason = gms.getAttribute('t').strip() == 'P' 347 _regular = gms.getAttribute('t').strip() == 'R' 348 return time.time()
349