def setUp(self): # Create some saesons for year in range(1994, 2015): season = Season(season=year) season.save() self.seasons = Season.objects.all()
def create_new_season(current_season, end_date): # New season. logger.info("detected season change") # New season date is not super important, ladders around break will end up in correct season, so # set it to today + 1 day. season = current_season season.end_date = end_date season.save() logger.info("set end date %s on season %d" % (season.end_date, season.id)) next_season = season.get_next() next_season.start_date = end_date + timedelta(days=1) next_season.year = next_season.start_date.year next_season.number = 1 if next_season.year != season.year else season.number + 1 next_season.name = "%d Season %d" % (next_season.year, next_season.number) next_season.save() logger.info("set start data %s, year, name and number on season %d" % (next_season.start_date, next_season.id)) next_next_season = Season(id=next_season.id + 1, start_date=None, end_date=None, year=0, number=0, name='', version=Version.LOTV) next_next_season.save() logger.info("created empty season %d" % next_next_season.id) # Fixing rankings if they ended up with data time after season end. rankings = Ranking.objects.filter( season=season, status__in=(Ranking.COMPLETE_WITH_DATA, Ranking.COMPLETE_WITOUT_DATA), data_time__gte=season.end_time()).order_by('-id') for i, ranking in enumerate(rankings): ranking.data_time = season.end_time() - timedelta(seconds=i) ranking.save() logger.info( "changed data_time to %s for ranking %d since it was after season break" % (ranking.data_time, ranking.id)) # Warn about the event to make monitoring send eamil. logger.warning( "season break detected, current is now season %d, start_date %s" % (next_season.id, next_season.start_date))
def refetch_past_seasons(check_stop=lambda: None, bnet_client=None, now=None, skip_fetch_new=False): bnet_client = bnet_client or BnetClient() now = now or utcnow() # Wait for this date before refetching season. season_end_limit = now - timedelta( days=Season.REFETCH_PAST_MIN_DAYS_AFTER_SEASON_END) # Fetch new for a while after season close to make sure we got all ladders. Since we don't save non 200 leagues # we can still miss ladders here, but unlikely. There is no point in continuing after need_refetch_limit since they # will not be picked up in the ranking anyway. prev_season = Season.get_current_season().get_prev() need_refetch_limit = prev_season.end_time() + timedelta( days=Season.REFETCH_PAST_UNTIL_DAYS_AFTER_SEASON_END) if not skip_fetch_new and prev_season.end_time( ) < now <= need_refetch_limit: for region in Region.ranking_ids: fetch_new_in_region(check_stop, bnet_client, prev_season, region) # Refetch all past seasons. for season in Season.objects.filter( id__gt=14, end_date__lt=season_end_limit).order_by('-id'): refetch_past_season(season, now, check_stop, bnet_client) check_stop()
def get_season_based_on_fetch_time_and_match(fetch_time, match): """ Mach is a really special snowflake, it can return curr when it is prev right after season switch but never prev instead of curr (since if a new ladder is referred the player_ladder is up to date). This code will trust that prev/curr is correct and let code outside make a sanity check and keep it as nyd if this fails. Returns the ladder's season based on fetch_time and match. """ if match not in ('curr', 'prev'): raise Exception("match '%s' unexpected" % match) fetch_season = Season.get_active(fetch_time) if fetch_season.near_start(fetch_time): # May be season before if another region caused the season switch. fetch_valid = -1 elif fetch_season.is_open() or fetch_season.near_end(fetch_time): # We may actually be in the next season. fetch_valid = 1 else: # We are mid season so it is correct. fetch_valid = 0 if match == 'prev': fetch_season = fetch_season.get_prev() return fetch_season, fetch_valid
def fetch_data(tag, sort_key_id, is_reverse=None, league_id=None, region_id=None, race_id=None): # Get ids from db. teams = {t.id: t for t in Team.objects .filter(member0__tag=tag, mode=Mode.TEAM_1V1, season=Season.get_current_season()) .all() .select_related('member0')} # Fetch data from server. data = client.get_clan(team_ids=list(teams.keys()), key=sort_key_id, reverse=is_reverse, region=region_id, race=race_id, league=league_id) # Fetch information about teams from database. team_ranks = data['teams'] for tr in team_ranks: t = teams[tr["team_id"]] tr['rank'] += 1 tr["m0_id"] = t.member0.id tr["m0_name"] = t.member0.name tr["m0_bnet_url"] = get_bnet_url(t.member0) return data
def get(self, request, *args, **kwargs): context = self.get_context_data() current_season = Season.get_current_season() clan = request.GET.get('clan', None) if clan is None: context['clans'] = cache_value("top_clans", 1200, get_top_clans) else: clan = clan.strip() context['search'] = True clans = list(Player.objects .filter(season=current_season, mode=Mode.TEAM_1V1, tag__gt='') .filter(Q(clan__istartswith=clan) | Q(tag__istartswith=clan)) .values('clan', 'tag').annotate(count=Count('tag')).order_by('-count')[:33]) if len(clans) == 33: clans = clans[:32] context['left_out'] = True clans.sort(key=lambda c: c['clan']) if len(clans) == 1: return redirect('clan', tag=clans[0]['tag'], reverse='', sort_key=DEFAULT_SORT_KEY) context['clans'] = clans return self.render_to_response(context)
def create_season(self, **kwargs): kwargs = merge_args( { 'id': 16, 'start_date': '2013-11-11', 'end_date': '2014-01-03', 'name': '2013 Season 6', 'year': 2013, 'number': 6, 'version': Version.HOTS }, **kwargs) try: self.get(Season, id=kwargs['id']) raise AssertionError("Season with id %d already exists." % kwargs['id']) except Season.DoesNotExist: pass self.season = Season(**kwargs) self.season.save() return self.season
def fetch_new(region=None, check_stop=lambda: None, bnet_client=None): with transaction.atomic(): current_season = Season.get_current_season() for count in range(1, 3): check_stop() res = bnet_client.fetch_current_season(region) api_season = res.api_season if res.status == 200: update_season_cache(api_season, region, res) # Check season. if current_season.id == api_season.season_id(): # We already have latest season, continue with fetch. break elif current_season.id + 1 == api_season.season_id(): # New season detected, create new season and wait for next fetch new. create_new_season(current_season, api_season.start_date()) return elif current_season.near_start(utcnow(), days=2): logger.info( "current season %d near start, blizzard says %d, probably cached or other region" ", bailing" % (current_season.id, api_season.season_id())) return else: raise Exception( "season mismatch blizzard says %d, current in db is %d" % (api_season.season_id(), current_season.id)) else: # Is should be safe to continue after this since season id is in call to blizzard api. This is info logging # because it happens all the time due to some blizzard bug. logger.log( INFO, "could not get season info from %s after %d tries, status %s, skipping season check" % (api_season.url, count, res.status)) fetch_new_in_region(check_stop, bnet_client, current_season, region)
def countinously_update(regions=None, check_stop=None, update_manager=None, switch_hour=10): update_manager = update_manager or UpdateManager() ranking = Ranking.objects.order_by('-id').first() if ranking.status != Ranking.COMPLETE_WITH_DATA: raise Exception("ranking %d is not in a good state, clean up" % ranking.id) season = ranking.season cpp = sc2.RankingData(get_db_name(), Enums.INFO) while not check_stop(throw=False): # Check if we want to switch to new season. current_season = Season.get_current_season() if current_season.id != season.id: if current_season.id != season.get_next().id: raise Exception( "something is really wrong, current season is not next") if Ladder.objects.filter(season=current_season, strangeness=Ladder.GOOD).count() > 8: season = current_season logger.info( "switching to rank new season %d multiple new season ladders was detected" % season.id) # Do we want to create new ranking? We want to switch around switch_hour UTC every day but not if ranking is # too young. If too old, switch anyway. now = utcnow() if season.id != ranking.season_id: # Create new ranking based on new season. ranking = Ranking.objects.create(season=season, created=now, data_time=season.start_time(), min_data_time=season.start_time(), max_data_time=season.start_time(), status=Ranking.CREATED) logger.info("created new ranking %d based on new season %d" % (ranking.id, season.id)) cpp.clear_team_ranks() cpp.reconnect_db() update_manager.save_ranking(cpp, ranking, 0) elif ((ranking.created + timedelta(hours=48) < now or (ranking.created + timedelta(hours=12) < now and now.hour == switch_hour)) and not ranking.season.near_start(now, days=4)): # Create a new ranking within the season. cpp.clear_team_ranks() cpp.reconnect_db() with transaction.atomic(): new_ranking = Ranking.objects.create( season=season, created=now, data_time=ranking.data_time, min_data_time=ranking.min_data_time, max_data_time=ranking.max_data_time, status=Ranking.CREATED) # Copy all caches of old ranking to new ranking. Also remake the full ranking while doing so to get # rid of leave leaguers. logger.info( "created new ranking %d basing it on copy of ranking %d, seaons %d" % (new_ranking.id, ranking.id, season.id)) count = ranking.sources.count() logger.info( "copying %d cached ladders from ranking %d to ranking %d and adding them to ranking" % (count, ranking.id, new_ranking.id)) for i, lc in enumerate(ranking.sources.all(), start=1): lc.pk = None lc.created = utcnow() lc.ladder = None lc.ranking = ranking lc.save() new_ranking.sources.add(lc) ladder = Ladder.objects.get(region=lc.region, bid=lc.bid) team_size = Mode.team_size(ladder.mode) stats = cpp.update_with_ladder( ladder.id, lc.id, ladder.region, ladder.mode, ladder.league, ladder.tier, ladder.version, ladder.season_id, to_unix(lc.updated), team_size, ApiLadder(lc.data, lc.url).members_for_ranking(team_size)) if i % 100 == 0: logger.info( "copied and added cache %d/%d, player cache size %d, team cache size %d" % (i, count, stats['player_cache_size'], stats['team_cache_size'])) ranking = new_ranking update_manager.save_ranking(cpp, ranking, 0) else: logger.info("continuing with ranking %d, season %d" % (ranking.id, season.id)) cpp.reconnect_db() cpp.load(ranking.id) now = utcnow() until = now.replace(hour=switch_hour, minute=0, second=0) if until < now: until += timedelta(hours=24) update_manager.update_until(ranking=ranking, cpp=cpp, regions=regions, until=until, check_stop=check_stop)
def update_until(self, ranking=None, cpp=None, regions=None, until=None, check_stop=None, fetch_manager=None, bnet_client=None): """ Update until time until (utc) has passed and code outisde will decide if season and/or ranking switching should be done. """ bnet_client = bnet_client or BnetClient() fetch_manager = fetch_manager or FetchManager(ranking, regions, bnet_client) try: logger.info( "updating season %d, ranking %d, regions %s, until %s" % (ranking.season_id, ranking.id, regions, until)) last_save = utcnow() last_season_check = utcnow() last_gm = utcnow(days=-20) last_plat = utcnow(days=-20) last_rest = utcnow(days=-20) while not check_stop(throw=False): now = utcnow() if now > until: logger.info( "we reached until time %s, pausing to switch season/ranking" % until) break if now - last_season_check > timedelta(minutes=30): last_season_check = now if ranking.season_id != Season.get_current_season().id: logger.info( "current season %d is closed, pausing to give chance for season switch" % ranking.season_id) break if now - last_save > timedelta(seconds=60): self.save_ranking(cpp, ranking, len(fetch_manager.fetched_queue)) last_save = utcnow( ) # This can take a long time, so get new now again. if now - last_gm > timedelta(seconds=60): last_gm = now fetch_manager.start_gm() if now - last_plat > timedelta(minutes=10): last_plat = now fetch_manager.start_plat() if now - last_rest > timedelta(minutes=60): last_rest = now fetch_manager.start_rest() try: ladder, status, api_ladder, fetch_time = fetch_manager.pop( ) with transaction.atomic(): stats = update_ladder_cache(cpp, ranking, ladder, status, api_ladder, fetch_time) with LogContext(region=ladder.region): logger.info( "saved updated ladder %d and added data to ranking %d, " "updated %d players %d teams, inserted %d players %d teams, " "cache sizes %d players %d teams" % ( ladder.id, ranking.id, stats["updated_player_count"], stats["updated_team_count"], stats["inserted_player_count"], stats["inserted_team_count"], stats["player_cache_size"], stats["team_cache_size"], )) except IndexError: sleep(0.04) logger.info("stopped fetching, saving") fetch_manager.stop() fetch_manager.join() self.save_ranking(cpp, ranking, len(fetch_manager.fetched_queue)) except Exception: fetch_manager.stop() raise cpp.release()
class Db(object): """ Helper class to create test data in db. """ def __init__(self): self.db_name = connection.settings_dict['NAME'] print(self.db_name) self.team = None self.cache = None self.player = None self.season = None self.ladder = None self.ranking = None self.ranking_data = None self.default_ranking_data__data = {} self.clear_defaults() def clear_defaults(self): self.default_ranking_data__data = {} @staticmethod def filter(klass, *args, **kwargs): return klass.objects.filter(*args, **kwargs) @staticmethod def count(klass, *args, **kwargs): return klass.objects.filter(*args, **kwargs).count() @staticmethod def get(klass, *args, **kwargs): return klass.objects.get(*args, **kwargs) @staticmethod def objects(klass): return klass.objects @staticmethod def all(klass): return klass.objects.all() @staticmethod def execute(sql): cursor = connection.cursor() cursor.execute(sql) return cursor def delete_all(self, keep=None, delete=None): for d in (delete or [ RankingData, RankingStats, Cache, Ranking, Team, Player, Ladder, Season ]): if d not in (keep or []): if d == Ranking: cursor = connection.cursor() cursor.execute("update cache set ranking_id = NULL") if d == Ladder: cursor = connection.cursor() cursor.execute("update cache set ladder_id = NULL") self.all(d).delete() def create_season(self, **kwargs): kwargs = merge_args( { 'id': 16, 'start_date': '2013-11-11', 'end_date': '2014-01-03', 'name': '2013 Season 6', 'year': 2013, 'number': 6, 'version': Version.HOTS }, **kwargs) try: self.get(Season, id=kwargs['id']) raise AssertionError("Season with id %d already exists." % kwargs['id']) except Season.DoesNotExist: pass self.season = Season(**kwargs) self.season.save() return self.season def create_cache(self, type=Cache.LADDER, members=None, **kwargs): data = kwargs.pop('data', None) if data is None and members is not None: data = gen_ladder_data(members) kwargs = merge_args( { 'bid': randint(1, 1e6), 'url': 'http://bnet/' + uniqueid(10), 'type': type, 'region': Region.EU, 'created': utcnow(), 'updated': utcnow(), 'status': 200, 'retry_count': 0 }, **kwargs) kwargs['data'] = json.dumps(data) self.cache = Cache(**kwargs) self.cache.save() return self.cache def create_ladder(self, **kwargs): kwargs = merge_args( { 'bid': 1, 'region': Region.EU, 'strangeness': Ladder.GOOD, 'league': League.GOLD, 'tier': 0, 'version': Version.HOTS, 'mode': Mode.TEAM_1V1, 'season': self.season, 'first_join': utcnow(), 'last_join': utcnow(), 'created': utcnow(), 'updated': utcnow(), 'max_points': 20 }, **kwargs) self.ladder = Ladder(**kwargs) self.ladder.save() return self.ladder def create_player(self, **kwargs): kwargs = merge_args( { 'bid': randint(0, 1e9), 'region': Region.EU, 'realm': 1, 'mode': Mode.TEAM_1V1, 'season': self.season, 'race': Race.ZERG, 'name': uniqueid(12), 'clan': uniqueid(32), 'tag': uniqueid(6) }, **kwargs) self.player = Player(**kwargs) self.player.save() return self.player def create_team(self, **kwargs): kwargs = merge_args( dict(region=Region.EU, mode=Mode.TEAM_1V1, season=self.season, version=Version.HOTS, league=League.GOLD, member0=self.player, member1=None, member2=None, member3=None, race0=Race.ZERG, race1=Race.UNKNOWN, race2=Race.UNKNOWN, race3=Race.UNKNOWN), **kwargs) self.team = Team(**kwargs) self.team.save() return self.team def create_teams(self, count=1, **kwargs): teams = [] for i in range(count): self.create_player(name="%s-%d" % (uniqueid(8), i)) teams.append(self.create_team(**kwargs)) return teams def get_teams_by_member0_bids(self, *bids, mode=Mode.TEAM_1V1): tids = [] for bid in bids: p = self.get(Player, bid=bid) tids.append(self.get(Team, member0=p, mode=mode).id) return tids def create_ranking(self, **kwargs): kwargs = merge_args( dict(created=utcnow(), data_time=utcnow(), min_data_time=utcnow(), max_data_time=utcnow(), status=Ranking.COMPLETE_WITH_DATA, season=self.season), **kwargs) self.ranking = Ranking.objects.create(**kwargs) return self.ranking def _default_team_rank(self, team_rank): """ Update a team_rank dict with defaults. """ for k, v in self.default_ranking_data__data.items(): team_rank.setdefault(k, v) team_rank.setdefault("team_id", self.team.id) team_rank.setdefault("data_time", to_unix(self.ranking.data_time)) team_rank.setdefault("version", Version.HOTS) team_rank.setdefault("region", Region.EU) team_rank.setdefault("mode", Mode.TEAM_1V1) team_rank.setdefault("league", League.GOLD) team_rank.setdefault("tier", 0) team_rank.setdefault("ladder_id", self.ladder.id) team_rank.setdefault("join_time", to_unix(self.ranking.data_time)) team_rank.setdefault("source_id", self.cache.id) team_rank.setdefault("mmr", 1000) team_rank.setdefault("points", 100.0) team_rank.setdefault("wins", 10) team_rank.setdefault("losses", 10) team_rank.setdefault("race0", Race.ZERG) team_rank.setdefault("race1", Race.UNKNOWN) team_rank.setdefault("race2", Race.UNKNOWN) team_rank.setdefault("race3", Race.UNKNOWN) team_rank.setdefault("ladder_rank", 1) team_rank.setdefault("ladder_count", 1) team_rank.setdefault("league_rank", 1) team_rank.setdefault("league_count", 1) team_rank.setdefault("region_rank", 1) team_rank.setdefault("region_count", 1) team_rank.setdefault("world_rank", 1) team_rank.setdefault("world_count", 1) def create_ranking_data(self, raw=True, **kwargs): kwargs = merge_args(dict(ranking=self.ranking, updated=utcnow()), kwargs) data = kwargs.pop('data', []) ranking = kwargs['ranking'] for team_rank in data: self._default_team_rank(team_rank) ranking.sources.add(self.get(Cache, pk=team_rank['source_id'])) self.ranking_data = RankingData.objects.create(**kwargs) sc2.save_ranking_data_raw(self.db_name, ranking.id, 0, data, True) if not raw: cpp = sc2.RankingData(self.db_name, Enums.INFO) cpp.load(ranking.id) cpp.save_data(ranking.id, ranking.season_id, to_unix(utcnow())) cpp.release() return self.ranking_data def update_ranking_stats(self, ranking_id=None): """ Will build ranking stats based of the ranking by calling c++. """ if ranking_id is None: ranking_id = self.ranking.id cpp = sc2.RankingData(self.db_name, Enums.INFO) cpp.load(ranking_id) cpp.save_stats(ranking_id, to_unix(utcnow())) cpp.release()
def get_season_based_on_join_times(first_join, last_join): """ Get season based off first and last join times only. Return <season, value>, valid will be 0 if sure, -1 if could be season before +1 if could be season after (if both joins are to close to boundary). Season boundaries will not be exact (also join time is in unknown time zone), since we switch autimatically they will be on the date after switch or event two days after because of different time zones. Also ladders have been created well before season start some time. If both times are near a boundary the last_join will be used against the stored value on the season (this will make it more likely to fall in the first season). If there is player ladder data it will sort this out, if there is not it may stay wrong. NOTE: Join times can be off by serveral days, like 3-4 days. If both joins are too close to the same season end valid will be -1 or +1 and join season could be off by one season. """ context = "first_join %s, last_join %s" % (first_join, last_join) if last_join < first_join: raise Exception("%s, last_join is before first_join, this is strange" % context) first_season = Season.get_active(first_join) last_season = Season.get_active(last_join) context += ", first_season %s, last_season %d" % (first_season.id, last_season.id) if first_season.id == last_season.id: # Same season matched, this should be the most common case. if first_season.is_open() and not (first_season.near_start(first_join) or last_season.near_start(last_join))\ or not first_season.is_open() and first_season.near_end(first_join): # Compact join period to the end of season, insecure, could be season after. return last_season, 1 if last_season.near_start(last_join): # Compact join period to the start of season, insecure, could be season before. return last_season, -1 # One/both join in the middle or first in beginning and last in end, secure. return last_season, 0 if first_season.id + 1 == last_season.id: # End points in different seasons, prefer determine by last_join, but it depends on how valid it is. if last_season.near_start(last_join): # Last end is unsure. if first_season.near_end(first_join): # First end is also unsure, pick last_season, but may be season before. return last_season, -1 # First end is sure, pick first season. return first_season, 0 if first_season.near_end(first_join): # First end is unsure, pick last season. return last_season, 0 # This means both are sure but in different seasons. raise CatError( "%s, can't determine season this ladder seems to span full seasons" % context) if first_season.id + 2 == last_season.id \ and first_season.near_end(first_join) \ and last_season.near_start(last_join): # Both ends are insecure, so it has to be season in the middle. return first_season.get_next(), 0 raise CatError( "%s, this must be buggy, seasons wrong order or too far apart" % context)
def get_top_clans(): current_season = Season.get_current_season() return list(Player.objects .filter(season=current_season, mode=Mode.TEAM_1V1, tag__gt='') .values('clan', 'tag').annotate(count=Count('tag')).order_by('-count')[:32])
def get(self, request, *args, tag=None, reverse=None, sort_key=None, **kwargs): if sort_key == 'ladder-rank': return redirect(self.redirect_mmr_url(request, 'clan', tag=tag, reverse=reverse)) context = self.get_context_data() json_response = 'json' in request.GET if tag is None: raise Http404() region_id, race_id, league_id = self.extract_filters(request) is_reverse, sort_key_id = self.extract_common(reverse, sort_key) player = Player.objects.filter(tag=tag, mode=Mode.TEAM_1V1, season=Season.get_current_season()).first() if not player: raise Http404() context['tag'] = tag context['clan'] = player.clan try: data = self.fetch_data(tag, sort_key_id, is_reverse=is_reverse, league_id=league_id, region_id=region_id, race_id=race_id) except ClientError as e: logger.exception("Fetch from client error: %s" % str(e)) context['error'] = 'The server is not feeling well, try again later.' return self.respond(context, json_response) LadderCommon.update_age(data) context['ladder'] = data context['team_size'] = 1 paths = { 'tag': tag, 'sort_key': sort_key, 'reverse': reverse, } args = { 'region': Region.key_by_ids.get(region_id, Region.ALL_KEY), 'league': League.key_by_ids.get(league_id), 'race': Race.key_by_ids.get(race_id), } values = [('All', None)] + [(Race.name_by_ids[i], Race.key_by_ids[i]) for i in Race.ids if i != Race.UNKNOWN] LadderCommon.set_nav(context, request, clan_url, paths, args, name='race', values=values) values = [('All', None)] + [(League.name_by_ids[i], League.key_by_ids[i]) for i in League.ids if i not in (League.UNKNOWN, League.ALL)] LadderCommon.set_nav(context, request, clan_url, paths, args, name='league', values=values) values = [(Region.name_by_ids[i], Region.key_by_ids[i]) for i in Region.ids if i != Region.UNKNOWN] LadderCommon.set_nav(context, request, clan_url, paths, args, name='region', values=values) values = [('MMR', 'mmr'), ('League points', 'league-points'), ('Games played', 'played'), ('Wins', 'wins'), ('Losses', 'losses'), ('Win rate', 'win-rate')] LadderCommon.set_nav(context, request, clan_url, paths, args, name='sort_key', values=values) context['reverse_visiting'] = 'visiting' if reverse else '' context['reverse_href'] = clan_url(request, paths, args, 'reverse', '' if reverse else '-') return self.respond(context, json_response)
def fetch_new(region=None, check_stop=lambda: None, bnet_client=None): with transaction.atomic(): current_season = Season.get_current_season() for count in range(1, 6): check_stop() res = bnet_client.fetch_current_season(region) api_season = res.api_season if region == Region.SEA: if res.status != 200: logger.info( "could not get season info from %s, status %s, bailing" % (api_season.url, res.status)) return else: logger.warning("sea is alive") break if res.status == 200: break else: level = INFO if region == Region.CN else WARNING logger.log( level, "could not get season info from %s after %d tries, status %s, bailing" % (api_season.url, count, res.status)) return # Update or create the season cache, just for bookkeeping. try: cache = Cache.objects.get(region=region, type=Cache.SEASON, bid=1) cache.updated = res.fetch_time cache.data = api_season.to_text() cache.save() except Cache.DoesNotExist: Cache.objects.create(url=api_season.url, bid=1, type=Cache.SEASON, region=region, status=res.status, created=res.fetch_time, updated=res.fetch_time, data=api_season.to_text()) # Check season. if current_season.id == api_season.season_id(): # We already have latest season. pass elif current_season.id + 1 == api_season.season_id(): # New season detected, create new season. create_new_season(current_season, api_season.start_date()) return elif current_season.near_start(utcnow(), days=2): logger.info( "current season %d near start, blizzard says %d, probably cached or other region, bailing" % (current_season.id, api_season.season_id())) return else: raise Exception( "season mismatch blizzard says %d, current in db is %d" % (api_season.season_id(), current_season.id)) fetch_new_in_region(check_stop, bnet_client, current_season, region)